From e9d37cefcc950a62d9534aa24db643fa46333d9d Mon Sep 17 00:00:00 2001 From: Umputun Date: Thu, 20 Aug 2026 15:30:30 -0500 Subject: [PATCH 1/3] docs: move custom commands out of the scripting nav group keymap.conf is agterm's extension mechanism, but docs.html shelved it under SCRIPTING after the agtermctl reference, behind a heading that named key configuration. A user wanting one key that opens a file browser has no reason to look there and does not think he is scripting. Move the section to sit after Customization and rename it "Custom commands & keys". Content is unchanged; the diff is a relocation. --- site/docs.html | 1428 ++++++++++++++++++++++++------------------------ 1 file changed, 715 insertions(+), 713 deletions(-) diff --git a/site/docs.html b/site/docs.html index 6dfa1273..63f41bd9 100644 --- a/site/docs.html +++ b/site/docs.html @@ -194,6 +194,9 @@ Notifications Accessibility Customization + Custom commands & keys
SCRIPTING agtermctl reference @@ -204,7 +207,6 @@ class="hv9" >Cookbook ↗ - Customizing keys Ghostty config Agent status
@@ -1228,8 +1230,8 @@ - -
+ +

- agtermctl reference + Custom commands & keys

+

+ agterm reads a user-editable, kitty-flavored keymap file at + ~/.config/agterm/keymap.conf. It rebinds built-in menu shortcuts and defines custom shell commands bound to keys (and listed in the action + palette). The file is optional — a commented starter is written on first launch, and the directory can be changed in + Settings ▸ Key Mapping. Three verbs; blank lines and + # comments are ignored. +

-

- Looking for the full list? Every command, with its arguments, return values, and errors, lives on the - Command reference → -

+ # rebind a built-in to a chord (mods joined by +)
map + cmd+shift+l   toggle_split
map + ctrl+shift+k  command_palette

# or to several alternatives, joined by | with no spaces around it
map + cmd+t|ctrl+a>t  toggle_scratch

# define custom commands ("name" shows in the palette; chord optional)
command + "Open in Zed"  cmd+shift+e  open -a Zed {AGT_SESSION_PWD}
command + "Lazygit"      ctrl+a>g     agtermctl session overlay open 'zsh -lc lazygit' + --socket {AGT_SOCKET}
command + "Deploy"               ./deploy.sh

# one system-wide chord that summons the quick terminal from any application
global-hotkey ctrl+opt+space
-

- agterm can be driven from a script over a local unix-domain socket through the companion CLI, - agtermctl. This is for - fire-and-forget scripting that manages workspaces and sessions, injects text, invokes control actions, and polls a - control-event feed for status and lifecycle changes. There is no terminal-output streaming. +

+ A chord is modifier words (ctrl, cmd, + opt, + shift) joined by + + and a base key (a single + character or tab/space/return/delete/left/right/up/down). A Shift-typed symbol is written + shift+<base> + (e.g. shift+/ for + ?, + shift+= for + +) — the base key, not the + shifted symbol. Custom commands may also use a leader sequence + (ctrl+a>g), and their chord + must include a modifier — a bare key can't shadow a plain terminal key.

-

- To open a terminal at a directory without the CLI, - open -a agterm <path> — or - right-click a folder in Finder and choose Open With ▸ agterm. agterm adds a - session in that directory to the last-active window. This works when agterm is already running (its usual state); if it - isn't, launch agterm first, then run the command. The socket equivalent, and the way to place the session precisely, is - agtermctl session new --cwd <path>. +

+ global-hotkey takes exactly one + chord and binds it system-wide: it summons the quick terminal while any + application is frontmost, which no other binding here can do — the rest only fire while agterm has the keyboard. It + must carry a modifier, takes no alternatives and no leader sequence, and a second + global-hotkey line replaces the + first. macOS registers it by physical key position, so it keeps firing on a non-Latin layout. Because the system owns + it rather than agterm, it takes no part in the collision rules below. Note which side wins: the system-wide chord + takes the key even while agterm is in front, so binding one a menu item already uses means that menu shortcut stops + firing. It is unset unless you add the line: agterm ships no default because registering a chord takes it + from every other application on the Mac, and that is not a cost to impose on someone who may never summon + the panel from outside agterm. Spending it on the chord the in-app binding already uses is allowed and is + the way to get one shortcut that works everywhere — + global-hotkey ctrl+` + makes ⌃` summon the panel + from any application, at the price of that chord no longer reaching anything else.

-

- Each command targets a session or workspace by its UUID, a unique prefix of that UUID (git-style), or the keyword - active (the selected session / - current workspace). --target - defaults to active, so the - current one rarely needs naming. --target/--workspace take an id, a - prefix, or active — never a - name. For workspaces, - active is the one a new session - lands in: a workspace you just created in the foreground, otherwise the selected session's, otherwise the last one. - A newly created workspace stays the target until the selection changes — to a different session or to none — - or until you delete it or hide it behind the workspace filter. - workspace select moves the - target to the workspace you name, an empty one included. So New Session (and - session new) right after - creating one lands in the new workspace rather than the one you came from. Mutating commands normally print the - affected id; batch - session close and - session move accept repeated - --target options and print the - number of sessions actually changed. Add - --json for the raw response, - or --socket PATH to override - the socket. The exit code is zero on success, non-zero on error. +

+ One binding can offer several alternatives, joined by + | inside a single token with no + spaces around it — + map cmd+t|ctrl+a>t toggle_scratch + fires the action from either, and a command + takes alternatives the same way. For a built-in, the first single-chord alternative the menu can carry becomes the menu + shortcut; every + other alternative, on either verb, is delivered by a key monitor and so must carry a modifier on its first chord. This + is also how a built-in gets a leader sequence, because a menu item holds exactly one key equivalent: + map ctrl+a>s toggle_split binds + the sequence and leaves the action with no menu shortcut at all — its shipped ⌘D is gone rather than kept, and free for + another action to claim. A typo in one alternative rejects the whole line, so a mistake can't hide behind a line that + half worked; an alternative that merely breaks a rule or collides with an existing binding drops on its own and its + siblings keep firing. If a line ends up binding nothing at all, the action simply keeps the shortcut it shipped with. +

+

+ Chords are written in Latin and keep working on a non-Latin keyboard layout. A layout that cannot type ASCII — + Russian, Greek, Hebrew, Arabic, Thai — resolves every chord by the physical key position, so + cmd+o still fires on the key + marked O even though it types щ. + A layout that can type ASCII binds what it types, so an alternative Latin layout keeps its own letter positions: on + Dvorak, cmd+o follows the O you + actually type.

- Native picker + Bindable built-in actions

-

- agtermctl pick reads nonblank - lines or a JSON array of - {id,label,subtitle?} objects - from stdin and opens agterm's fuzzy picker. It blocks by default and prints the picked, custom, or cancelled result as - bare JSON. Add --no-block to - get the picker id immediately, then use - pick result or - pick cancel. The target tree's - top-level pickPending field - carries that id while the picker waits. -

-

- Typing matches item labels only; a subtitle is shown but never searched, so consequence text on one row cannot filter - out its safer neighbour. An empty query lists the items in the order they were supplied, so the caller's first item is - the one Return runs on open. - --query TEXT prefills the field - and filters immediately, which ranks by match score and therefore does not preserve that order. With - --allow-custom the item list may - be empty, which turns the picker into a plain text prompt: the custom row appears as soon as the query is nonblank, - whether prefilled by --query or - typed. An itemless call still reads stdin, so redirect it - (< /dev/null) or it blocks. -

- printf '%s\n' staging production | agtermctl pick --prompt - "Deploy where?"

- agtermctl pick --allow-custom --query "$name" --prompt - "Rename to" < /dev/null

