Files
tai/grrtui.md
T

14 KiB

grr in the terminal: tai.py --tui

./tai.py --tui is the grr app (the phone-first web client in the sibling grr project) as a fullscreen terminal program: same bots, chats, automations, library, projects, settings, approvals and computers, same words, same colors, from the same server. It is a client. Nothing is stored in tai's vault; the bots, chats and files live in the grr server, so the web app and the terminal show the same state at the same moment.

Standard library only, in tai.py like everything else, built on the retained TUI framework in tui.md. The Telegram-style chat scene on the local vault is still there as ./tai.py --tui-local.

GrrSpecTests in test_tai.py fails when the tables below drift from the code.

Run it

grr-serve                         # the server, http://127.0.0.1:8765 (or: python3 -m grr serve)
./tai.py --tui                    # the client
GRR_URL=http://host:8765 ./tai.py --tui

First start shows the sign-in form (DevPlace username or email and password, or a pasted DevPlace key, or the key of the server's own session when the server says it allows that). The grr session token, not the DevPlace key, is kept in <TAI_HOME>/grr.json (mode 0600) together with the server address and every signed-in account. GRR_URL beats the saved address. --theme and TAI_THEME belong to the other scenes: here the theme is the account's (Appearance). When the server cannot be reached the screen says so, shows the address and offers Try again and Change address. Ctrl+C always quits.

What it takes from the server

One source of truth, nothing copied into tai:

  • GET /api/spec: caps, modes, tiers, shapes, themes, variants, languages, schedule kinds, rule actions, pane tabs, palette. The TUI reads the palette from here (night, day, Black and Blue variants), so a brand change on the server is a brand change here.
  • GET /locales.json: the string table, in the account's language (system follows LC_ALL, LC_MESSAGES, LANG). Every word on screen is one of these keys. Four sort labels the server does not serve (library.sort_*) have a built-in English fallback (GRR_STRING_FALLBACK); any other missing key shows as the key.
  • GET /api/events (server-sent events) on a thread of its own: message.new, message.update, status and the other events listed under Events. It reconnects every GRR_SSE_RETRY seconds and re-reads bots and chats when the link comes back, like the web app does.
  • Every other screen is one or two REST calls. The same error codes the web app words (apierr.*) are worded here.

Layout and responsiveness

Terminals are small phones too. One pane below GRR_SIDE_MIN_COLS columns (the drawer is the home screen, every other screen replaces it, Esc goes back); two panes from there on (the drawer GRR_SIDE_W columns wide, the screen beside it, a welcome mark when nothing is open). Tabs that do not fit fold into one dropdown chip, the mode bar of a general chat folds into a dropdown, tiles reflow into fewer columns, texts wrap. Nothing is wider than the terminal at 30 columns (a test draws every screen at 30, 56, 100 and 160 columns and fails on any overflow).

The side pane of a bot chat (Preview, Files, Deploy) sits beside the chat from GRR_PANE_MIN_COLS columns and over it below that. Cards are rounded with half-block edges and quarter-block corners, so they look rounded in any font. Colors are the grr palette as truecolor values; COLORFGBG decides what system means (night unless it says light).

Screens

Every screen is one of these routes, the same patterns as the web app's.

Route View The screen
/bots GrrBotsView Bots and group chats, the avatar strip, new bot and new group chat
/bot/:id GrrProfileView Profile: name and description, Info (character shape and color, instructions, memories, skills, routines, voice, notifications, auto-approve, share as template), Documents, Computer, Links
/bot/:id/instructions GrrInstructionsView Instruction, persona and rules, each with reset to default
/bot/:id/memories GrrMemoriesView Memories list and add
/memory/:id GrrMemoryView One memory, edit and delete
/bot/:id/skills GrrSkillsView Skills list and add
/skill/:id GrrSkillView One skill, edit and delete
/bot/:id/computer GrrComputerView Take control: the bot computer's live shell
/bot/:id/screen GrrScreenView The graphical screen: start, stop and the address to open it
/chat/:id GrrChatView Bot, group, general and private chats; new makes a general chat on the first send
/automations GrrAutomationsView Routines as cards, run now, pause, delete
/automations/new GrrNewAutomationView Name, schedule or trigger, instruction, bot, tier, notifications
/automation/:id GrrAutomationView One routine: active, instruction, schedule, bot, notifications, last run
/library GrrLibraryView All, Media, Apps, Files; sort, filter, upload; the file viewer
/projects GrrProjectsView Projects, filter and sort, new project
/project/:id GrrProjectView A project's chats, rename, star, instructions, delete
/search GrrSearchView Messages, bots, group chats, files, routines
/settings GrrSettingsView The settings tree
/settings/account GrrAccountView Accounts, switch, add, sign out, delete account
/settings/usage GrrUsageView Weekly usage, days, gateway, model health, counters, change limit
/settings/rules GrrRulesView Auto-review rules
/settings/rule/:id GrrRuleView One rule (new adds), allow or ask first
/settings/appearance GrrAppearanceView System, Day, Night, Black, Blue, size
/settings/language GrrLanguageView Language
/settings/haptics GrrHapticsView The haptics switch (kept in the account; a terminal cannot vibrate)
/settings/widget GrrWidgetView The widget page: switch, open, copy the address
/settings/computer GrrComputersView Each bot computer: disk, update, reset
/settings/connectors GrrConnectorsView Tool groups on and off
/settings/advanced GrrAdvancedView Default tier, replace the key, export, server health
/settings/builder GrrBuilderView Builder state and settings (admins)
/settings/feedback GrrFeedbackView Send feedback
/settings/timezone GrrTimezoneView Time zone list with filter
/template/:id GrrTemplateView A bot template: details, publish, unpublish, copy link
/template/:id/:part GrrTemplatePartView Instructions, memories or skills of a template
/public/:slug GrrPublicTemplateView A shared template and Add to my bots

The chat

Messages are items in a page that follows the newest row while you are at the bottom (End jumps there, a hint shows when something arrived above). Your messages are right-aligned bubbles, a bot's are left, rendered with the markdown renderer of the chat scene (headings, bold, inline and fenced code, lists, quotes). A divider names the time when more than 30 minutes passed, NEW marks what you have not read. Under the newest bot message, and under any message you select, sit copy, good, bad and try again; steps fold open with Enter.

Cards: approvals (Allow once, Deny, Always allow in browser for the browser tool, then the state), questions (lettered options and Skip), files (Enter opens the viewer: text with line numbers, anything else is saved and opened by the desktop), several files, templates (publish, copy link, details), and live process cards. A running command's card follows the server's byte stream into a VT screen (8 rows, 24 expanded) with ^C, Stop, Type (a full terminal in a dialog, Esc leaves) and Expand; a finished one shows the stored transcript.

The composer is a multiline box (Enter sends, Shift+Enter or Alt+Enter adds a line, @name completes bots, PageUp and PageDown scroll the conversation). The button on its right is Stop while the bot works, Send when there is text and the voice mark otherwise. Plus attaches a file by path, inserts a skill or opens Connectors. A general chat has the Ask, Imagine and Build tabs, the tier chip and the private-chat switch; a private chat is deleted when you leave it. Unsent text is kept per chat while the program runs (GRR_DRAFT_KEEP chats).

Keys

Mouse works everywhere: click rows, buttons, tabs, chips and tiles, wheel scrolls.

Key What it does
Esc back, or close the open dialog or menu
Up Down move the selection
Left Right move inside tabs, chips, tiles and card buttons
Enter, Space open the selected row, press the selected button, flip the selected switch
PageUp PageDown scroll the page or the conversation
Home End first or last row, End jumps to the newest message
Tab, Shift+Tab next or previous area
Ctrl+N new chat
Ctrl+F search
Ctrl+G settings
Ctrl+O, F10 the More menu of the screen
Ctrl+P the side pane of a bot chat
Ctrl+] leave the terminal of a bot computer
F1 key help
Ctrl+Q quit