- id=$(printf '%s\n' alpha beta | - agtermctl pick --no-block | jq -r '.id')
- agtermctl pick result "$id"
- agtermctl pick cancel "$id"
- agtermctl tree --json | jq -r - '.result.tree.pickPending // empty' + new_window   rename_window   delete_window
new_workspace   rename_workspace   delete_workspace
new_session   open_directory   rename_session   duplicate_session
close_session   reopen_recent   undo_close   clear_status
increase_font_size   decrease_font_size   reset_font_size
toggle_split   toggle_horizontal_split   toggle_scratch   toggle_search
toggle_sidebar   toggle_flag   toggle_flagged_view
focus_left_pane   focus_right_pane   focus_workspace   toggle_workspace_filter
toggle_workspace_collapse
previous_session   next_session   first_session   last_session
previous_workspace   next_workspace
previous_attention_session   next_attention_session
quick_terminal   session_palette   command_palette
custom_command_palette   show_attention
select_theme   toggle_fullscreen   toggle_terminal_zoom
dashboard
+

+ toggle_fullscreen is the one + action with no menu item of its own. macOS adds an "Enter Full Screen" item to the View menu whenever that menu is + drawn, and nothing suppresses it, so an item of agterm's own would sit beside it as a duplicate. The binding (⌃⌘F by + default) is handled directly instead, which is why the menu entry advertises the system's Globe+F rather than your + chord. Both work, and rebinding + toggle_fullscreen changes the + chord as usual — it just won't show up next to that menu item. +

- Sessions & workspaces + Command tokens

- # print the workspace/session tree with ids
agtermctl tree

# create a workspace, capture its id, open a session in it
ws=$(agtermctl workspace new work)
agtermctl session new --workspace - "$ws" --cwd ~/src/agterm

# run a command as the session's process (argv-style; wrap in sh -c for shell syntax)
agtermctl session new --command "ssh user@host"
agtermctl session new --command - "zsh -lc 'make test'" --wait -   # hold open after the command exits (press any key to close); needs --command
agtermctl session new - --name myhost --workspace-name - servers --create-workspace

# step / reorder / relocate
agtermctl session go --to next -    # next|prev|first|last
agtermctl session move --to up -    # reorder: up|down|top|bottom
agtermctl session move - "$ws"     # relocate to a workspace
agtermctl session new --after active -   # create right after the current session (--before to precede)
agtermctl session new --cwd ~/src/agterm --no-select -   # create in the background without switching to it
agtermctl session duplicate --target 9f3c -   # a fresh shell in that session's workspace and cwd, right after it
agtermctl session move --after - 9f3c   # place after an anchor (its workspace is used; relocates cross-workspace)
agtermctl session move "$ws" --target 9f3c --target abcd -   # move a batch as one ordered block
agtermctl session close --target 9f3c --target abcd -   # one grace-period undo for the batch
agtermctl workspace go --to next -    # step to the next workspace and select its first session
agtermctl workspace move --to top -    # reorder a workspace
agtermctl workspace new work --collapsed -    # create a workspace closed in the sidebar (fill with session new --no-select)
agtermctl workspace collapse --target 9f3c -  # collapse one workspace; workspace expand re-opens it
agtermctl workspace focus on -    # mark this workspace alone and apply the filter (on|off|toggle|add) -
agtermctl workspace focus add --target a1b2 -   # mark another one without narrowing the tree yet
agtermctl workspace filter on -      # apply the marked set; filter off suspends it without losing the set -
agtermctl session reveal --target 9f3c -  # reveal the focused pane's cwd in Finder + {AGT_SESSION_ID}   {AGT_SESSION_NAME}   {AGT_SESSION_PWD}
{AGT_WORKSPACE_ID}   {AGT_WORKSPACE_NAME}
{AGT_WINDOW_ID}   {AGT_WINDOW_NAME}
{AGT_PANE}   {AGT_SELECTION}   {AGT_SOCKET}
- -

+ Tokens expand at fire time (also exported as + $AGT_* env vars on the spawned + process). A token is substituted raw into the shell line, so for content you + don't control — + {AGT_SELECTION}, and also + {AGT_SESSION_NAME}/{AGT_SESSION_PWD} + (a remote host can set these via OSC) — prefer the matching quoted env var, e.g. + "$AGT_SELECTION". +

+
- Typing, selection & text -

+ Detached, no TTY — a + custom command runs as a detached + /bin/sh -c with no controlling terminal, so + it suits fire-and-forget launches (GUI apps, scripts) — not interactive TUIs. Run a TUI like + lazygit in an overlay + (agtermctl session overlay open) or a scratch + terminal, which have a real TTY. A non-zero exit posts a notification banner. +
- # inject text (every newline is a real Return; use $'…\n' or --stdin)
agtermctl session type --target 9f3c $'make test\n'
echo - 'make test' | agtermctl session type --stdin

# route to a pane: left (default) | right (split) | scratch (even when hidden)
agtermctl session type --pane right $'ls\n'

# type into a background session without changing focus
agtermctl session type --target "$id" - $'echo hi\n'

# read a pane's selection (does not touch the clipboard) or its output
sel=$(agtermctl session copy --target 9f3c)
agtermctl session text - --pane scratch --target 9f3c

# paste the system clipboard (⌘V), or select the whole buffer (⌘A) then copy it
agtermctl session paste --target 9f3c
agtermctl session select-all --target 9f3c + GUI PATH — a custom + command resolves its binaries against the app's GUI + PATH: the launchd default plus the bundled + agtermctl, + /usr/local/bin and + /opt/homebrew/bin. A bare + agtermctl or Homebrew binary works; + anything else your shell profile adds does not, and fails with exit 127. Give it an absolute path + or wrap the line in + zsh -lc '…' — + zsh -ilc '…' when that + PATH comes from + ~/.zshrc, which + -lc does not read. The program an + overlay or scratch terminal runs gets the app's own unwidened + PATH and always needs one of those.
- +

+ Open the file with File ▸ Edit Keymap… (or the ⌃⇧P palette): it opens in a + 95% overlay running $VISUAL/$EDITOR + (falling back to vi) and + reloads on quit. Apply edits made elsewhere with + File ▸ Reload Keymap or + agtermctl keymap reload. A + malformed line never discards the rest — it surfaces in the diagnostics list in Settings ▸ Key Mapping while the good + lines still apply. +

+

+ To check what is actually bound, + agtermctl keymap list prints every + built-in with the binds it resolved to (the menu shortcut first, then any alternatives, joined with + |), the custom commands, + each diagnostic in full, and the key equivalents the menu + bar is really carrying. If a binding will not fire, compare the last two: an action whose chord no menu item holds is + usually a menu problem, not a keymap one. Only the menu shortcut can appear there — an alternative never does, and + neither do + undo_close (⌘Z) and + toggle_fullscreen (⌃⌘F), which a + key monitor delivers rather than a menu item. +

- Splits, scratch, quick & search + v1 limitations

-
- agtermctl session split toggle --axis horizontal
agtermctl session split close -      # vertical=left/right; horizontal=top/bottom
agtermctl session resize --split-ratio 0.7 -  # primary fraction; grow role/position aliases also work
agtermctl session scratch toggle -     # on|off|toggle
agtermctl session flag on -        # on|off|toggle|clear
agtermctl session seen --target 9f3c -  # clear the unseen badge, focus-free
agtermctl - sidebar mode flagged    # tree|flagged|toggle
agtermctl quick - toggle        # show|hide|toggle
agtermctl quick type 'ls -la' -  # or --stdin; quick text reads it back
agtermctl surface zoom        # fill the window with the active surface (show|hide|toggle; --target surface:<id>:right)
agtermctl dashboard "$a" "$b" "$c" --auto-size  # view-only grid; a split session is two cells, capped at 9 panes (--mru; --font-size N | --auto-size; --close)
agtermctl font inc           # main pane font size
agtermctl font dec --pane right  # just the split pane (left|right|scratch)

# open the search bar, print the "N of M" counter
agtermctl - session search "error"
agtermctl session search --next -     # --prev | --close -
+
    +
  • + A built-in's menu shortcut is single-chord only: a leader sequence binds through the key monitor + instead, as an alternative, and so never shows next to its menu item. +
  • +
  • + A map line can't bind a + bare, modifier-less arrow — a built-in rides an always-on menu key equivalent, so a bare arrow would + swallow the key in the terminal, the palettes, the dashboard grid, and every text field; any modifier + makes it bindable. The literal + + and + > can't be a bare + key token (they are the separators), but those keys are bindable as + shift+= and + shift+.; only + increase_font_size's + default ⌘+ shows as a glyph because its stored form doesn't round-trip through the file. +
  • +
  • + The Ctrl-Tab MRU switcher and Ctrl-1/Ctrl-2 pane focus are not rebindable yet. +
  • +
  • + Shortcut hints are not written the same way for both verbs. A built-in shows macOS glyphs (⌘⇧E), with every + alternative listed space-separated and a sequence's chords joined by + > as in the file + (⌘T ⌃A>T), in the palette and in the toolbar and sidebar tooltips alike. A + command shows the raw kitty + spelling you typed instead (cmd+shift+e), alternatives still joined by |. +
  • +
+
-

+
+

- Overlays -

+ agtermctl reference +

- # full overlay on a session (hides the shell beneath)
agtermctl session - overlay open "revdiff HEAD~3" --target 9f3c

# floating framed panel at 70% of the pane, optionally tinted
agtermctl session overlay open "htop" --size-percent 70
agtermctl - session overlay open "revdiff HEAD~3" --size-percent 80 --background-color - "#2a1a3a"

# switch the user to the target as the overlay opens
agtermctl session overlay open "revdiff HEAD~3" --size-percent 80 - --target 9f3c --follow

# keep it open after exit, or block until it exits and inherit its status
agtermctl session overlay open "make test" --wait
agtermctl session - overlay open "make test" --block

# resize an open overlay in place — floating percent, or back to full (the program keeps running)
agtermctl session overlay resize --size-percent 60 --target 9f3c
agtermctl session overlay resize --full - --target 9f3c

agtermctl session overlay close --target - 9f3c
agtermctl session overlay result -   # last overlay's exit status

# cover only one split pane, leaving the sibling pane live
agtermctl session overlay open "revdiff HEAD~3" --target 9f3c --pane right

# read what is INSIDE the overlay — the selection the user made, or its drawn screen
agtermctl session overlay copy --target 9f3c --pane right
agtermctl session overlay text --target 9f3c - --pane right -
-

- --wait keeps a "press any key - to close" prompt so you can read the final output; - --block reports only the exit - status (the overlay never captures stdout — a TUI writes its own result file). By default an overlay opens on its - target without switching the active session (full and --size-percent - floating alike); - --follow switches the user to - the target. - session overlay resize changes an - open overlay in place — - --size-percent makes it floating, - --full switches it back — without - restarting the program. A - (overlay) tag in - agtermctl tree marks a session - whose overlay is open. +

+ Looking for the full list? Every command, with its arguments, return values, and errors, lives on the + Command reference → +

+ +

+ agterm can be driven from a script over a local unix-domain socket through the companion CLI, + agtermctl. This is for + fire-and-forget scripting that manages workspaces and sessions, injects text, invokes control actions, and polls a + control-event feed for status and lifecycle changes. There is no terminal-output streaming.

-

- session overlay copy returns the - selection made inside the overlay and - session overlay text its terminal - buffer. These exist because - session copy and - session text address the pane the - overlay covers: text you highlight in an overlay reads there as - no selection, and - session text --pane right hands - back the shell underneath. Reach for them when the read is not chord-driven: an agent polling from outside, or a - script wanting the selection some time after the fact. A chord already receives the selection of the surface it - fired in, the overlay included, as - $AGT_SELECTION, so a custom - command should use that rather than a later socket read. The buffer one returns a TUI's screen as drawn, wrapped - where it wrapped — for a program's real output, still read the file it writes. +

+ To open a terminal at a directory without the CLI, + open -a agterm <path> — or + right-click a folder in Finder and choose Open With ▸ agterm. agterm adds a + session in that directory to the last-active window. This works when agterm is already running (its usual state); if it + isn't, launch agterm first, then run the command. The socket equivalent, and the way to place the session precisely, is + agtermctl session new --cwd <path>.

-

- --pane left|right scopes the - overlay to one split pane instead of the whole session: it covers exactly that pane and leaves the sibling pane - visible and interactive, so an agent working in the split can show a diff or a TUI over its own pane without - blanking the one you are reading. The two panes are independent and may both hold an overlay at once, each with its - own background color. A pane overlay is always full-pane — there is no floating variant, so - --pane cannot combine with - --size-percent and - session overlay resize takes no - --pane — and everything else - matches the session-wide overlay. A non-split session accepts - --pane left, since its shell - reports AGTERM_PANE=left, so a - script can pass --pane "$AGTERM_PANE" - without checking whether the session is split. A pane that is not on screen is refused with - pane not visible; hiding the - split after opening is fine, the program keeps running and reappears when the split comes back. - close, - result, - copy and - text take the same - --pane, ⌘W dismisses the - focused pane's overlay before it would close the session (one on the other pane is not in front of you, so ⌘W keeps - its ordinary meaning), and - agtermctl tree --json reports the - covered panes as paneOverlays. +

+ Each command targets a session or workspace by its UUID, a unique prefix of that UUID (git-style), or the keyword + active (the selected session / + current workspace). --target + defaults to active, so the + current one rarely needs naming. --target/--workspace take an id, a + prefix, or active — never a + name. For workspaces, + active is the one a new session + lands in: a workspace you just created in the foreground, otherwise the selected session's, otherwise the last one. + A newly created workspace stays the target until the selection changes — to a different session or to none — + or until you delete it or hide it behind the workspace filter. + workspace select moves the + target to the workspace you name, an empty one included. So New Session (and + session new) right after + creating one lands in the new workspace rather than the one you came from. Mutating commands normally print the + affected id; batch + session close and + session move accept repeated + --target options and print the + number of sessions actually changed. Add + --json for the raw response, + or --socket PATH to override + the socket. The exit code is zero on success, non-zero on error.

- Message panel (HUD) + Native picker

+

+ agtermctl pick reads nonblank + lines or a JSON array of + {id,label,subtitle?} objects + from stdin and opens agterm's fuzzy picker. It blocks by default and prints the picked, custom, or cancelled result as + bare JSON. Add --no-block to + get the picker id immediately, then use + pick result or + pick cancel. The target tree's + top-level pickPending field + carries that id while the picker waits. +

+

+ Typing matches item labels only; a subtitle is shown but never searched, so consequence text on one row cannot filter + out its safer neighbour. An empty query lists the items in the order they were supplied, so the caller's first item is + the one Return runs on open. + --query TEXT prefills the field + and filters immediately, which ranks by match score and therefore does not preserve that order. With + --allow-custom the item list may + be empty, which turns the picker into a plain text prompt: the custom row appears as soon as the query is nonblank, + whether prefilled by --query or + typed. An itemless call still reads stdin, so redirect it + (< /dev/null) or it blocks. +

- # post a message over a session; the session keeps focus and stays typable
agtermctl - session hud "gathering options…" --spinner --detail - "scanning branches" --target - "$AGTERM_SESSION_ID"

# repaint in place, no re-spawn and no blink
agtermctl session hud update "ready" --detail - "pick a branch" --target - "$AGTERM_SESSION_ID"

# anchor it and set its width by hand instead of measuring the message
agtermctl session hud "deploying" --position top-right --size-percent - 30

# color both halves of the panel
agtermctl session hud "deploying" --text-color - "#7ec07e" --background-color - "#202020"

agtermctl session hud close --target "$AGTERM_SESSION_ID" + printf '%s\n' staging production | agtermctl pick --prompt + "Deploy where?"

+ agtermctl pick --allow-custom --query "$name" --prompt + "Rename to" < /dev/null

+ id=$(printf '%s\n' alpha beta | + agtermctl pick --no-block | jq -r '.id')
+ agtermctl pick result "$id"
+ agtermctl pick cancel "$id"
+ agtermctl tree --json | jq -r + '.result.tree.pickPending // empty'
-