In the terminal of a bot computer every other key goes to the shell. Tab still moves focus (the framework's rule for every terminal widget, see tui.md), so shell tab completion is not available there.

Events

The server sends bot.update, bot.delete, chat.update and chat.delete (the lists are read again), status (who is working on what, drives the working row, the busy dots and the stop button), toast (a banner with the bot and a line, click opens the chat, not shown for the chat you are looking at) and these, handed to the open screen: message.new, message.update, routine.update, automation.update, library.update, usage.update, builder.update, files.changed, port.opened, port.closed, preview.ready, preview.failed, chat.draft, connector.update.

Character shapes

A bot's mark is two rows of seven cells (five when small, four rows as a hero) with two eyes, one template per shape: blob, cloud, tilt, plus, flower, leaf, pebble, oval, play, gem, cog, hex, in the bot's own color.

Caps

Every limit is a constant in tai.py.

Constant Value Meaning
GRR_HTTP_TIMEOUT 30 seconds for one REST call
GRR_SSE_TIMEOUT 60 seconds of silence before the event stream reconnects (the server beats every 15)
GRR_SSE_RETRY 2.0 seconds between reconnects
GRR_SSE_LINE_MAX 1048576 bytes of one event line; longer ends the connection, it is counted and never parsed
GRR_BODY_MAX 33554432 bytes of one answer or download
GRR_SIDE_W 34 columns of the drawer in two-pane mode
GRR_SIDE_MIN_COLS 100 columns from which two panes show
GRR_PANE_MIN_COLS 150 columns from which the side pane sits beside the chat
GRR_PANE_MIN_W 40 narrowest side pane
GRR_PANE_MAX_W 90 widest side pane
GRR_MESSAGE_KEEP 400 messages a chat keeps in memory (older ones load again on demand)
GRR_TOAST_SECS 3.2 seconds a toast stays
GRR_BANNER_SECS 5.2 seconds a bot banner stays
GRR_TOAST_MAX 4 toasts at once
GRR_COMPOSER_MAX_ROWS 6 rows the composer grows to
GRR_VIEWER_MAX_BYTES 2097152 bytes the file viewer reads
GRR_DRAFT_KEEP 64 chats with a kept draft
GRR_LIST_MORE 12 rows Show more adds
GRR_MENU_MAX_H 14 rows of a menu before it scrolls

Measured

One machine (Linux 7.0, Python 3.14), headless harness with the real server on loopback, no terminal in the loop, median of 50 runs of "change, lay out, build the frame bytes":

What Result
boot to first frame (spec, locales, me, bots and chats over REST) 29 ms
repaint of a 400 message chat, every row cold 44 ms
scroll by 3 rows in that chat 1.1 ms
one typed character in the composer 0.12 ms
one new message appended 2.0 ms
the settings page repainted 0.8 ms
peak memory of that process 44 MB

Through the real binary (tests/grr_tui.py, a real grr server on a throwaway home, the real gateway behind it): typing a message in the TUI, the server storing it, a real model answering and the answer reaching the screen took 11 s once; that case is probabilistic by design (a free, rotating backend), the other three cases are deterministic and take 5 to 7 s each.

Gaps, next to what closes them

  • Voice conversation and dictation (the wave and mic buttons): no audio capture in a terminal client. The button says so. Closes in GrrChatView.send_action with tai.py's own listen and the server's /api/voice/* routes.
  • The graphical bot screen (VNC) and camera: a terminal cannot draw either. GrrScreenView starts and stops the screen and opens it in the desktop browser; the bot's shell is GrrComputerView.
  • Images (link thumbnails, the image viewer, library thumbnails) are not drawn: they show as named files and open in the desktop viewer. Closes in GrrFileViewer and GrrMessageItem with a pixel protocol (kitty or sixel).
  • Preview frame: a terminal cannot frame a page, GrrPanePreview shows the address and opens it in the browser.
  • File highlighting in the Files tab: line numbers only, no syntax colors (the web app uses the rule sets in the server's spec). Closes in GrrPaneFiles.view_file.
  • Dates and relative times use the system locale's day and month names and English words for "ago".
  • Settings that only a screen has: the size slider is kept in the account but does not change the terminal; haptics likewise.
  • Server-side chat drafts (chat.draft) are not read; drafts are kept per run in memory.
  • Wide characters count as one column (see tui.md), so CJK names in the language list can misalign by a cell.
  • Not measured: frame cost of the grr screens on real terminals, memory over a long session, behavior on terminals without truecolor (colors are sent as 24-bit values).