- A HUD carries a message rather than a program, and it is passive: the session keeps first responder and stays - typable under the panel, the terminal behind it is neither dimmed nor click-blocked, and nothing waits for an - answer. It is for the seconds a script or an agent needs before it can show anything, so the session says what is - happening rather than sitting silent. - open is the default - subcommand, so session hud "…" - posts one. - --position - anchors it to any of the nine positions - session background takes — - top-left, - top-center, - top-right, - center-left, - center, - center-right, - bottom-left, - bottom-center, - bottom-right — defaulting to - center, with every anchor off - center holding a margin off that pane edge on its own; a corner is what keeps a panel out of the text you are - reading. The bare - top and - bottom this took before are - still accepted for the middle column. The panel comes from the message on both axes — width from the longest line, height - from the number of them, so a title and a subtitle give a wide, short panel rather than a square one. - --size-percent overrides the - width only, bounded to at most 80% of the pane, so a message never covers the session it is about. - --text-color sets the text's - own color and - --background-color the panel's - backing, so the two halves are independent. - An update replaces the whole message, so - --detail, - --spinner and - --text-color have to be repeated - to survive it. The spinner takes a style — - --spinner-style bar|braille|circle|blocks|dot|none, - which turns it on by itself, with - dot blinking rather than - animating — and an update may switch style in place; - --background-color is the - exception, read once when the panel is created and kept — in the panel and in the read-back — across every - update, which is why an update does not take it at all. -

-

- It shares the overlay slot, so a second - hud replaces the first, - session overlay open replaces a - HUD, and ⌘W or - session overlay close takes one - down; a HUD over a running program is refused instead, because a message is replaceable and a program is - not. - session overlay result over a - HUD errors, and - session overlay resize accepts a - percent but refuses --full, - which would cover the session the message is about. - agtermctl tree --json reports - the panel as the session node's - hud object, with - overlay reading false beside it; - there is no event, so a script that needs the state polls the tree. -

- Windows + Sessions & workspaces

- agtermctl window list     # id name [open] [active]
w=$(agtermctl window new work)
agtermctl window new proj-b - --minimized  # create one, parked in the Dock
agtermctl window select - "$w"  # raise it (opening if closed)
agtermctl window rename "$w" personal -
agtermctl window minimize "$w" on -  # park it in the Dock (off restores)
agtermctl window close - "$w"   # close its window (bundle kept)
agtermctl window delete "$w" -  # delete (last window can't be deleted)

# print the workspace/session tree with ids
agtermctl tree

# --window targets a specific window's tree on session/workspace/tree/font
agtermctl tree --window "$w"
agtermctl session new --window - "$w" --cwd ~/src/agterm -
- -

- Keymap, config & notify -

-
ws=$(agtermctl workspace new work)
agtermctl session new --workspace + "$ws" --cwd ~/src/agterm

# run a command as the session's process (argv-style; wrap in sh -c for shell syntax)
agtermctl session new --command "ssh user@host"
agtermctl session new --command + "zsh -lc 'make test'" --wait +   # hold open after the command exits (press any key to close); needs --command
agtermctl session new + --name myhost --workspace-name + servers --create-workspace

# step / reorder / relocate
agtermctl session go --to next +    # next|prev|first|last
agtermctl session move --to up +    # reorder: up|down|top|bottom
agtermctl session move + "$ws"     # relocate to a workspace
agtermctl session new --after active +   # create right after the current session (--before to precede)
agtermctl session new --cwd ~/src/agterm --no-select +   # create in the background without switching to it
agtermctl session duplicate --target 9f3c +   # a fresh shell in that session's workspace and cwd, right after it
agtermctl session move --after + 9f3c   # place after an anchor (its workspace is used; relocates cross-workspace)
agtermctl session move "$ws" --target 9f3c --target abcd +   # move a batch as one ordered block
agtermctl session close --target 9f3c --target abcd +   # one grace-period undo for the batch
agtermctl workspace go --to next +    # step to the next workspace and select its first session
agtermctl workspace move --to top +    # reorder a workspace
agtermctl workspace new work --collapsed +    # create a workspace closed in the sidebar (fill with session new --no-select)
agtermctl workspace collapse --target 9f3c +  # collapse one workspace; workspace expand re-opens it
agtermctl workspace focus on +    # mark this workspace alone and apply the filter (on|off|toggle|add) +
agtermctl workspace focus add --target a1b2 +   # mark another one without narrowing the tree yet
agtermctl workspace filter on +      # apply the marked set; filter off suspends it without losing the set +
agtermctl session reveal --target 9f3c +  # reveal the focused pane's cwd in Finder +
+ +

+ Typing, selection & text +

+
- agtermctl keymap reload -  # re-read keymap.conf, returns the diagnostic count
agtermctl config - reload  # re-read the ghostty config, returns the diagnostic count
agtermctl notify --title Build --body - "tests passed" + # inject text (every newline is a real Return; use $'…\n' or --stdin)
agtermctl session type --target 9f3c $'make test\n'
echo + 'make test' | agtermctl session type --stdin

# route to a pane: left (default) | right (split) | scratch (even when hidden)
agtermctl session type --pane right $'ls\n'

# type into a background session without changing focus
agtermctl session type --target "$id" + $'echo hi\n'

# read a pane's selection (does not touch the clipboard) or its output
sel=$(agtermctl session copy --target 9f3c)
agtermctl session text + --pane scratch --target 9f3c

# paste the system clipboard (⌘V), or select the whole buffer (⌘A) then copy it
agtermctl session paste --target 9f3c
agtermctl session select-all --target 9f3c
+

+ Splits, scratch, quick & search +

- In-session env — a shell - inside agterm gets AGTERM_ENABLED, - AGTERM_WINDOW_ID, - AGTERM_WORKSPACE_ID, - AGTERM_SESSION_ID, - AGTERM_SOCKET (the live socket path), and - AGTERM_PANE (which pane the shell runs in — - left (main), - right (split), or - scratch; unset in an overlay), and - AGTERM_PANE_ID (a stable per-surface token the - agent-status hook forwards as session status --pane-id, - so a promoted-then-re-split pane still tags correctly), so a - script can drive its own window without hard-coding ids. + agtermctl session split toggle --axis horizontal
agtermctl session split close +      # vertical=left/right; horizontal=top/bottom
agtermctl session resize --split-ratio 0.7 +  # primary fraction; grow role/position aliases also work
agtermctl session scratch toggle +     # on|off|toggle
agtermctl session flag on +        # on|off|toggle|clear
agtermctl session seen --target 9f3c +  # clear the unseen badge, focus-free
agtermctl + sidebar mode flagged    # tree|flagged|toggle
agtermctl quick + toggle        # show|hide|toggle
agtermctl quick type 'ls -la' +  # or --stdin; quick text reads it back
agtermctl surface zoom        # fill the window with the active surface (show|hide|toggle; --target surface:<id>:right)
agtermctl dashboard "$a" "$b" "$c" --auto-size  # view-only grid; a split session is two cells, capped at 9 panes (--mru; --font-size N | --auto-size; --close)
agtermctl font inc           # main pane font size
agtermctl font dec --pane right  # just the split pane (left|right|scratch)

# open the search bar, print the "N of M" counter
agtermctl + session search "error"
agtermctl session search --next +     # --prev | --close
-

- The cookbook collects - complete agtermctl - workflows, each in its own directory with a README and, where it needs one, its scripts: switching the sidebar to a - single project, closing a project's workspaces and bringing them back later, picking a path with - fzf and typing it into the - shell, and giving each tab its own Claude Code or Codex conversation across a restart. They are written to be copied - into your own setup and edited, not only read; - cookbook/CONTRIBUTING.md - has the rules for adding one. -

-

- Recipes come from other people as well as the maintainer. Every one is reviewed before it is accepted, but they are - shell scripts you run on your own machine against your own sessions, and several close sessions or delete - workspaces, so read a recipe before you run it. -

-
- - -
-

- Customizing keys -

-

- agterm reads a user-editable, kitty-flavored keymap file at - ~/.config/agterm/keymap.conf. It rebinds built-in menu shortcuts and defines custom shell commands bound to keys (and listed in the action - palette). The file is optional — a commented starter is written on first launch, and the directory can be changed in - Settings ▸ Key Mapping. Three verbs; blank lines and - # comments are ignored. -

+ Overlays +
- # rebind a built-in to a chord (mods joined by +)
map - cmd+shift+l   toggle_split
map - ctrl+shift+k  command_palette

# or to several alternatives, joined by | with no spaces around it
map - cmd+t|ctrl+a>t  toggle_scratch

# define custom commands ("name" shows in the palette; chord optional)
command - "Open in Zed"  cmd+shift+e  open -a Zed {AGT_SESSION_PWD}
command - "Lazygit"      ctrl+a>g     agtermctl session overlay open 'zsh -lc lazygit' - --socket {AGT_SOCKET}
command - "Deploy"               ./deploy.sh

# full overlay on a session (hides the shell beneath)
agtermctl session + overlay open "revdiff HEAD~3" --target 9f3c

# one system-wide chord that summons the quick terminal from any application
global-hotkey ctrl+opt+space + ># floating framed panel at 70% of the pane, optionally tinted
agtermctl session overlay open "htop" --size-percent 70
agtermctl + session overlay open "revdiff HEAD~3" --size-percent 80 --background-color + "#2a1a3a"

# switch the user to the target as the overlay opens
agtermctl session overlay open "revdiff HEAD~3" --size-percent 80 + --target 9f3c --follow

# keep it open after exit, or block until it exits and inherit its status
agtermctl session overlay open "make test" --wait
agtermctl session + overlay open "make test" --block

# resize an open overlay in place — floating percent, or back to full (the program keeps running)
agtermctl session overlay resize --size-percent 60 --target 9f3c
agtermctl session overlay resize --full + --target 9f3c

agtermctl session overlay close --target + 9f3c
agtermctl session overlay result +   # last overlay's exit status

# cover only one split pane, leaving the sibling pane live
agtermctl session overlay open "revdiff HEAD~3" --target 9f3c --pane right

# read what is INSIDE the overlay — the selection the user made, or its drawn screen
agtermctl session overlay copy --target 9f3c --pane right
agtermctl session overlay text --target 9f3c + --pane right

- A chord is modifier words (ctrl, cmd, - opt, - shift) joined by - + and a base key (a single - character or tab/space/return/delete/left/right/up/down). A Shift-typed symbol is written - shift+<base> - (e.g. shift+/ for - ?, - shift+= for - +) — the base key, not the - shifted symbol. Custom commands may also use a leader sequence - (ctrl+a>g), and their chord - must include a modifier — a bare key can't shadow a plain terminal key. + --wait keeps a "press any key + to close" prompt so you can read the final output; + --block reports only the exit + status (the overlay never captures stdout — a TUI writes its own result file). By default an overlay opens on its + target without switching the active session (full and --size-percent + floating alike); + --follow switches the user to + the target. + session overlay resize changes an + open overlay in place — + --size-percent makes it floating, + --full switches it back — without + restarting the program. A + (overlay) tag in + agtermctl tree marks a session + whose overlay is open.

- global-hotkey takes exactly one - chord and binds it system-wide: it summons the quick terminal while any - application is frontmost, which no other binding here can do — the rest only fire while agterm has the keyboard. It - must carry a modifier, takes no alternatives and no leader sequence, and a second - global-hotkey line replaces the - first. macOS registers it by physical key position, so it keeps firing on a non-Latin layout. Because the system owns - it rather than agterm, it takes no part in the collision rules below. Note which side wins: the system-wide chord - takes the key even while agterm is in front, so binding one a menu item already uses means that menu shortcut stops - firing. It is unset unless you add the line: agterm ships no default because registering a chord takes it - from every other application on the Mac, and that is not a cost to impose on someone who may never summon - the panel from outside agterm. Spending it on the chord the in-app binding already uses is allowed and is - the way to get one shortcut that works everywhere — - global-hotkey ctrl+` - makes ⌃` summon the panel - from any application, at the price of that chord no longer reaching anything else. + session overlay copy returns the + selection made inside the overlay and + session overlay text its terminal + buffer. These exist because + session copy and + session text address the pane the + overlay covers: text you highlight in an overlay reads there as + no selection, and + session text --pane right hands + back the shell underneath. Reach for them when the read is not chord-driven: an agent polling from outside, or a + script wanting the selection some time after the fact. A chord already receives the selection of the surface it + fired in, the overlay included, as + $AGT_SELECTION, so a custom + command should use that rather than a later socket read. The buffer one returns a TUI's screen as drawn, wrapped + where it wrapped — for a program's real output, still read the file it writes.

- One binding can offer several alternatives, joined by - | inside a single token with no - spaces around it — - map cmd+t|ctrl+a>t toggle_scratch - fires the action from either, and a command - takes alternatives the same way. For a built-in, the first single-chord alternative the menu can carry becomes the menu - shortcut; every - other alternative, on either verb, is delivered by a key monitor and so must carry a modifier on its first chord. This - is also how a built-in gets a leader sequence, because a menu item holds exactly one key equivalent: - map ctrl+a>s toggle_split binds - the sequence and leaves the action with no menu shortcut at all — its shipped ⌘D is gone rather than kept, and free for - another action to claim. A typo in one alternative rejects the whole line, so a mistake can't hide behind a line that - half worked; an alternative that merely breaks a rule or collides with an existing binding drops on its own and its - siblings keep firing. If a line ends up binding nothing at all, the action simply keeps the shortcut it shipped with. + --pane left|right scopes the + overlay to one split pane instead of the whole session: it covers exactly that pane and leaves the sibling pane + visible and interactive, so an agent working in the split can show a diff or a TUI over its own pane without + blanking the one you are reading. The two panes are independent and may both hold an overlay at once, each with its + own background color. A pane overlay is always full-pane — there is no floating variant, so + --pane cannot combine with + --size-percent and + session overlay resize takes no + --pane — and everything else + matches the session-wide overlay. A non-split session accepts + --pane left, since its shell + reports AGTERM_PANE=left, so a + script can pass --pane "$AGTERM_PANE" + without checking whether the session is split. A pane that is not on screen is refused with + pane not visible; hiding the + split after opening is fine, the program keeps running and reappears when the split comes back. + close, + result, + copy and + text take the same + --pane, ⌘W dismisses the + focused pane's overlay before it would close the session (one on the other pane is not in front of you, so ⌘W keeps + its ordinary meaning), and + agtermctl tree --json reports the + covered panes as paneOverlays. +

+ +

+ Message panel (HUD) +

+
+ # post a message over a session; the session keeps focus and stays typable
agtermctl + session hud "gathering options…" --spinner --detail + "scanning branches" --target + "$AGTERM_SESSION_ID"

# repaint in place, no re-spawn and no blink
agtermctl session hud update "ready" --detail + "pick a branch" --target + "$AGTERM_SESSION_ID"

# anchor it and set its width by hand instead of measuring the message
agtermctl session hud "deploying" --position top-right --size-percent + 30

# color both halves of the panel
agtermctl session hud "deploying" --text-color + "#7ec07e" --background-color + "#202020"

agtermctl session hud close --target "$AGTERM_SESSION_ID" +
+

+ A HUD carries a message rather than a program, and it is passive: the session keeps first responder and stays + typable under the panel, the terminal behind it is neither dimmed nor click-blocked, and nothing waits for an + answer. It is for the seconds a script or an agent needs before it can show anything, so the session says what is + happening rather than sitting silent. + open is the default + subcommand, so session hud "…" + posts one. + --position + anchors it to any of the nine positions + session background takes — + top-left, + top-center, + top-right, + center-left, + center, + center-right, + bottom-left, + bottom-center, + bottom-right — defaulting to + center, with every anchor off + center holding a margin off that pane edge on its own; a corner is what keeps a panel out of the text you are + reading. The bare + top and + bottom this took before are + still accepted for the middle column. The panel comes from the message on both axes — width from the longest line, height + from the number of them, so a title and a subtitle give a wide, short panel rather than a square one. + --size-percent overrides the + width only, bounded to at most 80% of the pane, so a message never covers the session it is about. + --text-color sets the text's + own color and + --background-color the panel's + backing, so the two halves are independent. + An update replaces the whole message, so + --detail, + --spinner and + --text-color have to be repeated + to survive it. The spinner takes a style — + --spinner-style bar|braille|circle|blocks|dot|none, + which turns it on by itself, with + dot blinking rather than + animating — and an update may switch style in place; + --background-color is the + exception, read once when the panel is created and kept — in the panel and in the read-back — across every + update, which is why an update does not take it at all.

- Chords are written in Latin and keep working on a non-Latin keyboard layout. A layout that cannot type ASCII — - Russian, Greek, Hebrew, Arabic, Thai — resolves every chord by the physical key position, so - cmd+o still fires on the key - marked O even though it types щ. - A layout that can type ASCII binds what it types, so an alternative Latin layout keeps its own letter positions: on - Dvorak, cmd+o follows the O you - actually type. + It shares the overlay slot, so a second + hud replaces the first, + session overlay open replaces a + HUD, and ⌘W or + session overlay close takes one + down; a HUD over a running program is refused instead, because a message is replaceable and a program is + not. + session overlay result over a + HUD errors, and + session overlay resize accepts a + percent but refuses --full, + which would cover the session the message is about. + agtermctl tree --json reports + the panel as the session node's + hud object, with + overlay reading false beside it; + there is no event, so a script that needs the state polls the tree.

- Bindable built-in actions + Windows

- new_window   rename_window   delete_window
new_workspace   rename_workspace   delete_workspace
new_session   open_directory   rename_session   duplicate_session
close_session   reopen_recent   undo_close   clear_status
increase_font_size   decrease_font_size   reset_font_size
toggle_split   toggle_horizontal_split   toggle_scratch   toggle_search
toggle_sidebar   toggle_flag   toggle_flagged_view
focus_left_pane   focus_right_pane   focus_workspace   toggle_workspace_filter
toggle_workspace_collapse
previous_session   next_session   first_session   last_session
previous_workspace   next_workspace
previous_attention_session   next_attention_session
quick_terminal   session_palette   command_palette
custom_command_palette   show_attention
select_theme   toggle_fullscreen   toggle_terminal_zoom
dashboard + agtermctl window list     # id name [open] [active]
w=$(agtermctl window new work)
agtermctl window new proj-b + --minimized  # create one, parked in the Dock
agtermctl window select + "$w"  # raise it (opening if closed)
agtermctl window rename "$w" personal +
agtermctl window minimize "$w" on +  # park it in the Dock (off restores)
agtermctl window close + "$w"   # close its window (bundle kept)
agtermctl window delete "$w" +  # delete (last window can't be deleted)

# --window targets a specific window's tree on session/workspace/tree/font
agtermctl tree --window "$w"
agtermctl session new --window + "$w" --cwd ~/src/agterm
-

- toggle_fullscreen is the one - action with no menu item of its own. macOS adds an "Enter Full Screen" item to the View menu whenever that menu is - drawn, and nothing suppresses it, so an item of agterm's own would sit beside it as a duplicate. The binding (⌃⌘F by - default) is handled directly instead, which is why the menu entry advertises the system's Globe+F rather than your - chord. Both work, and rebinding - toggle_fullscreen changes the - chord as usual — it just won't show up next to that menu item. -

- Command tokens + Keymap, config & notify

- {AGT_SESSION_ID}   {AGT_SESSION_NAME}   {AGT_SESSION_PWD}
{AGT_WORKSPACE_ID}   {AGT_WORKSPACE_NAME}
{AGT_WINDOW_ID}   {AGT_WINDOW_NAME}
{AGT_PANE}   {AGT_SELECTION}   {AGT_SOCKET} -
-

- Tokens expand at fire time (also exported as - $AGT_* env vars on the spawned - process). A token is substituted raw into the shell line, so for content you - don't control — - {AGT_SELECTION}, and also - {AGT_SESSION_NAME}/{AGT_SESSION_PWD} - (a remote host can set these via OSC) — prefer the matching quoted env var, e.g. - "$AGT_SELECTION". -

-
- Detached, no TTY — a - custom command runs as a detached - /bin/sh -c with no controlling terminal, so - it suits fire-and-forget launches (GUI apps, scripts) — not interactive TUIs. Run a TUI like - lazygit in an overlay - (agtermctl session overlay open) or a scratch - terminal, which have a real TTY. A non-zero exit posts a notification banner. + agtermctl keymap reload +  # re-read keymap.conf, returns the diagnostic count
agtermctl config + reload  # re-read the ghostty config, returns the diagnostic count
agtermctl notify --title Build --body + "tests passed"
+
- GUI PATH — a custom - command resolves its binaries against the app's GUI - PATH: the launchd default plus the bundled - agtermctl, - /usr/local/bin and - /opt/homebrew/bin. A bare - agtermctl or Homebrew binary works; - anything else your shell profile adds does not, and fails with exit 127. Give it an absolute path - or wrap the line in - zsh -lc '…' — - zsh -ilc '…' when that - PATH comes from - ~/.zshrc, which - -lc does not read. The program an - overlay or scratch terminal runs gets the app's own unwidened - PATH and always needs one of those. + In-session env — a shell + inside agterm gets AGTERM_ENABLED, + AGTERM_WINDOW_ID, + AGTERM_WORKSPACE_ID, + AGTERM_SESSION_ID, + AGTERM_SOCKET (the live socket path), and + AGTERM_PANE (which pane the shell runs in — + left (main), + right (split), or + scratch; unset in an overlay), and + AGTERM_PANE_ID (a stable per-surface token the + agent-status hook forwards as session status --pane-id, + so a promoted-then-re-split pane still tags correctly), so a + script can drive its own window without hard-coding ids.
-

- Open the file with File ▸ Edit Keymap… (or the ⌃⇧P palette): it opens in a - 95% overlay running $VISUAL/$EDITOR + The cookbook collects + complete agtermctl + workflows, each in its own directory with a README and, where it needs one, its scripts: switching the sidebar to a + single project, closing a project's workspaces and bringing them back later, picking a path with + fzf and typing it into the + shell, and giving each tab its own Claude Code or Codex conversation across a restart. They are written to be copied + into your own setup and edited, not only read; + cookbook/CONTRIBUTING.md - (falling back to vi) and - reloads on quit. Apply edits made elsewhere with - File ▸ Reload Keymap or - agtermctl keymap reload. A - malformed line never discards the rest — it surfaces in the diagnostics list in Settings ▸ Key Mapping while the good - lines still apply. + has the rules for adding one.

-

- To check what is actually bound, - agtermctl keymap list prints every - built-in with the binds it resolved to (the menu shortcut first, then any alternatives, joined with - |), the custom commands, - each diagnostic in full, and the key equivalents the menu - bar is really carrying. If a binding will not fire, compare the last two: an action whose chord no menu item holds is - usually a menu problem, not a keymap one. Only the menu shortcut can appear there — an alternative never does, and - neither do - undo_close (⌘Z) and - toggle_fullscreen (⌃⌘F), which a - key monitor delivers rather than a menu item. +

+ Recipes come from other people as well as the maintainer. Every one is reviewed before it is accepted, but they are + shell scripts you run on your own machine against your own sessions, and several close sessions or delete + workspaces, so read a recipe before you run it.

-

- v1 limitations -

-
From 0dc327436e1e88643a403987e22b135f577b7dda Mon Sep 17 00:00:00 2001 From: Umputun Date: Thu, 20 Aug 2026 15:30:56 -0500 Subject: [PATCH 2/3] docs: teach the extension model with a four-step lesson A new user asked whether agterm has a file browser. It does, as one keymap line, and nothing in the docs put that within reach: overlays were explained eleven hundred lines from the `command` directive, and the cookbook's recipes nearly all ship a script, so there was no rung between the five nouns and a full workflow. Add an "Extend agterm" section between Install and the concepts tour. Four steps, each naming the building block it adds: session context, the overlay, the control API, then asking an agent that has the bundled skill. That last step reframes the skill from driving agterm during automation to customizing it, which no public copy said. The paste lines pin --target, --workspace and --window: a custom command is spawned detached, so `active` resolves on arrival and can land in whatever the user switched to. Overlay programs are wrapped in `zsh -lc`, the only way a terminal-spawned program reaches a Homebrew binary (#393 widened the runner's PATH, not the terminal's). Name yazi in the floating-overlay captions in README and index.html, where the question actually forms. Two findings kept out of scope go to docs/backlog: the in-app menu wording, and the seeded Lazygit example's missing --target. --- README.md | 4 +- ...ymap-menu-hides-the-extension-mechanism.md | 24 ++ .../seeded-lazygit-example-omits-target.md | 29 +++ site/docs.html | 241 ++++++++++++++++++ site/index.html | 7 +- 5 files changed, 302 insertions(+), 3 deletions(-) create mode 100644 docs/backlog/edit-keymap-menu-hides-the-extension-mechanism.md create mode 100644 docs/backlog/seeded-lazygit-example-omits-target.md diff --git a/README.md b/README.md index b9161b3b..53f99bdc 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ What it does: - **Agent skill.** An installable skill (Help ▸ Install Agent Skill…) teaches Claude Code or Codex the control model and the `agtermctl` commands, so an agent running inside agterm can build its own layout, run overlays, manage windows, and show images inline without you explaining the API. - **Agent status.** A coding agent reports its state (active, blocked, or completed) onto its session's row, so you can see which of many running agents needs you. Status hooks for Claude Code, Codex, Pi, OpenCode, and other agents install from Help ▸ Install Agent Status Hooks…. +A lot of "does it have X?" questions have the same answer: bind X yourself. A `command` line in `keymap.conf` turns any shell line into a key chord, and an overlay gives an interactive program a real terminal over the session, so a file manager, a git UI, or a database browser is one line away. Bigger workflows become scripts, which is what the [cookbook](cookbook/) collects. [Extend agterm](https://agterm.com/docs#extend) walks through both, and an agent with the bundled skill installed will write the line for you. + For the real terminal work, rendering, VT parsing, and shell I/O, `agterm` embeds [Ghostty](https://ghostty.org)'s engine (libghostty); everything above is `agterm`'s own. ![agterm](docs/screenshots/main.png) @@ -33,7 +35,7 @@ An agent's interactive prompt mid-session, with attention glyphs on the sessions ![Agent prompt](docs/screenshots/agent-prompt.png) -A file manager in a floating overlay over the active session: +The yazi file manager in a floating overlay over the active session, from one `command` line in `keymap.conf`: ![Floating overlay](docs/screenshots/floating-overlay.png) diff --git a/docs/backlog/edit-keymap-menu-hides-the-extension-mechanism.md b/docs/backlog/edit-keymap-menu-hides-the-extension-mechanism.md new file mode 100644 index 00000000..704f39d7 --- /dev/null +++ b/docs/backlog/edit-keymap-menu-hides-the-extension-mechanism.md @@ -0,0 +1,24 @@ +--- +worth: later +where: agterm/agtermApp+Menus.swift +added: 2026-08-20 +--- +# File ▸ Edit Keymap… does not read as "add features" + +The seeded `keymap.conf` is the best explanation of custom commands anywhere in the project. It documents +the `command` directive, the detached-no-TTY limit, the PATH rule, the context tokens, and ships the +`agtermctl session overlay open 'zsh -lc lazygit'` pattern (`agtermCore/Sources/agtermCore/ConfigPaths.swift:39-116`). +It is better than the public docs were before the `#extend` lesson, and a user reaches it only by opening +a menu item named after key configuration. + +Both menu entries name the mechanism rather than what it is for: `File ▸ Edit Keymap…` and +`Navigate ▸ Custom Commands`. A user looking for a file browser has no reason to open either. The docs +side of this is fixed (the lesson, the nav rename, the screenshot captions); the in-app affordance is not. + +Worth considering, none of it obviously right: wording that advertises extension rather than +configuration, a Help entry that points at the lesson, or seeding the palette with one commented example +so `Custom Commands` is not empty on a fresh install. All of it is AppKit/menu work whose cost should be +priced on its own, which is why it was kept out of the docs change. + +Surfaced while brainstorming the onboarding gap after a new user asked whether agterm has a file browser +and the answer turned out to be one keymap line. diff --git a/docs/backlog/seeded-lazygit-example-omits-target.md b/docs/backlog/seeded-lazygit-example-omits-target.md new file mode 100644 index 00000000..286e563e --- /dev/null +++ b/docs/backlog/seeded-lazygit-example-omits-target.md @@ -0,0 +1,29 @@ +--- +worth: later +where: agtermCore/Sources/agtermCore/ConfigPaths.swift:77 +added: 2026-08-20 +--- +# the seeded Lazygit example omits --target and can open its overlay elsewhere + +The starter `keymap.conf` ships: + +``` +command "Lazygit" ctrl+a>g agtermctl session overlay open 'zsh -lc lazygit' --socket "$AGT_SOCKET" +``` + +`TargetOptions.target` defaults to `active` (`agtermCore/Sources/agtermctlKit/Commands.swift:69`), and `active` +is resolved when the request reaches the server, not when the chord built its context. The custom command is +spawned detached and fire-and-forget (`agterm/Commands/CustomCommandRunner.swift:336`), so a session or window +switch between the keypress and delivery opens the overlay over whatever is selected by then. The command +reference already tells automated callers to pin it (`site/commands.html:379`), and a custom command has the +stable `$AGT_SESSION_ID` for exactly this. + +The fix is one flag: `--target "$AGT_SESSION_ID"`. The line is otherwise valid — `overlay open` does not +require a size flag, so omitting `--size-percent` correctly gives a full-size overlay. + +Worth doing because the `#extend` lesson now sends new users to **File ▸ Edit Keymap…**, where they read this +example next to the docs' pinned one and get two different answers. Kept out of the docs change (#PR) because +touching `ConfigPaths.swift` pulls in the full Swift gate run for a one-line seed-text improvement, and no +existing user is blocked by it. + +Found by codex while reviewing the paste lines in the `#extend` lesson. diff --git a/site/docs.html b/site/docs.html index 63f41bd9..002cfc77 100644 --- a/site/docs.html +++ b/site/docs.html @@ -183,6 +183,7 @@ > Overview Install + Extend agterm
CONCEPTS + +
+

+ Extend agterm +

+

+ agterm ships a small set of features on purpose. A great many of the tools you might wish it shipped are one line + in keymap.conf, and the line + stays short because three pieces do the work: a + command turns any shell line + into a named action on a key chord, an overlay gives an interactive program its own temporary terminal over the + session, and any CLI you already have fills that slot. agterm never needs to know what the program is. +

+

+ So “does agterm have a file browser?” has no yes-or-no answer. You bind the file browser you like, in + one line. The four steps below each add one building block; put the lines in + keymap.conf via + File ▸ Edit Keymap… and apply them with + File ▸ Reload Keymap. +

+ +

+ 1. Act on the session you are in +

+
+# open the session's working directory in Finder +command "Open in Finder" ctrl+a>o open "$AGT_SESSION_PWD"
+

+ Read the line left to right: + command is the directive, the + quoted text is the name shown in the + Navigate ▸ Custom Commands palette, + ctrl+a>o is the chord that + fires it — Ctrl-A, then O — and everything after that is an ordinary shell line. The chord is + optional: leave it out and the action exists in the palette only. +

+

+ The building block is session context. Every custom command is handed the + session it fired from — its directory, name, id, and current selection — as + $AGT_* environment variables + and as {AGT_*} tokens + substituted into the line. That is what makes one binding work in every session instead of hard-coding a path. +

+ +

+ 2. Run an interactive program over the session +

+
+# the yazi file manager, floating over the session at 95% +command "Yazi" ctrl+a>y agtermctl session overlay open 'zsh -lc yazi' --size-percent 95 --target "$AGT_SESSION_ID" --socket "$AGT_SOCKET"
+

+ The building block is the overlay. A custom command on its own runs detached + with no terminal, which suits launching a GUI app but kills a TUI at once, since it has nothing to draw on. An + overlay is a real terminal, so yazi, + lazygit, + htop and the rest behave + normally in it. It opens in the session's own working directory, and it disappears when the program exits, leaving + the shell underneath untouched. +

+
+ Note zsh -lc rather than + zsh -c. The program an overlay runs inherits + agterm's own PATH, not your shell's, so a Homebrew binary needs a login shell to be found — or an absolute + path. Skip it and the overlay flashes open and vanishes, because the binary was never found. +
+

+ agtermctl is how the line asks + for that terminal: it is agterm's own command-line tool, and opening an overlay is one thing it can do. Step 3 + opens up the rest. +

+

+ --target "$AGT_SESSION_ID" is + worth understanding rather than copying. + agtermctl does not act on + agterm directly; it sends a request over a socket, and agterm resolves it on arrival. Without a target the request + means “whichever session is selected when this lands”, which is normally the one you pressed the chord + in — but not if you switched in between. The token pins it to the session that fired, so the overlay always + opens where you asked for it. +

+ +

+ 3. Reach the rest of the app +

+
+# a second session in this directory, alongside this one, left in the background +command "Session here" ctrl+a>n agtermctl session new --cwd "$AGT_SESSION_PWD" --workspace "$AGT_WORKSPACE_ID" --window "$AGT_WINDOW_ID" --no-select --socket "$AGT_SOCKET"
+

+ The building block is the control API. Overlays are one entry in a much + larger catalog: the same + agtermctl creates and renames + sessions, splits panes, moves windows, opens the native picker, reads a session's text back, and posts + notifications. Every state it sets it also reads back, which is what lets a script build a whole layout rather than + fire a single action. +

+

+ The extra flags are the same idea as + --target above, applied to + every axis this command can drift on. A bare + session new lands in + whichever window is frontmost and whichever workspace is current when the request arrives, and it selects the new + session; the ids pin all three to where the chord was pressed, and + --no-select leaves you where + you were. Keymap lines never wrap, so this one is long: one directive is always one line. +

+

+ Past a certain size the line becomes a script, and that is where the + cookbook starts: pick a + project from anywhere and open it in its own workspace, park a set of workspaces and bring them back later, or have + each tab resume its own agent conversation after a restart. Those are not one-liners, and the recipes do not + pretend otherwise. +

+ +

+ 4. Or have an agent write it for you +

+

+ Install the agent skill from Help ▸ Install Agent Skill…, then + ask the agent running in one of your sessions: +

+
+ add a custom command that opens yazi in a 95% overlay, bound to ctrl+a then y +
+

+ The skill carries the keymap syntax, the full command catalog, the context tokens, and the PATH rule, so the agent + writes a working line instead of guessing at one. This step comes last on purpose: an agent without the skill will + still answer, plausibly and wrongly, and by now you can tell the difference. +

+ +

+ From here, Custom commands & keys has the full keymap syntax and + the bindable actions, the command reference lists every + agtermctl command with its + arguments, and the cookbook + collects worked recipes to copy and edit. +

+

A file manager in a floating overlay over the active session
overlay -  Run a program in a floating panel over the active session. +  The yazi file manager in a floating panel over the active session, from one + command line in + keymap.conf. + Extend agterm
Date: Thu, 20 Aug 2026 15:39:07 -0500 Subject: [PATCH 3/3] docs: make the agent skill the recommended way to extend agterm The lesson had the skill as step 4 titled "Or have an agent write it for you", which reads as a fallback for people who dislike config files. It is the intended route: the skill carries the keymap syntax, the command catalog, the context tokens and the PATH rule, so an agent that has it writes a working line where one without it writes a plausible broken one. State that before step 1 rather than after step 4, and drop the "Or" from the heading. Keep the step itself last: the three steps before it are what let a reader judge the line that comes back and change it later, which is the reason for the order and not a ranking. Same inversion in the README, where it was a trailing clause. --- README.md | 4 +++- site/docs.html | 41 +++++++++++++++++++++++++++++++++-------- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 53f99bdc..1acd9d9d 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,9 @@ What it does: - **Agent skill.** An installable skill (Help ▸ Install Agent Skill…) teaches Claude Code or Codex the control model and the `agtermctl` commands, so an agent running inside agterm can build its own layout, run overlays, manage windows, and show images inline without you explaining the API. - **Agent status.** A coding agent reports its state (active, blocked, or completed) onto its session's row, so you can see which of many running agents needs you. Status hooks for Claude Code, Codex, Pi, OpenCode, and other agents install from Help ▸ Install Agent Status Hooks…. -A lot of "does it have X?" questions have the same answer: bind X yourself. A `command` line in `keymap.conf` turns any shell line into a key chord, and an overlay gives an interactive program a real terminal over the session, so a file manager, a git UI, or a database browser is one line away. Bigger workflows become scripts, which is what the [cookbook](cookbook/) collects. [Extend agterm](https://agterm.com/docs#extend) walks through both, and an agent with the bundled skill installed will write the line for you. +A lot of "does it have X?" questions have the same answer: bind X yourself. A `command` line in `keymap.conf` turns any shell line into a key chord, and an overlay gives an interactive program a real terminal over the session, so a file manager, a git UI, or a database browser is one line away. Bigger workflows become scripts, which is what the [cookbook](cookbook/) collects. + +You are not meant to write those lines by hand. Install the agent skill (Help ▸ Install Agent Skill…) and ask the agent in your session for what you want, and it writes the line with the right syntax, targeting, and PATH handling. [Extend agterm](https://agterm.com/docs#extend) shows that, and teaches enough of the model to read and change what comes back. For the real terminal work, rendering, VT parsing, and shell I/O, `agterm` embeds [Ghostty](https://ghostty.org)'s engine (libghostty); everything above is `agterm`'s own. diff --git a/site/docs.html b/site/docs.html index 002cfc77..dc5a5c3e 100644 --- a/site/docs.html +++ b/site/docs.html @@ -579,9 +579,30 @@

So “does agterm have a file browser?” has no yes-or-no answer. You bind the file browser you like, in - one line. The four steps below each add one building block; put the lines in - keymap.conf via - File ▸ Edit Keymap… and apply them with + one line. The four steps below each add one building block. +

+
+ You are not expected to write these by hand. Install the agent skill from + Help ▸ Install Agent Skill… and ask the agent in your session + for what you want; it knows the syntax, the command catalog, and the traps below. + Step 4 shows exactly that. The three steps before it are not the + long way round: they are what let you read the line the agent writes, change it later, and tell a working answer + from a plausible one. +
+

+ To edit the file yourself, open it with + File ▸ Edit Keymap… and apply your changes with File ▸ Reload Keymap.

@@ -751,19 +772,22 @@

- 4. Or have an agent write it for you + 4. Have the agent write it

- Install the agent skill from Help ▸ Install Agent Skill…, then - ask the agent running in one of your sessions: + This is the normal way to add things, not a shortcut for people who dislike config files. Install the agent skill + from Help ▸ Install Agent Skill…, then ask the agent running + in one of your sessions:

The skill carries the keymap syntax, the full command catalog, the context tokens, and the PATH rule, so the agent - writes a working line instead of guessing at one. This step comes last on purpose: an agent without the skill will - still answer, plausibly and wrongly, and by now you can tell the difference. + writes a working line instead of guessing at one. The skill is what makes the difference: an agent without it will + still answer, plausibly and wrongly. It is shown last rather than first because the three steps above are what let + you tell those two apart, and what let you change the line afterwards without asking again.