From 01b735b7d34bd7221f4cc6ceb92cf3d6169833de Mon Sep 17 00:00:00 2001 From: Iurii Plugatarov Date: Sun, 8 Nov 2026 18:24:04 +0200 Subject: [PATCH 1/2] HERD-38 README: short and current, user guide split out MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit README.md goes from 389 lines (10.8k words) to 119 (1k): what the app is, the gallery with short captions, Install, Set up a machine (by code, by hand, Windows), a one-line-per-feature list linking to the guide, Building and checks, Layout, Contributing, License. - docs/GUIDE.md: the user guide, by what you do, in plain short sentences. Fixes stale paths (Health, Usage and Logins are tiles on the machine screen, not ⋯ items) and adds Autopilot and the Assistant's chat tools. - docs/ARCHITECTURE.md: how the app reaches a machine (developer notes moved out of the README). - docs/RELEASING.md: release signing and building updates on a machine. - tools/ui-preview/README.md: the screenshot recipe, as a script. - herdr-push README: the Windows link and the Add machine path; CONTRIBUTING links to the new anchors. Co-Authored-By: Claude Opus 5.5 --- CONTRIBUTING.md | 8 +- README.md | 406 +++++++------------------------------ docs/ARCHITECTURE.md | 40 ++++ docs/GUIDE.md | 242 ++++++++++++++++++++++ docs/RELEASING.md | 42 ++++ host/herdr-push/README.md | 4 +- tools/ui-preview/README.md | 15 ++ 7 files changed, 413 insertions(+), 344 deletions(-) create mode 100644 docs/ARCHITECTURE.md create mode 100644 docs/GUIDE.md create mode 100644 docs/RELEASING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9278d469..f6ef18e0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,13 +11,13 @@ You need **JDK 17**. The app also needs the **Android SDK** (platform 36, with ` ./gradlew :app:installDebug # the same, installed on the phone adb sees ``` -A debug build is `com.makefunstuff.herdr.debug`, so it installs next to the release app instead of over it, with its own machines and SSH key. `./gradlew :app:assembleRelease` builds the release APK, signed with the debug key unless you set the signing variables (see [Signing key](README.md#signing-key-once-for-the-repo-owner)). +A debug build is `com.makefunstuff.herdr.debug`, so it installs next to the release app instead of over it, with its own machines and SSH key. `./gradlew :app:assembleRelease` builds the release APK, signed with the debug key unless you set the signing variables (see [`docs/RELEASING.md`](docs/RELEASING.md)). ### The host side -The app talks to [herdr](https://herdr.dev) over SSH, so you need a machine running herdr that your phone can reach by SSH: install herdr there and add the machine in the app ([Setting up a machine](README.md#setting-up-a-machine)). The host tools in `host/` (`herdr-push` for notifications, `herdr-usage` for the usage page, `herdr-logins` for the agent logins and `herdr-app-build` for changing the app from the phone) are copied to a machine by the app when a feature needs them; each folder's README says how to run it by hand. +The app talks to [herdr](https://herdr.dev) over SSH, so you need a machine running herdr that your phone can reach by SSH: install herdr there and add the machine in the app ([Set up a machine](README.md#set-up-a-machine)). The host tools in `host/` (`herdr-push` for notifications, `herdr-usage` for the usage page, `herdr-logins` for the agent logins and `herdr-app-build` for changing the app from the phone) are copied to a machine by the app when a feature needs them; each folder's README says how to run it by hand. -Without a machine of your own, the screen previews (below) show every screen against a fake machine with made-up projects; they are also where the README's screenshots come from. +Without a machine of your own, the screen previews (below) show every screen against a fake machine with made-up projects; they are also where the README's screenshots come from ([how](tools/ui-preview/README.md#the-readmes-screenshots)). ## Tests @@ -39,7 +39,7 @@ The core tests run the host scripts themselves, so they want `python3` (and `ssh - **The look lives in `ui/theme` and `ui/design`** ([`docs/DESIGN.md`](docs/DESIGN.md)). Screens use its colours, type, spacing and components and nothing else; a new one is added there first. - **Android-only calls stay out of shared code** the previews compile; see "When it doesn't compile" in [`tools/ui-preview/README.md`](tools/ui-preview/README.md). - **Host scripts** (`host/`) stay one file that needs nothing installed: `herdr-push`, `herdr-usage`, `herdr-logins` and `herdr-app-build` are Python 3.6+ with the standard library only. Raise a script's `version:` line near its top when you change it, so the app replaces older copies on machines. -- **Words on screen and in docs are plain and short.** A feature users see is described in the README. +- **Words on screen and in docs are plain and short.** A feature users see is described in the [user guide](docs/GUIDE.md). - **The repo is public**: no real hostnames, IPs, user names, emails or keys in code, tests, demo data or screenshots. Use the demo machines (`devbox`, `gpu-box`, `ci-runner`, user `demo`). ## Issues diff --git a/README.md b/README.md index 0e41c880..0e47a82f 100644 --- a/README.md +++ b/README.md @@ -1,389 +1,119 @@ # Herdr for Android -A native Android client for [herdr](https://herdr.dev) hosts: see your agents across machines, answer the blocked ones, and open any pane as a live terminal. Kotlin and Jetpack Compose. It wears the Herdr desktop's `workbench` look (an engineering-puzzle-game console: near-black panels, amber frames, numbered space slots, chip-package icons, dot-matrix glyphs; paper and dark amber in light mode), flat and square, laid out as Shepherdr 3.1's phone web app: a drawer of spaces and a bottom bar of Chat, Tasks and Artifacts ([`docs/DESIGN.md`](docs/DESIGN.md)). +An Android app for people who run coding agents with [herdr](https://herdr.dev): see every agent and task on your machines, answer the ones that wait for you, and open any pane as a terminal. - - - - + + + + - - - - + + + + - - - - + + + +
The Tasks list
Tasks: what needs you on every machine first, answerable on the row
A space's Assistant chat
Assistant: talk to a space; its plans, news lines and Accept cards
An agent's chat
Agent chat: the agent's own transcript, each turn's work folded to one line
A blocked agent asking for approval
Needs you: the question docked over the composer, answered with a thumb
The Tasks list
Tasks: what needs you, first
A space's Assistant chat
Assistant: a space's chat
An agent's chat
Agent chat: its own transcript
A blocked agent asking for approval
Needs you: answer with a tap
A task ready to accept
A task: its chat, files, changes, PR and browser as tabs, Accept on top
A task's pull request
Pull request: checks, approve or request changes from the phone
A task's live browser
Browser: what the task's agent sees in its browser, live
A herdr.doc investigation
Docs: the agents' herdr.doc write-ups, drawn natively
A task ready to accept
A task: ready to accept
A task's pull request
Pull request: checks and review
A task's live browser
Browser: the agent's, live
A herdr.doc investigation
Docs: agents' write-ups
The spaces drawer
Spaces: every machine's spaces in the drawer, Settings at its foot
A machine's spaces
A machine: health, usage and logins, then its spaces and panes
Terminal
Terminal: any pane, live, sized to the phone
The Tasks list in light mode
Light mode: follows the phone
The spaces drawer
Spaces: every machine's
A machine's spaces
A machine: health and panes
Terminal
Terminal: any pane
The Tasks list in light mode
Light mode
-The screenshots are the [screen previews](#screen-previews): the app's own screens rendered against a fake machine with made-up projects. `./gradlew -p tools/ui-preview render -Ponly=agents-v2,chat-tab,chat,chat-blocked,task-v2-ready,task-v2-pr,task-v2-browser,doc,machines,machine,terminal`, then each shot `magick .png -strip -resize 540x -quality 82 .webp` (agents-v2 → tasks, agents-v2-light → tasks-light, chat-tab → assistant, chat → chat, chat-blocked → blocked, task-v2-ready → task, task-v2-pr → pull-request, task-v2-browser → browser, doc → doc, machines → spaces, machine → machine, terminal → terminal), the tall `agents-v2`, `agents-v2-light` and `doc` cut to their first screen first (`-gravity north -crop 824x1784+0+0 +repage` before `-strip`). +The screenshots are the app's own screens, rendered against a fake machine with made-up projects ([how](tools/ui-preview/README.md#the-readmes-screenshots)). -## Getting the app +## Install -On your phone, open the repo's [latest release](https://github.com/makefunstuff/herdr-android/releases/latest) and tap **herdr-android.apk** (the direct link is `https://github.com/makefunstuff/herdr-android/releases/latest/download/herdr-android.apk`). Android asks you once to allow installs from your browser or file manager. +On your phone, open the [latest release](https://github.com/makefunstuff/herdr-android/releases/latest) and tap **herdr-android.apk**. Android asks once to allow installs from your browser. -To update, do the same again: every build is signed with the same key and has a higher version code, so it installs over the app you have and keeps its machines and SSH key. No uninstall needed. +To update, install the new APK the same way. It installs over the old one and keeps your machines and SSH key. -Only the default branch publishes a release (tagged `v0.1.`). Other branches and pull requests build the APK too, as the `herdr-android-` artifact on their Actions run page, without a release. +The app makes its own SSH key. An uninstall or a new phone loses it, so back it up: **Settings → SSH key → Back up**. After a reinstall, **Restore or import** brings it back, or takes an existing OpenSSH key. -## Keeping the SSH key +## Set up a machine -The key stays through updates. An uninstall or a new phone wipes it, so back it up first: **Settings → SSH key → Back up** saves it as an OpenSSH private key protected by a passphrase you choose (`ssh-keygen -y -f herdr-android-key` reads it too). After a reinstall the app offers **Restore or import** before it makes a new key; the same button is in Settings. It also takes an existing OpenSSH (or PEM) private key, with or without a passphrase. +The machine needs herdr (or the Herdr desktop app) and an SSH server the phone can reach, for example over Tailscale. -The app never replaces a key on its own: it only makes one when it has none and you ask for it, and **New key** asks before throwing the old one away. +### By its code -## Signing key (once, for the repo owner) - -Releases must all be signed with one key, or Android refuses the update. Create it once and keep a copy somewhere safe; losing it means everyone has to uninstall (and restore their SSH key backup) once. - -```sh -keytool -genkeypair -keystore herdr-release.jks -alias herdr \ - -keyalg RSA -keysize 4096 -validity 10000 -dname "CN=Herdr for Android" -base64 -w0 herdr-release.jks > herdr-release.jks.b64 -``` - -Then add these secrets under **Settings → Secrets and variables → Actions** (or with `gh secret set NAME`): - -| Secret | Value | -| --- | --- | -| `HERDR_KEYSTORE_BASE64` | contents of `herdr-release.jks.b64` (`gh secret set HERDR_KEYSTORE_BASE64 < herdr-release.jks.b64`) | -| `HERDR_KEYSTORE_PASSWORD` | the keystore password | -| `HERDR_KEY_ALIAS` | `herdr` | -| `HERDR_KEY_PASSWORD` | optional: the key password, only if it differs from the keystore password (keytool uses the same one by default) | - -Without these secrets, and for local builds, the APK is signed with the normal debug key and the default branch skips publishing a release (and keeps the old `apk-*` pre-releases until the first real one). To sign a local build with the real key, set `HERDR_KEYSTORE_FILE`, `HERDR_KEYSTORE_PASSWORD` and `HERDR_KEY_ALIAS` and run `./gradlew :app:assembleRelease`. - -## Setting up a machine - -**By its code**, with nothing to copy or type: - -1. On the machine running herdr, run `herdr-push pair`. A machine the app already set up for notifications has it in `~/.local/share/herdr-push/`; elsewhere (or where that one is older and doesn't know `pair`), fetch it: +1. On the machine, run `herdr-push pair`: ```sh curl -fsSLO https://raw.githubusercontent.com/makefunstuff/herdr-android/HEAD/host/herdr-push/herdr-push python3 herdr-push pair ``` - (On Windows the interpreter is usually `python`, see below.) - It shows a QR code with where the machine is (its Tailscale name and address, its address on the local network), its user, its SSH host key's fingerprint and a one-time code, valid five minutes. -2. At the foot of the spaces drawer, tap **Add machine**, then **Scan the code**. The phone's own code scanner reads it (Google Play services': the app asks for no camera permission). Without one, **Paste the link** takes the link printed under the code. -3. The app finds the machine at the first address that answers with that host key and shows what it found. **Pair and add** logs in with the one-time code, which can do nothing but hand over this phone's public key: it goes into `~/.ssh/authorized_keys` there, the code is spent, and the app pins the host key and opens the machine. - -How it works and what it touches: [`host/herdr-push/README.md`](host/herdr-push/README.md#pairing-a-phone). - -**By hand** (**Add machine → Type the address instead**): - -1. Open **Settings** (at the foot of the spaces drawer) and copy the app's public key. -2. Add it to `~/.ssh/authorized_keys` on the machine running herdr. -3. Tap **Add machine**, then **Type the address instead**, and enter `user@host[:port]`. The app pins the host key on first connect. - -**A Windows machine** works with the Herdr desktop app's herdr-core: its agents and their terminals. Classic herdr, Shepherdr and App change need Linux or macOS. It needs OpenSSH Server running with [Git for Windows](https://gitforwindows.org)' Bash as its shell, once, in a PowerShell opened as administrator: - -```powershell -New-ItemProperty -Path HKLM:\SOFTWARE\OpenSSH -Name DefaultShell -Value 'C:\Program Files\Git\bin\bash.exe' -PropertyType String -Force -``` - -Then pair as above with `python herdr-push pair`. For a user in Administrators, Windows' sshd reads keys from `C:\ProgramData\ssh\administrators_authorized_keys`, so run `pair` in a terminal opened as administrator too (or add the key there by hand). - -For notifications, the PC has to be awake and signed in: the watcher runs in your signed-in session, and herdr-push needs a Python 3 there (the app finds `python3`, `python` or `py -3`, and skips the Microsoft Store's stand-in). See [Notifications while the app is closed](#notifications-while-the-app-is-closed). - -Machines reachable only through another one can use it as a jump host ("Connect through"). For zero-touch or read-only machines, **Allow host scripts** (on by default) can be turned off so the app skips copying its helper scripts (`herdr-usage`, `herdr-logins`, `herdr-push`) there. - -## How it talks to herdr - -The app opens an SSH connection and reaches herdr's socket API through `direct-streamlocal` (falling back to `herdr remote-api-bridge`). herdr answers one request per socket connection, so each call opens a fresh channel; `events.subscribe` keeps one channel open for live updates. Terminals use `herdr terminal session control`. - -## Needs you, first - -The app is laid out as Shepherdr 3.1's phone web app: a drawer of spaces grouped by machine (each machine with a meter: CPU, memory and its tightest plan; a tap opens the machine; Search on top, **Settings** and **Add machine** at its foot) and a bottom bar of **Chat** (the space picked in the drawer: its Assistant, or its agent's chat), **Tasks** and **Artifacts**. - -The **Tasks** tab opens with one **Needs you** list across every machine: agents asking a question, Shepherdr tasks waiting for you (asking, or wanting a secret), and Assistants that ask; then the **Ready** tasks, each with its **Accept**; then the work by state: Working, Queued, To do, Done and Idle. A filter field and chips (All, Needs you, Ready, Working…, each with its count) narrow it. An agent and the task it works on show once, as one row (the app ties them by the task Shepherdr labels its terminal with, or the workspace it opens for the task). Each row carries its answers: - -- a question: **Yes** and **No** (or the menu's two choices), and **Reply**. Like a notification's buttons, Yes and No read the agent's screen again first and press nothing if the question changed; a longer menu opens in the chat. -- a task: its question card's choices, **Give it** for a secret it asks for, or **Accept** for one that is Ready (Shepherdr's Accept card first); a tap opens its chat. - -Agents at work are one line each, with a thin bar. Finished ones wait under **Done** until you open their chat on the phone, swipe them right, press **Mark all seen**, or look at their pane in herdr itself (herdr then says idle; the app reads its `completion_seq` to tell one finish from the next); a finish seen rests with the idle ones. - -Swipe an agent left for **Pin** (it stays on top, under Needs you, in the order pinned; kept on the phone, and gone when the pane is). Its long-press sheet has Pin too, and its space's **Notify**: **All** (its questions and each finish), **Needs me** (only its questions) or **Off** (nothing: no alerts, no Live Update while it works, no in-app alert; its rows show a crossed-out bell). The same choice is a long press on the space in the drawer, as in Shepherdr 3.1. A space that picks nothing follows the switches in Settings (Needs me by default). A question always shows in Needs you, whatever its space's Notify. Mutes and watches from before Notify move onto their spaces the first time the app reads their machine: a muted agent's space becomes Off, a watched one's All. The **+** offers **New task**, the **Assistant** and **Start an agent** (on which machine, when there are several). The Tasks badge counts what waits for an answer; Ready tasks have their Accept on the list instead. The boards are read every 15 seconds while the list is in front. - -## Search - -The search icon on **Tasks**, the spaces drawer and a machine (or **Ctrl+K** with a hardware keyboard, from anywhere) opens one box for the agents, the boards' tasks, the machines, their workspaces and the words in the recent chats. Chips above the results filter them to what **Needs you**, what is **Working** and what is **Done**, each with its count. From a machine it searches only that machine, until its chip is taken off. Every word typed has to be in a result, and a word at the start of a name counts most; with nothing typed it lists the agents, the tasks waiting for you and **Start work…**. - -Words in the chats are looked for on the machines themselves: each running agent's own transcript (Claude Code, codex, pi and omp) is searched where it is, a moment after you stop typing (three letters at the least), and what was said around the words shows with who said it and when; tool calls and their output don't count. Under the results, **New task about "…"** starts work about the words, and **Open a terminal in …** opens a shell in the workspace found (each pane on a machine's screen has the same terminal button). Enter opens the first result, Esc goes back. - -## Start work - -**Start work** asks first what you want, then how and where. It opens from the Tasks tab's **+** (**New task** or **Start an agent**), a space's long-press sheet in the drawer, the search, and the launcher's shortcut. - -- **Board task**: the sentence goes to the chosen space's Assistant (**Send to the Assistant**), picked among the machine's spaces, which makes the task: Shepherdr makes tasks only through a space's Assistant. -- **Agent now**: an agent starts in the folder, in a new tab of the workspace that sits there (else a workspace of its own), named after the sentence's first words, and the sentence is its first message. The agents offered are the ones installed on that machine: those herdr's integrations find, and every other kind herdr can start whose program is on the machine's PATH (gemini, amp…). - -The folders offered are the ones work was started in from the phone, then the workspaces' (a task's worktree counts as its repository); **Browse…** walks the machine's folders, Git repositories marked. The phone never starts an agent in a mode that skips its permission prompts: it asks before it acts, as it does at the desk. - -## Managing from the phone - -A machine's screen (a tap on the machine in the spaces drawer) opens with its places as tiles in one row, each with a live line: **Health**, **Usage** and **Logins**, framed in a state colour only when something there is wrong. Its spaces follow (classic herdr's workspaces and herdr-core's spaces alike), each with its branch and pull request under its name and one row per pane: the agent and its state, or the shell and its folder, a tap opening its chat (or terminal) and the terminal icon its terminal. Tab chips show only for a workspace with several tabs. The bar says when the machine last answered ("Online · answered 4 s ago"). - -Everything that exists in a classic herdr session is editable from the app, not just readable (herdr-core's spaces are the desktop app's: their panes open, nothing more). A workspace or tab header has an actions button (a workspace's one tab is under its actions, **Its tab**), and panes and agents answer to a long press: rename, focus, reorder, move, swap, zoom, clear a pane's scrollback, read what a pane is running, and ask `agent.explain` why an agent is in the state it shows. Closing a workspace, tab or pane asks first. herdr's own refusals ("There's no pane in that direction.") come back as plain sentences. - -The screens follow herdr's `events.subscribe` stream, so a change made anywhere — desktop or phone — appears here. Notifications are the exception: herdr has no list of them, so the app derives them from agents finishing or blocking (see Notifications below). - -## The terminal - -Any pane opens as a live terminal, sized to the phone. - -- **The key bar** over the keyboard has two rows of keys on a phone: Esc, Tab, ^C, ^R (the shell's history), ^W (delete a word), `|`, `~`, **fn** (a page of F1 to F12, Page Up and Down), Ctrl, Alt, Space and the arrows. Ctrl, Alt and Shift are sticky: a tap holds one for the next key (the bar's or the keyboard's: Ctrl then ← is a word back), a second tap keeps it on (underlined) until a third. **Hold Space and drag** to move the cursor: a cell for each 12 dp across, a line for each 24 dp up or down. -- **Your own keys.** The pencil at the end of the bar (or **⋯ → Edit key bar…**) lists the bar's keys, each moved earlier or later or taken off, and offers more by kind: modifiers and pages (Shift, the keyboard key), shortcuts (^D, ^Z, ^L, ^A, ^E, ^U, ^K, ^B, Alt+B, Alt+F, Alt+., Shift+Tab), keys (Enter, Backspace, Delete, Home, End, Page Up and Down…) and characters; a tap puts a key on the bar or takes it off. **A shortcut of your own** builds any other: Ctrl, Alt and Shift with a letter, a sign or a key's name (`F5`, `Home`, `Left`). **Back to the first keys** undoes it all. The bar is the phone's, the same in every terminal, and stays through restarts. -- **Selected text** (a long press, or **⋯ → Select text**, then a drag) gets **Copy**, **Quote in chat** and **Send to the Assistant**. Quoting puts it in a fenced block, with where it came from (`From the terminal (shell on devbox):`), at the end of an agent's message box and opens its chat: the workspace's agent, or the one you pick when there are several. **Send to the Assistant** puts it in the shown space's Assistant chat the same way (on a machine with Shepherdr). Nothing is sent until you send it. -- **Keep screen on** (in the **⋯**): the screen stays on while a terminal is open, for a long build or a log you follow. It is kept for every terminal until turned off. - -## Reading a chat - -An agent's chat is its own transcript (Claude Code's, codex's, pi's or omp's), cut to fit a phone that shows about fifteen lines: - -- **One line per turn's work**, as in the herdr web UI: everything the agent did between your message and its answer (the calls, its words on the way) folds under **Worked for 2m 36s · 1 edit · 2 file reads · 1 command** once the turn is over, and the answer stays out of the fold. Calls that failed are counted apart, in red (**· 1 failed**), since the fold hides their rows. A tap opens or folds it again, and it stays as you left it. The turn at work is open, headed **Working for 3m** (or **Needs you** while it asks); a turn whose last words came before its last call stays open, so nothing it said is hidden. -- **A time on each turn**: `10:05`, or `Oct 16, 09:12` on another day. -- **Short paths**: a file in the repo shows from it, deep ones as `core/…/host/HostState.kt`, and an edit says what it changed (`+12 −2`). A tap on a row still shows the whole call and what it printed. -- **One Working line**: the strip under the bar says what the newest turn is at (*Working · editing HostState.kt*) and the turn's own header how long it has worked (*Working for 3m 12s*), so there is no second "Working" at the bottom. -- **While you were away**: coming back to a chat after five minutes or more, a card says what happened since it was last on screen, in up to three lines from the transcript (what the agent answered, what it was asked and what it is at, the files it changed with `+61 −8`, the commands it ran and whether the tests passed). It goes when you write, or with **Hide**. "Last on screen" is kept per chat on the phone: when you left it, or put the app away while it was open. -- **"2 new messages"**: read further up while the agent writes, and a pill counts what came in; a tap goes to the newest (with nothing new, the round arrow does). - -The times, kinds, failures and edits come from the same reading of the agent's session as the activity timeline (below); a transcript without times still folds its turns, without durations or times. - -## Writing to an agent - -The composer knows the agent it writes to: - -- **Held messages**: what you send while the agent works isn't typed into its screen in the middle of its turn. It waits over the composer, *Held until claude is ready*, with **Send now** (it goes at once, as the agent's next words) and **X** to discard it. Nothing held goes by itself: once the agent is done the line says *claude is ready: review and send*, in amber. Held messages are kept per chat on the phone, through restarts; while the agent asks something they fold to their caption, and Send now waits for the answer. -- **Quick replies**: one-tap chips over an empty box, *continue*, *commit and push*, *run the tests*. Off until **Settings → Quick replies** turns them on; there (or from the pencil at the chips' end) you edit, add or remove them. A chip sent while the agent works is held like anything typed. -- **`/` and `@`**: a `/` at the start of the message lists the agent's own commands (Claude Code's `/compact`, `/review`, `/model`…, codex's, pi's and omp's), with your own and the project's commands and skills read on its machine (`~/.claude/commands`, `.claude/commands` in its folder, the skills folders; codex's saved prompts; pi's prompts and skills). `@` completes a file in the agent's folder: Git's files there (or a walk of the folder when it isn't a checkout), listed once over SSH, matched as you type. A tap puts it in the message. -- **Talk to type**: a tap on the mic beside Send listens, and **Done** (✓, in Send's place) puts what you said into the box where the cursor was, to edit and send; the **X** drops it. While it listens the box shows how loud you are and, with the phone's recogniser, the words as they come. Where the chat's machine runs Shepherdr 3 with whisper.cpp (`shepherdr whisper`), the phone records (AAC, `audio/mp4`) and the machine writes it down (`POST /api/dictate`, as 3.1's web app does; asked once every 10 minutes per machine with an empty recording); elsewhere Android's own speech recogniser does, on the phone where it can (on-device on Android 12 and later). It stops by itself after 5 minutes. Failures are logged under the tag `HerdrDictate`. -- **Voice messages**: holding the mic records what you say and sends the recording itself to the agent; the phone turns nothing into text. Hold it while you speak and let go to send (slide left to cancel); TalkBack's **Record a voice message** action on the mic starts one hands-free, sent with Send; the box shows how loud you are and the time, and the draft you typed stays as it was. The audio is AAC in an `.m4a` file (about 360 KB a minute, at most 10 minutes; under a second is dropped as too short). For a herdr agent it is copied over SSH to `~/.cache/herdr-android/uploads//voice-message-0m07s.m4a` on its machine and named to the agent as an `@path` mention, the way an attached file goes (held like typed words while the agent works); on Shepherdr it is kept by its service (`/api/upload`) and goes to the space's Assistant as a file sent in its chat, its path after the words. The agent's own program gets the file: Claude Code, Codex and pi can't listen to audio today, so the agent sees a voice message's file and its length but not what you said, unless it has its own way to transcribe it on the machine. In the chat it is a voice bubble with play, its length, and how far along while it plays; one that couldn't be copied or sent stays on the phone with why, and goes again when the chat opens, when the machine comes back, or on **Retry** (or **Discard**). The microphone is asked for the first time you touch the mic, with a line saying why; refused, the composer says so and offers **Allow** (or the phone's settings, once Android won't ask again). Recording and failures are logged under the tag `HerdrMic`. The Assistant on Shepherdr has the same mic. -- **The model pill**: beside **+**, *Opus 5.5 · 62%* is the model the agent answers with and how full its context is, with a ring (amber past 80%). It comes from herdr's own fields for the agent (an integration that reports `model` and `context` in its `tokens`) or else the transcript: the model and the tokens its last request filled, of the window Codex states or Claude's usual 200k (1M once a request went past that). A tap shows the details (the tokens, the window, the reasoning level, the model's id, what herdr reports about the agent) and **Usage and limits**, the machine's provider page, with what the agent's provider has left. - -## Answering a blocked agent - -In an agent's chat, the question a blocked agent asks is docked over the composer, so it never scrolls away with the transcript, and it is made for one thumb: - -- **The choices are buttons**: **Yes** first and filled; a permanent allow ("Yes, and don't ask again for npm run commands") as **Always for npm run**, which asks for the fingerprint first (below); **No, tell it…** opens the reply, and the no is pressed only when your words go, which then become the agent's next prompt (X takes it back and the question stays). -- **The agent's footer is buttons too**: "Esc to cancel · Tab to amend" becomes **Amend** (presses Tab, then what you type is added to your answer) and **Cancel** (Esc); other keys it names (`ctrl+e to explain`) get their own. -- **A tag says what it would do**: *runs a migration*, *pushes*, *installs packages*, *edits a file*, in red when it can't be taken back (*deletes files*, *force-pushes*, *discards changes*, *deletes data*). -- **Claude's own questions** (its AskUserQuestion form) list each option with its description and a *Recommended* tag; your own words go in the composer. A multiple choice has boxes and **Submit (n)**; a form of several questions shows its steps as chips (✓ answered, the one asked now, Submit). -- Every press reads the agent's screen again first and presses nothing if the question, or the command it is about, changed; so do the notification buttons and the Needs you cards. While you type, the card folds to one line. - -## Fingerprint and app lock - -**Settings → Fingerprint and app lock** asks for the fingerprint (or face, or the phone's PIN) before what can't be undone, each one switchable: **Always allow** on an agent's question, **Approve and merge** (also "Approve anyway", and on a Needs you card), **Stop a process** (and the leftovers, and Gradle daemons, on a machine's health), **Remove a worktree**, and **Replace the SSH key** (a new key, or restoring another). Every one asks until you turn it off, and turning one off asks too. A notification's **Approve and merge** or permanent allow opens the prompt over whatever is on screen before it does anything. - -**App lock** makes opening the app ask as well (it holds the SSH key to every machine), again after it was away for a while (at once, 1, 5 or 15 minutes), keeps its picture out of the recent apps, and makes a locked phone show its notifications only as "Something needs you", without the question, the command or the buttons. A phone with no screen lock has nothing to ask with: the screen says so, the app lock stays off, and the one-tap actions (always allow, merges) ask for a tap on **Confirm** instead. - -## Machine health and cleanup - -A machine's screen has a **Health** tile (the busiest of RAM, CPU and its fullest disk, red when it runs low), and **⋯ → Health and cleanup** opens the whole picture, read over the same SSH connection every 5 seconds while the screen is in front: - -- **RAM, swap, CPU and every disk** (free space per mount), and a chart of RAM and CPU over the **last hour**. The hour comes from herdr-push, which notes a reading every 30 seconds on the machine; without it the chart fills while the screen is open. -- **The biggest processes** by memory or CPU, each with what it is and whose: Android emulators (with their AVD), QEMU VMs, Gradle and Kotlin daemons, dev servers (vite, next, uvicorn, …), node, the coding agents, herdr; and the worktree it runs in or was started from, or the herdr pane and agent it runs under. A tap shows its details and **Stop process** (after a confirmation: SIGTERM, then SIGKILL after 5 seconds, and only while that pid is still the same program). -- **Stop Gradle daemons**: every Gradle and Kotlin daemon of yours; the next build starts one again. -- **A warning** when RAM or a disk runs low: under 10% of RAM available, or a disk with less than a tenth free (at most 20 GB, so a big disk isn't "low" with 50 GB left). It names what uses the most memory, on the screen and as a notification through herdr-push (and the app's own looks while closed), which comes down once there is room again (15% of RAM, one and a half times the disk's mark). It needs you, so it comes through quiet hours; a tap opens the machine's health. + It shows a QR code, valid for five minutes. +2. In the app, open the spaces drawer, tap **Add machine**, then **Scan the code**. Without a scanner, **Paste the link** takes the link printed under the code. +3. Tap **Pair and add**. The phone's key goes into the machine's `~/.ssh/authorized_keys`, and the app opens the machine. -The full readings and process labels are Linux's (`/proc`); on macOS the screen shows memory, load, disks and the processes from `ps`, without whose they are. On Windows the disks are the fixed drives (as `/c`, `/d`), the processes with their CPU and memory come from `Win32_Process` through PowerShell, a process is yours when it runs in one of your logon sessions, and **Stop process** uses `taskkill` (asking first, then `taskkill /F` for one that refuses or still runs after 5 seconds); there is no working folder or herdr pane for a process there. +The code works only with that machine and only once. Details: [`host/herdr-push/README.md`](host/herdr-push/README.md#pairing-a-phone). -## Usage and limits +### By hand -Each machine has its own logins, so each has a **Usage** page (the **Usage** line under the tiles on its screen, or **⋯ → Usage and limits**): one card per AI provider that machine uses. +1. In **Settings**, copy the app's public key. +2. Add it to `~/.ssh/authorized_keys` on the machine. +3. Tap **Add machine → Type the address instead** and enter `user@host[:port]`. -- **Claude Code** and **Codex** (subscriptions): the 5-hour session and the weekly limits (and Claude's per-model weekly ones), how much is used and when each resets, amber from 80% and red when out. -- **API accounts** whose keys are on the machine: DeepSeek's balance, OpenRouter's spend today and this month and its key's limit, Anthropic's costs (with an admin key), OpenCode Go's key and its limits (estimated from what the machine's agents spent: it has no usage API). -- **Local models** (llama.cpp, say, serving Qwen): local, no limit, and whether the server answers. -- On every card: the tasks and agents that used it most this week and a 7-day chart. +A machine you reach only through another one can use it as a jump host (**Connect through**). -**At a glance**, each machine's meter in the spaces drawer has a bar for its tightest plan, amber near a limit and red past it; the status widget carries the tightest limit across the machines ("Claude 82% · resets 17:00"). **Warnings** come as notifications at 80% and 95% and when a limit runs out; **Limits** in the notification settings switches them off, and quiet hours hold them. +### Windows -It is all read on the machine, over SSH, by [herdr-usage](host/herdr-usage/README.md) (Python 3, copied there by the app): Claude Code's and Codex's own usage APIs with their own logins, the providers' balance and spend APIs with the keys already there, and the agents' session records for what each task and agent used. No key comes to the phone. Readings are cached on the machine for a few minutes and on the phone until the next one, so the page opens at once; while the app is in front, each connected machine is read again every 10 minutes, and while it is closed its own looks (and herdr-push's warnings) keep the limits current for the widget. A limit whose reset time has passed since it was read counts as fresh everywhere, and an agent whose provider's key or login failed says so in the agent choices. Which source each provider's numbers come from, and why: [`host/herdr-usage/README.md`](host/herdr-usage/README.md). +A Windows PC works with the Herdr desktop app's herdr-core: its agents, their chats and terminals, notifications and health. Shepherdr, simple-herdr-projects and changing the app need Linux or macOS. -### Agent logins +Install OpenSSH Server, then make [Git for Windows](https://gitforwindows.org)' Bash its shell, in a PowerShell opened as administrator: -Each machine also has an **Agent logins** line under Usage (or **⋯ → Agent logins**): which agent CLIs are signed in there (Claude Code, Codex, pi, opencode, Gemini CLI), how, and when each login runs out, amber when one runs out soon or a key sits in a shell profile, red once one ran out. From there you can sign in again (Codex's device code shows on the phone; for Claude you paste the code its page shows back into the app), paste a key (Codex's, or one pi provider's) or sign out (opencode's logins one by one; its sign-in asks with menus, so the app shows how to run it there), after the fingerprint. Each is that CLI's own command, run on the machine over the app's SSH session by [herdr-logins](host/herdr-logins/README.md); a key or code goes in as the command's input, never on a command line, in shell history or on the phone, and a key pi takes from a command (a keyring) is never replaced. A key exported from `~/.bashrc` (or another profile) gets a warning with the steps to move it; the app edits no profile. - -## Notifications while the app is closed - -**Settings → Notifications → While the app is closed** makes your machines tell the phone when an agent needs you or finishes, like Puggie does. Each notification has its buttons: **Approve** or **Deny** a blocked agent's question, **Reply** to that agent. A tap opens the agent's chat (a Shepherdr task's agent opens its task). The buttons connect to the machine over SSH while the app stays closed; Approve and Deny read the agent's screen again first and press nothing if the question changed. - -Two ways in, both on every machine the app knows: - -- **Pushes**, within seconds. Each machine runs [herdr-push](host/herdr-push/README.md), a small Python 3 watcher that the app sets up over SSH as the machine connects. It sends to the phone's [UnifiedPush](https://unifiedpush.org) address, which a push app such as [ntfy](https://ntfy.sh) gives (no library, no Google services), sealed with a key only that machine and the phone know. How to check it, run it by hand or use your own push server: [`host/herdr-push/README.md`](host/herdr-push/README.md). -- **The app's own looks**: an exact alarm every 15 minutes (or hourly, or never) connects to each machine and notices what changed, for a machine without Python 3 or a push that got lost. - -Each state is shown once, however it arrives, and comes down again when it is over (answered on the desktop, the agent back at work). The switch next to a machine in Settings leaves it out. - -**On a Windows PC** the app sets herdr-push up the same way, with no administrator rights: a `Run` entry under your user starts it when you sign in, and a scheduled task of yours starts it right away in your desktop session. It reads the Herdr desktop app's herdr-core (where one runs), RAM, CPU, drives and the biggest processes, and tells the phone how long ago you last used the keyboard or mouse and whether the session is locked. The PC has to be awake and signed in: with nobody signed in, herdr-push's install says so (and its `status` shows `interactive: false`), and it starts with the next sign-in. The phone needs ntfy (or another UnifiedPush app), as for any machine. Classic herdr, Shepherdr and App change are not there. - -### What reaches the phone - -**Settings → Notifications → What reaches the phone** has a switch for each kind: **Needs you** (an agent asks, a machine runs low), **Finished** (an agent ended its turn), **Went wrong** (a review or a check failed) and **Limits** (an AI provider at 80% or 95% of a limit or out of it; see Usage and limits). Finished is off unless you turn it on: a ping for every finished turn teaches you to ignore the ones that matter. What is switched off isn't shown, and turning it on later starts from then. - -- **Hold while I'm at the machine** (on by default): while you typed in herdr in the last two minutes, or a machine's screen is unlocked, notifications arrive silently, without a sound or a pop-up. herdr-push tells, with every push, how long ago someone typed in a herdr window there (the last input of its terminal, as `w` reads it), and whether a desktop session there is unlocked (logind's lock, or a screen locker running; on macOS the console's lock). Anything that touches `~/.local/state/herdr-push/present` counts as typing too, a shell prompt or an editor hook, say. Any machine counts: you are at your desk, whichever machine pings. -- **Spaces**: each space's own **Notify** (All / Needs me / Off), picked in the drawer or an agent's long-press sheet, overrides Needs you and Finished for its agents. The screen lists the spaces that picked their own, each with **Reset**. -- **In the app**: when another agent starts asking (or a Shepherdr task starts waiting) while you are in a chat or anywhere but the Tasks list, one black line drops from the top, "api-server · needs you: run db:migrate?", with the agent's mark. A tap opens it; it leaves after five seconds; it never shows for the chat or the task open in front. Not for a space set to Notify Off. - -### Live Updates - -With **Live Updates** on (Settings → Notifications, on by default), what is at work stays in the notifications while it runs: each working agent, with a **Stop** button. On Android 16 (a Pixel 10, say) they are Live Updates: promoted to the top of the shade and the lock screen, with a chip in the status bar. - -- **An agent** shows what it does and how long it has worked (the chip is that time). Stop presses Esc, as the chat's stop button does. - -They come from herdr-push, which also sends what runs to a phone that asked for it: at once when an agent starts or stops, its name and title at most every 90 seconds, and again every 30 minutes while anything runs (a push server counts pushes; ntfy.sh allows a few hundred a day). While the app is open it follows its machines' agents itself too, and its own looks while closed count as well. One that nothing confirmed for 75 minutes comes down by itself, as its machine may be gone. On Android 15 and older they are ordinary ongoing notifications with a progress bar. If Live Updates are turned off for the app in Android's settings, Settings says so and has a button to turn them on. - -### Quiet hours - -**Settings → Notifications → Quiet hours** holds the notifications that can wait, like Puggie's quiet hours: while the phone is in Do Not Disturb or a mode that turns it on (Bedtime, Driving) or in car mode, or on a schedule you set (from, to, and the days a quiet stretch starts on; the same time twice makes those days quiet all day). An agent that finished waits, and Live Updates come down. What needs you still comes through: an agent that asks, something that went wrong, a test. When quiet hours end, what was held shows as one summary notification (or as itself, when it is only one), and the Live Updates come back; one that was over by then (the agent back at work) is left out. Android tells a closed app nothing when Do Not Disturb ends, so the app looks every quarter of an hour while the phone is quiet; a schedule's end has its own alarm. Whether what comes through makes a sound is still Android's to decide: allow Herdr in the Bedtime or Driving mode to hear it. - -## The brief - -The **Brief** (**Settings → Brief → Open the brief**, or its notification) tells what the agents and Shepherdr's tasks did since you last looked, on every machine: what merged, what waits for you now (tasks to accept or answer, agents that ask), your own agents that finished, and what is still at work. Each line opens its task on the board or its agent's chat. "Since you last looked" is since the app was last on screen, or a brief was read aloud to its end; **Last 24 hours** is there too. - -- **At the times you choose.** **When the brief comes** sends it as a notification, at up to four times (say 07:30, and 17:30 for the drive home), every day or on the days you pick. Its lines say what waits and what merged; a tap opens it. -- **Read aloud, for the drive.** **Read aloud** (on the brief, and on its notification) reads it with the phone's voice, over the music: what waits for you, what was done, then what is at work. Titles rather than task keys, at most five of each. The reading goes on with the screen off or the app closed; its notification and the brief's bar have **Next** and **Stop**. **Read it aloud as it comes** can also read the scheduled brief by itself, in the car (car mode: Android Auto or a car dock) or always. - -To put it together the app reaches every machine over SSH, as for notifications while it is closed. Where [herdr-push](host/herdr-push/README.md) runs, its journal tells what the agents did, with times (herdr-push keeps a week of it: each agent that finished or asked); Shepherdr's tasks, and every machine without the journal, are compared with what the app showed you when you last looked, and the brief says so under the machine; a machine it can't reach is named, with why. - -## Code - -The code your agents work on, to read and comment on from the phone, where you check their work: a task's view has 3.1's tabs under its bar, **Chat**, **Files**, **Changes**, once it has one **Pull request**, and **Browser** where Shepherdr has a browser for the task. Files and Changes are the folder the task works in (its worktree, as Shepherdr names it); a file or a change opens full screen. An agent chat's **⋯** (**Browse the code**: the folder it works in) and a workspace's actions open the same screens on their folder. There is no machine-wide code browser: the code is reached from the work. - -- **Pull request.** The task's pull request as GitHub says it now, read with `gh` on the task's machine over SSH (again every 30 s while it shows): open, draft, merged or closed, its number, files and lines changed, its branch, **Open on GitHub** and **Copy link**, its description, its **Checks** (failed first, then running, passed and skipped, each opening on GitHub), its open threads with the code they sit on (resolved ones folded to a line) and the conversation. **Approve**, **Request changes** (with words), **Comment** and **Reply** in a thread each open a sheet and post only on its button, as the machine's gh login; **Resolve** and **Reopen** act at once. What you write goes to `gh api` on its input, never on a command line. On your own pull request only Comment is offered (GitHub takes no approval from its author); without gh, or not signed in, the tab shows the board's own answer and says why. The strip's **PR #** opens this tab. -- **Browser.** The task's agent-browser session, live, through Shepherdr 3.1's browser relay: the page, its address, and who drives (**LIVE**, **YOU'RE DRIVING**, **OFF**). Touch the page to take it over (the agent is told to hold off); **Hand back**, or a quiet minute, gives it back; Back, Reload and the phone's keyboard work on the page. **Open browser** starts one that isn't open. -- **Browse.** A checkout's files are folders; **Go to file** finds a file by a few letters of its name; the search icon searches the code itself, on the machine (`git grep`: words or a regular expression, with or without case). -- **Read.** Numbered lines in the herdr web UI's code colours, long lines wrapped or scrolled sideways, the text size you set (**Aa**), **Go to line** (**#**), Markdown as it reads (and its source a tap away). A big file opens at once: its first 4 MB, said so, drawn a line at a time as you scroll. -- **Changes.** Git's status (branch, ahead and behind, what isn't committed), the diff against the branch it left (`main`, or whatever `origin/HEAD` names), or only what isn't committed yet, new files included; a file's diff with both line numbers, stepping through the files. **History** lists the commits, those not in `main` yet first, and each commit's own diff. -- **Comment.** Tap a line number (or long-press a line), then another to take the lines between, in a file or in a diff, and **Comment**. It goes to the task's agent (into its chat, from a task's view) or an agent at work in that checkout, as a review note: the path, the lines, the quoted code (a diff's with its `+` and `-`) and your words. **Keep for later** adds it to the **Comments** waiting on that machine (the speech-bubble icon, with a count), to change, drop, or send together as one message. - -Everything is read over the machine's SSH connection with `git` and the shell, and nothing is changed there: Git runs with `GIT_OPTIONAL_LOCKS=0`, so not even `git status` touches the index. Only sending a comment does something, and that is a message; a review on the Pull request tab is posted to GitHub by the machine's `gh`. - -## The work board - -The **Tasks** tab shows every machine's tasks in one list (see [Needs you, first](#needs-you-first)); a task opens as its task view, with Chat, Files, Changes, Pull request and Browser tabs. A machine's board is [Shepherdr](#shepherdr) 3.1's where its service runs, or [simple-herdr-projects](#simple-herdr-projects) where `shp` is there; the choice is per machine and detected. - -### What an agent did, step by step - -**Activity timeline** (in an agent chat's **⋯**) follows an agent without reading its whole chat or terminal: every step it took, grouped by turn. - -- **Turns.** Each starts with what started it, and says when, how long it took, which agent and model ran it, and what it did (`12 commands, 7 edits, 3 tests`). A reviewer's turns say their round. Long histories show their newest turn open and the others folded; a tap opens one. -- **Steps.** Messages, commands with their exit status and how long they ran, files read and searched, edits (a tap shows the diff), tests and checks (a build, a linter: passed or failed, with the line that says so, `61 tests completed, 1 failed`), web lookups, other tool calls and the plan. A tap shows a command in full with what it printed. -- **Filters and failures.** Chips keep messages, commands, edits, tests or failures only, with how many there are; **Jump to a failure** (and the alert icon above) goes from one failed step to the next, opening its turn. -- **Live.** While the agent works the timeline grows as it takes each step (**Live** in the bar). - -It is built from what the agents' harnesses record, read over SSH: Claude Code's sessions (`~/.claude/projects`), codex's (`~/.codex/sessions`) and pi's or omp's (`~/.pi/agent/sessions`), each step with its times, exit status and diff. Nothing there is changed. Sessions are pruned on the machine before they are sent (with Python 3 where there is one: the copies of API requests and whole files Claude Code keeps beside an edit go, long outputs are cut), so a 20 MB session comes as a megabyte or two; of a very long one only the newest part is read, and the timeline says so. - -## Photos and screenshots - -Both chats, an agent's and an Assistant's, take pictures: **+** offers **Take a photo** (the phone's camera app), **Photos** (Android's photo picker, several at once), **Latest screenshot** (the newest in the phone's Screenshots folder; it asks once for leave to see your pictures, and on Android 14 leave for only some is enough) and **File** (a picture picked as a file counts too). Up to 10 go with one message. - -- **Made smaller first.** A photo is brought down to 2048 pixels on its long side and turned upright, as JPEG; a screenshot stays PNG so its text keeps sharp edges (unless that makes it over 3 MB). HEIC and WebP become what the agents read. -- **Shown before they go.** They sit in the message box as thumbnails, each with a × to take it out. A tap opens it full screen to **Crop** (drag the frame's corners, edges or middle) or mark it up: **Draw**, **Arrow** and **Highlight**, in six colours, with Undo. **Done** puts the marked-up picture in its place. -- **Copied at once.** Each is copied to the chat's machine as soon as it is added (or once the machine connects; a clock on the thumbnail says it waits) and Send waits until all are there. When the message goes, they are named at its start as `@path` mentions, the way herdr takes a file: herdr passes agents only text and keys, so a picture goes as a file. Claude Code reads an `@path` picture as the image itself; other agents open it with their own image tool. -- **In the chat.** Your turns show the pictures they name as thumbnails (one large, several in a grid) over your words; a tap shows it full size, to pinch and zoom. A picture sent from this phone is shown from the copy it kept; any other one named in a message (sent from the desktop, say) is read back from the machine once (`base64`, at most 12 MB) and kept. -- **Kept like the draft.** The pictures waiting in a message box stay through leaving the chat and the app being closed, until they are sent or taken out. - -## Asking from anywhere - -A space's Assistant is a tap away from outside the app too: - -- **Share to it.** Share a link, some text, an error or a screenshot to **Herdr Assistant** from any app's share sheet. It lands in the Assistant chat's message box: the words at the end of what you had typed, a picture among its pictures (made smaller, to look at, crop or mark up before it goes, as above), any other file copied to that machine and named as `@path`, as **+** does. You read it, add to it, and send it, so the Assistant can make a task of it. With several machines the app asks which one first (the one used last on top); a machine without Shepherdr can't be picked. A share waiting for a machine, and files waiting for a machine that isn't connected, are kept through the app being closed: the app asks again when it next opens; a picture waits in the message box, and the chat says which other file waits and attaches it once the machine connects. -- **Send selected text.** **Send to the Assistant** sits next to Copy in the menu of selected text, and does the same with just the text. -- **The Quick Settings tile.** **Assistant** opens the Chat tab at once, on the space shown last. Under its name it says what needs you ("2 need you · 3 working"). -- **The launcher's shortcuts.** A long press on the app's icon offers **Needs you** (with its count), the **Assistant** used last, **Start work**, and the two agents whose chats were opened last. -- **Three home-screen widgets.** **Ask the Assistant** opens the chat as the tile does. **Herdr** shows the counts across your machines, such as "2 need you · 3 working", and when they were last known; a tap opens the Tasks tab. **Needs you** lists what waits on you, newest first, with its notification's buttons on each row: **Accept** on a finished task, **Approve** and **Deny** on an agent's yes-or-no question (the fingerprint first where Settings ask for it), and **Answer** opening what is answered in words. A row stays until it is answered or over, even when its notification was swiped away; a tap on it opens it, a tap on its heading opens Needs you. - -The counts are what the app knows: what it sees while open (and each Shepherdr board as the app reads it while one of its lists is on screen), its own looks while closed, and the machines' pushes (see Notifications above). An agent that needs you and the task it works for count once, as do a task and its own agent at work. What works and nothing confirmed for 75 minutes stops counting. - -## Floating chats - -An agent's chat can float over other apps as an Android bubble (Android 11 and newer), to steer it while reading something else. **Float over other apps** is in an agent chat's **⋯** menu; an agent's notification has **Float chat** too, when it has room for a third button (a finished agent, one that asks something without a yes or no), and so has the Live Update of an agent at work. The bubble is the chat itself, with its draft, attachments and terminal; **Open in the app** (in its menu) opens it full size. Each chat gets its own bubble, and Android keeps them as conversations: dragging one away closes it, and the launcher's long press on the app lists them. - -Android lets an app's chats bubble only once you allow it. The first time, the app says so and opens Herdr's bubble settings: choose **All conversations can bubble**, and the chat floats as you come back. With **Selected conversations**, the chat shows as a notification instead, whose bubble button floats it (and marks it as one that may). - -## Accessibility - -Everything a long press opens is also offered to TalkBack as actions on the card or row: on a question's card **Answer yes**, **Answer no** (or the menu's two choices), **Reply…**, **Open terminal**, **Why this state?** and **More actions** (the long-press sheet); on an agent's row its triage (seen, pin) and the same; on a board card its board actions. Back is predictive (Android 13 and up shows where it goes while your thumb is still on the edge). Answering an agent, approving and sending give a light haptic. Chats and the terminal each have their own text size, on top of the phone's font size (**Settings → Text size**; pinching the terminal sets its size too). When the terminal asks for a password (`sudo`, `ssh`, a key's passphrase, a card's PIN), a masked field shows over its keys, which types it with Enter and keeps it nowhere. - -## Big screens - -On a window 840 dp wide or wider (a tablet, an open foldable, a large window or desktop windowing), the app shows the spaces drawer as a sidebar that stays on the left, as Shepherdr 3.1's web app does on a desktop, and the app (its tabs and what they open) fills the rest; a tap on a space opens it beside the sidebar from any screen. What a list opens takes the place of what was shown instead of piling up. A narrower window keeps the phone layout, with the drawer. Rotating or resizing across the line keeps what is open as it was: the chat, its draft and scroll, the Tasks list's filter and place. - -## Changing the app from the phone - -The app changes itself the way Herdr desktop's mods change Herdr, with all the work done on a machine of yours: nothing that edits or builds code runs on the phone, and the app runs no plugins or scripts. - -1. **Change this screen**: a long press on any screen's title, or **Change this screen** in its ⋯ menu, takes the app's own picture of the screen and asks what should change. **Send** copies the picture to the machine that builds the app and opens its **herdr-android** space's Assistant, with a first message naming the screen (its title and route), the app's version and your words. -2. **The machine does the work**: the Assistant makes it a task there (worktree, tests, ui-preview shots before and after), and its chat shows the task's news, its shots and its **Accept** card, as any task's. -3. **Update ready**: once a change accepted from the phone is merged, the app asks the Assistant to build the update (`herdr-app-build build`, below), copies the APK over its own SSH link, checks that it is this app, signed with the same key and newer, and offers **Update ready** (in that chat and in Settings › Change the app). **Install** hands it to Android's installer, which always asks you first. The app keeps its machines and SSH key. -4. **Undo**: the same chat's **Undo** asks the Assistant to revert the last change's merge as a new task. Android never installs an older build, so an undo ships as a new, higher one, the same way. +```powershell +New-ItemProperty -Path HKLM:\SOFTWARE\OpenSSH -Name DefaultShell -Value 'C:\Program Files\Git\bin\bash.exe' -PropertyType String -Force +``` -**Settings › Change the app** picks the machine that builds it (until one is picked, the one a change was last sent to) and looks there over SSH: it says what it has and what's missing. +Then pair with `python herdr-push pair`. If your user is an administrator, run it in a terminal opened as administrator. Notifications need the PC awake, you signed in, and Python 3 installed. -The machine needs: +## What you can do -- **A herdr-android checkout** with `origin` on GitHub: the herdr-android space's folder on Shepherdr, else `~/herdr-android`, `~/src/herdr-android` or `~/Work/herdr-android`. -- **The Android SDK** (`ANDROID_HOME`, or `sdk.dir` in the checkout's `local.properties`) and **a JDK 17 or newer** (`JAVA_HOME`, or `java` on the PATH), as a login shell there sets them; and Python 3 and git. -- **Shepherdr 3** with a space named **herdr-android** (or working in that checkout): its Assistant takes the changes. -- **The release key, as secrets** (optional): Android installs an update only when it is signed with the same key as the app. Keep `HERDR_KEYSTORE_FILE` (the keystore's path on that machine), `HERDR_KEYSTORE_PASSWORD` and `HERDR_KEY_ALIAS` in herdr-secret (Settings › Secrets in Herdr, or `herdr-secret set NAME`); the Assistant builds with `herdr-secret run …`, so the values reach only Gradle and never a log, a chat or git. Without them, each update is the release CI publishes for that merge, downloaded there and offered the same way. +The [user guide](docs/GUIDE.md) has the details. -The version code of every update is GitHub Actions' run number for the merge, the same one CI gives it, so a build there and CI's release of the same merge are the same version and the next one is always higher. The app copies its helper [`herdr-app-build`](host/herdr-app-build/README.md) to `~/.local/share/herdr-android/` there (unless **Allow host scripts** is off for that machine). It asks for `REQUEST_INSTALL_PACKAGES`, and Android asks you once to let it install updates. +- [**Tasks and Needs you**](docs/GUIDE.md#tasks-and-needs-you): one list across every machine, with what waits for you on top and its answer on the row. +- [**Agent chats**](docs/GUIDE.md#agent-chats): read an agent's transcript, write to it, and answer its questions with buttons. +- [**Pictures and voice**](docs/GUIDE.md#pictures-and-voice): send photos and screenshots, dictate, or send a voice message. +- [**The Assistant and the board**](docs/GUIDE.md#the-assistant-and-the-board): talk to a space's Assistant, accept finished tasks, read artifacts, on [Shepherdr](https://github.com/eliasstravik/shepherdr) 3 or [simple-herdr-projects](https://github.com/makefunstuff/simple-herdr-projects). +- [**Start work**](docs/GUIDE.md#start-work): make a task or start an agent in a folder you pick. +- [**The terminal**](docs/GUIDE.md#the-terminal): any pane, with a key bar you can edit. +- [**Search**](docs/GUIDE.md#search): agents, tasks, machines and the words in recent chats. +- [**Code, pull requests, browser and docs**](docs/GUIDE.md#code-pull-requests-browser-and-docs): read a task's changes, review its pull request, watch its browser, read the agents' docs. +- [**Machines**](docs/GUIDE.md#machines): health, AI usage and limits, agent logins, and the spaces and panes. +- [**Notifications**](docs/GUIDE.md#notifications): with the app closed, with Approve and Reply buttons, quiet hours and Live Updates. +- [**The brief**](docs/GUIDE.md#the-brief): what happened since you last looked, also read aloud. +- [**From outside the app**](docs/GUIDE.md#from-outside-the-app): share to the Assistant, widgets, a Quick Settings tile, floating chats. +- [**Change the app from the phone**](docs/GUIDE.md#change-the-app-from-the-phone): describe a change and install the result. +- [**Settings and security**](docs/GUIDE.md#settings-and-security): fingerprint before risky actions, app lock, the SSH key. +- [**Big screens and accessibility**](docs/GUIDE.md#big-screens-and-accessibility): a sidebar on tablets, TalkBack actions, text sizes. -## Screen previews +## Building and checks -UI changes are checked, and shown in review, with screen previews instead of an emulator: the app's real Compose screens rendered on the desktop JVM against a fake herdr machine with fixed demo data, in dark and light. +You need JDK 17, and the Android SDK for the app itself. ```sh -./gradlew -p tools/ui-preview render # every scene → tools/ui-preview/build/shots/*.png -./gradlew -p tools/ui-preview render -Ponly=agents,chat # some of them +./gradlew :app:assembleDebug # the app +./gradlew -p core test # core tests, no Android SDK needed +./gradlew :app:testDebugUnitTest # the app's JVM tests +./gradlew -p tools/ui-preview render -Ponly=agents # screen previews, no emulator ``` -It needs JDK 17, no Android SDK, and takes about half an hour for all 343 scenes (the Tasks list and what waits on it, the spaces drawer, search, start work, the Chat tab and a space's tab menu, the Assistants' chats with their plans, news lines, Accept cards, reactions, replies, find and voice, artifacts, docs and a document, a task's Chat, Files, Changes, Pull request (its review, approving, replying, a refusal) and Browser tabs, simple-herdr-projects' board and tasks, machines and adding one by its code or by hand, a machine, its health, usage and agent logins, agent chats (turns, held messages, `/` and `@`, the model pill, voice messages, dictation, pictures), the blocked agent's question, an agent's activity timeline, floating chats, the brief, the code screens, the terminal (its menu, a password, sticky keys, the key bar's editor, selected text), settings with notifications, Autopilot and the fingerprint, changing the app from the phone, machines on the Herdr desktop's herdr-core, the owner's real data's shape (`real-*`), TalkBack's actions, and the wide window). CI renders them too, a quarter of them on each of four runners, and uploads them as the `herdr-screens-` artifact. See [`tools/ui-preview/README.md`](tools/ui-preview/README.md) for how it works and how to add a scene, and [`AGENTS.md`](AGENTS.md) for how changes are reviewed. - -## Shepherdr - -On a machine running [Shepherdr](https://github.com/eliasstravik/shepherdr) 3.x (its service, `shepherdr.service`), the board is Shepherdr's: the app reaches the service on the machine's `127.0.0.1:7788` through the SSH connection (a port forward; `curl` and `bun` there where the SSH server forwards nothing), with the token in `~/.herdr/shepherdr/token`, read over SSH. It reads what the 3.1 web app reads (`web/shared/types.ts`): - -- **Spaces** of every machine the service links over herdr-core, each with its **Assistant**'s chat: words, tasks' news lines, question cards (a plan, several questions on one card), what an agent asks on its own card headed by its task, Shepherdr's notice when it can't read a chat, and its working line. -- **Tasks** in Shepherdr's own states and order (Needs you, Ready, Merging, Working, Queued, To do, Done), its second line, % done and the minutes left under the dot; tasks no space works in under their list's name. -- **Accept**: Shepherdr's Accept card (its summary, commits and files, checks, proof, details; the words of its `core/accept.js`) before `herdr-task accept`, from the task, its news line, Needs you and the board. -- **Artifacts**: recipes (`core/recipe.js`) drawn natively: text, lists, tables, charts, numbers, pictures, before and after, code, callouts, tabs, folded sections, and choices, forms and buttons that answer the agent; Mark up notes one block. A panel only the Herdr window runs says so. -- **Voice calls** with an Assistant or a task's agent (the phone icon), through the machine's ElevenLabs voice agent (`core/voice.js`, `/api/voice`): 16 kHz audio both ways, echo cancelled so you can talk over it. - -Shepherdr 2.x isn't supported any more. The code is `core/…/shepherdr/`; its tests read 3.1.2's own answers, scrubbed (`core/src/test/resources/shepherdr/`). - -## simple-herdr-projects - -On a machine with [simple-herdr-projects](https://github.com/makefunstuff/simple-herdr-projects) (`shp` on the `PATH`), the board can be its tasks instead, read over the same SSH connection with no service, port, token or push: `shp list --json` and `shp show` for the board and a task, `shp accept --yes` for Accept after the app's own confirmation, and `shp say` to answer what a worker asks. A task's chat is its worker's terminal: read with `hc read`, written with `shp say` (which sends through `hc send`), and an ask clears only once shp confirms the send. It polls while a screen is open. It has no Assistant, artifacts, Autopilot or voice, so those screens stay Shepherdr's, and 3.1 keeps working unchanged where it runs. - -Choose the task source per machine in **Edit machine → Tasks**: **Detect** (the default) picks simple-herdr-projects where `shp` is on the machine and Shepherdr 3 otherwise; **Shepherdr 3** or **simple-herdr-projects** pins one. The code is `core/…/shp/`; the screen previews fake its store (`tools/ui-preview/…/FakeShp.kt`, the `shp-*` scenes). - -## Docs - -The desktop's Docs panel on the phone: the herdr.doc/2 documents (the desktop's `mods/px-doc/SPEC.md`) agents write with `herdr-doc` (investigations, incident notes, runbooks, reports), read from a machine's `~/.herdr/docs` over its SSH connection with plain `ls`, `stat` and `head`, nothing to install. The list reads only each file's head (title, kind, status), newest first; one opens full screen with every block the format has (callouts, numbers, key/value lists, timelines, findings with their evidence, checks, steps, to-dos, tables, bars, code, diffs, links, pictures, folding sections), drawn in the app's look, and is read again whenever its file changes, so a draft fills in while you read it. Read only. They are in the **Artifacts** tab where the space's machine has no Shepherdr, behind the docs icon on Shepherdr's Artifacts list, and in a machine's **⋯ → Docs**. The code is `core/…/docs/` and `app/…/ui/docs/`; the previews fake the folder (`FakeDocs.kt`, the `doc*` scenes). +UI changes are checked with the [screen previews](tools/ui-preview/README.md): the app's real screens rendered on the desktop against a fake machine. [`AGENTS.md`](AGENTS.md) says which checks to run, [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) how the app reaches a machine, and [`docs/RELEASING.md`](docs/RELEASING.md) how releases are signed. ## Layout -- `core/`: pure Kotlin/JVM: SSH, herdr API, herdr-core's API (`core/hc`, [`docs/HERDR_CORE.md`](docs/HERDR_CORE.md)), host sessions, terminal emulator and its key bar (`core/terminal`), reading code (`core/code`), herdr.doc documents (`core/docs`), Shepherdr 3's client (`core/shepherdr`), simple-herdr-projects over SSH (`core/shp`), pull request reviews with gh (`core/github`), port forwarding (`core/ssh/PortForward.kt`, for Shepherdr's service and voice). `./gradlew -p core test`. -- `app/`: the Android app. `ui/theme` and `ui/design` hold its look and shared components ([`docs/DESIGN.md`](docs/DESIGN.md)). -- `tools/ui-preview/`: the screen previews. `./gradlew -p tools/ui-preview render`. -- `host/herdr-push/`: the watcher that runs on a machine and pushes to the phone (Python 3; core carries it as a resource, and its tests run it). -- `host/herdr-usage/`: the AI providers' usage reader, run on a machine for its usage page and herdr-push's warnings (Python 3; core carries it, and its tests run it). -- `host/herdr-app-build/`: the app's builder on the machine that builds the phone's updates (Python 3; core carries it, and its tests run it against a fake checkout, Gradle and herdr-secret). -- `host/herdr-logins/`: the agent CLIs' logins on a machine, and signing them in or out there with their own commands (Python 3; core carries it, and its tests run it against fake CLIs). +- `core/`: plain Kotlin: SSH, the herdr API, herdr-core ([`docs/HERDR_CORE.md`](docs/HERDR_CORE.md)), Shepherdr and simple-herdr-projects, transcripts, the terminal emulator, code and pull requests. +- `app/`: the Android app. Its look is in `ui/theme` and `ui/design` ([`docs/DESIGN.md`](docs/DESIGN.md)). +- `tools/ui-preview/`: the screen previews. +- `host/`: small Python scripts the app copies to a machine: [`herdr-push`](host/herdr-push/README.md) (notifications, pairing), [`herdr-usage`](host/herdr-usage/README.md) (AI usage), [`herdr-logins`](host/herdr-logins/README.md) (agent logins) and [`herdr-app-build`](host/herdr-app-build/README.md) (builds the app's updates). ## Contributing -Bug reports, ideas and pull requests are welcome: [`CONTRIBUTING.md`](CONTRIBUTING.md) says how to build and run the app, run the tests and the screen previews, and send a change. +Bug reports, ideas and pull requests are welcome. [`CONTRIBUTING.md`](CONTRIBUTING.md) says how to build, test and send a change. ## License -[MIT](LICENSE). The design tokens, icons, fonts, agent marks and the herdr logo it uses keep their own licences, listed in [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md). +[MIT](LICENSE). The design tokens, icons, fonts, agent marks and the herdr logo keep their own licences, listed in [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md). diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 00000000..f82cf779 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,40 @@ +# How the app reaches a machine + +Everything goes over one SSH connection per machine, with the key the app makes on the phone. Nothing runs on a server of ours. This page is for developers; what the user sees is in the [user guide](GUIDE.md). + +## herdr + +- **Classic herdr**: the app reaches herdr's socket API through `direct-streamlocal`, falling back to `herdr remote-api-bridge`. herdr answers one request per connection, so each call opens a new channel; `events.subscribe` keeps one open for live updates. Terminals use `herdr terminal session control`. +- **herdr-core** (the Herdr desktop app's runtime, also on Windows): one `herdr-core connect` channel per machine. How it is found and how its model maps onto the screens: [`HERDR_CORE.md`](HERDR_CORE.md). + +A machine may run both; the machine screen shows both in one list. + +## Boards + +- **Shepherdr 3** (`core/…/shepherdr/`): the app forwards a port to the service on the machine's `127.0.0.1:7788` (or uses `curl` and `bun` there when the SSH server forwards nothing), with the token from `~/.herdr/shepherdr/token`. It reads what the 3.1 web app reads (`web/shared/types.ts`). The tests use 3.1.2's own answers, scrubbed (`core/src/test/resources/shepherdr/`). +- **simple-herdr-projects** (`core/…/shp/`): `shp list --json`, `shp show`, `shp accept --yes` and `shp say` over SSH, and the worker's terminal with `hc read`. No service, port or token; the app polls while a screen is open. + +**Detect** picks `shp` where it is on the `PATH`, else Shepherdr 3. + +## Chats and the activity timeline + +An agent's chat and timeline are read from its own session files over SSH: Claude Code's (`~/.claude/projects`), Codex's (`~/.codex/sessions`), pi's and omp's (`~/.pi/agent/sessions`). Large sessions are pruned on the machine with Python 3 before they are sent, and only the newest part of a very long one is read. + +Pictures and voice messages are copied to `~/.cache/herdr-android/uploads/` on the machine and named to the agent as `@path`, since herdr passes agents only text and keys. + +## Code and pull requests + +Code is read with `git` and the shell. Git runs with `GIT_OPTIONAL_LOCKS=0`, so reading changes nothing, not even the index. Pull requests are read and reviewed with the machine's `gh` (`core/…/github/`); what you write goes to `gh api` on its input, never on a command line. + +## Docs + +herdr.doc/2 documents are read from `~/.herdr/docs` with `ls`, `stat` and `head` (`core/…/docs/`). The list reads only each file's head; an open document is read again when its file changes. + +## Host scripts + +The app copies small Python 3 scripts to a machine when a feature needs them, unless **Allow host scripts** is off for it. Each is one file using only the standard library, with a `version:` line the app compares before replacing it. + +- [`herdr-push`](../host/herdr-push/README.md): notifications, Live Updates, health readings, the brief's journal, and pairing. +- [`herdr-usage`](../host/herdr-usage/README.md): AI providers' usage and limits, with the keys and logins already on the machine. Nothing comes to the phone but the numbers. +- [`herdr-logins`](../host/herdr-logins/README.md): agent CLIs' logins, run with their own commands. +- [`herdr-app-build`](../host/herdr-app-build/README.md): builds the app's updates ([`RELEASING.md`](RELEASING.md#building-updates-on-a-machine)). diff --git a/docs/GUIDE.md b/docs/GUIDE.md new file mode 100644 index 00000000..4f901362 --- /dev/null +++ b/docs/GUIDE.md @@ -0,0 +1,242 @@ +# Herdr for Android: user guide + +What the app does and where to find it. To install it and add a machine, see the [README](../README.md). + +The app has a drawer of spaces on the left, grouped by machine, and three tabs at the bottom: **Chat**, **Tasks** and **Artifacts**. **Settings** and **Add machine** are at the foot of the drawer. + +- [Tasks and Needs you](#tasks-and-needs-you) +- [Agent chats](#agent-chats) +- [Pictures and voice](#pictures-and-voice) +- [The Assistant and the board](#the-assistant-and-the-board) +- [Start work](#start-work) +- [The terminal](#the-terminal) +- [Search](#search) +- [Code, pull requests, browser and docs](#code-pull-requests-browser-and-docs) +- [Machines](#machines) +- [Notifications](#notifications) +- [The brief](#the-brief) +- [From outside the app](#from-outside-the-app) +- [Change the app from the phone](#change-the-app-from-the-phone) +- [Settings and security](#settings-and-security) +- [Big screens and accessibility](#big-screens-and-accessibility) + +## Tasks and Needs you + +The **Tasks** tab is one list across every machine. In order: + +1. **Needs you**: agents that ask a question, tasks that ask or want a secret, Assistants that ask. +2. **Ready**: finished tasks, each with **Accept**. +3. The rest by state: Working, Queued, To do, Done and Idle. + +A filter field and chips (All, Needs you, Ready, Working…) narrow the list. An agent and the task it works on show as one row. + +Rows carry their answers: + +- A question has **Yes**, **No** and **Reply**. A longer menu opens in the chat. +- A task has its question's choices, **Give it** for a secret, or **Accept** when it is ready. +- Before pressing anything, the app reads the agent's screen again. If the question changed, it presses nothing. + +Finished agents wait under **Done** until you open their chat, swipe them right, or tap **Mark all seen**. + +Swipe an agent left to **Pin** it to the top. A long press opens its sheet, with Pin and its space's **Notify**: + +- **All**: its questions and each finish. +- **Needs me**: only its questions. +- **Off**: nothing. + +A long press on a space in the drawer sets the same. A question always shows in Needs you, whatever Notify says. + +The **+** offers **New task**, the **Assistant** and **Start an agent**. + +## Agent chats + +Tap an agent to open its chat. It shows the agent's own transcript (Claude Code, Codex, pi or omp), not its terminal. + +**Reading** + +- Each turn's work folds into one line, such as *Worked for 2m 36s · 1 edit · 1 command*. Tap it to open it. Failed calls are counted in red. +- Each turn shows its time. File paths are short, and edits show what they changed (`+12 −2`). +- Coming back after five minutes or more, a **While you were away** card says what happened. +- If you scroll up while the agent writes, a pill counts the new messages. +- **Activity timeline** (in the chat's **⋯**) lists every step the agent took, by turn: commands, edits, tests and failures, with filters and **Jump to a failure**. It is not available for herdr-core agents. + +**Writing** + +- A message sent while the agent works is **held** over the composer until the agent is ready. **Send now** sends it at once; **X** drops it. Nothing held goes by itself. +- **Quick replies** are one-tap chips (*continue*, *run the tests*). Turn them on and edit them in **Settings → Quick replies**. +- `/` at the start lists the agent's commands and skills. `@` completes a file in the agent's folder. +- The **model pill** beside **+** shows the model and how full its context is. Tap it for details and the machine's usage limits. + +**Answering a question** + +When an agent asks, its question is docked over the composer. + +- The choices are buttons. **Yes** comes first. A permanent allow shows as **Always for …** and asks for your fingerprint. +- **No, tell it…** opens a reply. The no is pressed only when you send your words. +- Keys the agent offers, such as Tab to amend or Esc to cancel, become buttons. +- A tag says what the action does, such as *pushes* or *installs packages*. It is red when it can't be undone (*deletes files*, *force-pushes*). +- Claude's own multiple-choice questions show each option with its description, and a **Submit** for several answers. + +## Pictures and voice + +**Pictures.** In any chat, **+** offers **Take a photo**, **Photos**, **Latest screenshot** and **File**. Up to 10 go with one message. + +- Photos are made smaller and turned upright. Screenshots stay sharp. +- Tap a thumbnail to crop it or mark it up with **Draw**, **Arrow** and **Highlight**. +- Pictures are copied to the machine as you add them. The agent gets them as files (`@path`). Claude Code sees the image; other agents open the file. +- Pictures in the chat open full size on a tap. + +**Talk to type.** Tap the mic, speak, and tap **Done**. Your words go into the message box to edit before you send. Where the machine runs Shepherdr 3 with whisper, the machine writes it down; elsewhere the phone does. + +**Voice messages.** Hold the mic, speak, and let go to send the recording itself. Slide left to cancel. The agent gets an audio file. Most agents can't listen to audio, so they see only that you sent one, unless the machine can transcribe it. + +## The Assistant and the board + +A machine's tasks come from one of two boards, detected per machine: + +- [**Shepherdr**](https://github.com/eliasstravik/shepherdr) **3**, where its service runs. Shepherdr 2 is no longer supported. +- [**simple-herdr-projects**](https://github.com/makefunstuff/simple-herdr-projects), where `shp` is installed. It has no Assistant, artifacts, Autopilot or voice. + +To pick one yourself: **Edit machine → Tasks**. + +**The Assistant** (Shepherdr only). Each space has an Assistant you talk to in the **Chat** tab. It makes tasks from what you ask, and its chat shows the tasks' news, its plans and questions, and **Accept** cards. You can reply to a message, react to it, find in the chat, hear the Assistant read aloud, or call it by voice. + +**A task** opens with tabs: **Chat**, **Files**, **Changes**, and, when it has them, **Pull request** and **Browser**. A ready task has **Accept** on top, with Shepherdr's summary of commits, files and checks. + +**Artifacts** (the third tab) are what the agents made for you: tables, charts, before-and-after shots, forms you answer. Where the machine has no Shepherdr, the tab shows its [docs](#code-pull-requests-browser-and-docs). + +**Autopilot** lets a space's Assistant keep working on its own. Turn it on in the Assistant's header. **Settings → Autopilot** shows how it runs on each machine. + +## Start work + +**Start work** asks what you want, then how and where. Open it from the **+** on Tasks, a space's long press, search, or the launcher icon's long press. + +- **Board task**: your sentence goes to the space's Assistant, which makes the task. +- **Agent now**: an agent starts in the folder you pick, with your sentence as its first message. It offers the agents installed on that machine. + +Folders you used before come first. **Browse…** walks the machine's folders. The phone never starts an agent in a mode that skips its permission prompts. + +## The terminal + +Any pane opens as a live terminal, sized to the phone. + +- **The key bar** has Esc, Tab, ^C, arrows, **fn** for F1–F12, and more. Ctrl, Alt and Shift are sticky: tap once for the next key, twice to keep them on. **Hold Space and drag** to move the cursor. +- **Edit the key bar** with the pencil at its end: add, remove and reorder keys, or build your own shortcut. The bar is the same in every terminal. +- **Select text** with a long press. Then **Copy**, **Quote in chat** (into an agent's message box) or **Send to the Assistant**. Nothing is sent until you send it. +- The **⋯** menu has **Find in scrollback**, **Copy screen**, **Keep screen on** and **Reconnect**. +- When the terminal asks for a password, a masked field appears. It types the password and keeps it nowhere. +- Pinch to change the text size. + +## Search + +The search icon on Tasks, the drawer or a machine opens one search (**Ctrl+K** on a keyboard). It finds agents, tasks, machines, workspaces and words in recent chats. Chips filter to **Needs you**, **Working** or **Done**. + +Words in chats are searched on the machines, in each agent's transcript, once you type three letters. Below the results, **New task about "…"** starts work on your words, and **Open a terminal in …** opens a shell in the workspace found. + +## Code, pull requests, browser and docs + +**Code.** A task's **Files** and **Changes** tabs show the folder it works in. An agent's **⋯ → Browse the code** opens the same for its folder. + +- Browse folders, **Go to file**, or search the code. +- Read files with line numbers and syntax colours. Markdown is rendered. +- **Changes** shows the branch, what isn't committed, the diff against `main`, and **History**. +- Tap a line number, then another, and **Comment**. The comment goes to the agent as a review note. **Keep for later** collects comments to send together. +- Nothing on the machine is changed by reading. + +**Pull request.** The task's pull request as GitHub shows it: state, checks, threads and conversation. **Approve**, **Request changes**, **Comment**, **Reply** and **Resolve** post as the machine's `gh` login. On your own pull request only Comment is offered. + +**Browser.** The task's agent browser, live. Touch the page to take over; **Hand back** returns it to the agent. **Open browser** starts one. + +**Docs.** The herdr.doc documents agents write (investigations, runbooks, reports), read from the machine's `~/.herdr/docs`. A draft fills in while you read it. Find them in a machine's **⋯ → Docs**, or the Artifacts tab where there is no Shepherdr. + +## Machines + +Tap a machine in the drawer. Its screen shows three tiles, then its spaces and panes. + +**Health.** RAM, CPU, swap and every disk, with a chart of the last hour. It lists the biggest processes and what they are (emulators, Gradle daemons, dev servers, agents). Tap one to **Stop process**. **Stop Gradle daemons** stops them all. When RAM or a disk runs low, you get a warning. Full detail on Linux; less on macOS and Windows. + +**Usage.** One card per AI provider the machine uses: + +- Claude Code and Codex: session and weekly limits, and when they reset. +- API accounts with keys on the machine: DeepSeek, OpenRouter, Anthropic, OpenCode Go. +- Local models: whether the server answers. + +Each card shows which tasks and agents used it most. You get notifications at 80%, 95% and when a limit runs out. Keys stay on the machine. The drawer's machine meter shows the tightest limit. + +**Logins.** Which agent CLIs are signed in (Claude Code, Codex, pi, opencode, Gemini CLI) and when each login runs out. Sign in again, paste a key, or sign out from the phone. A key found in a shell profile gets a warning. + +**Spaces and panes.** Each space lists its panes: an agent and its state, or a shell and its folder. Tap a pane to open its chat or terminal. On classic herdr, a long press renames, moves, zooms or closes panes, tabs and workspaces. herdr-core's spaces open their panes only. + +**Edit machine** (in **⋯**) changes its name, address, jump host, herdr session, board and **Allow host scripts**. Turn that off for a machine where the app should copy no helper scripts. + +## Notifications + +**While the app is closed.** Turn it on in **Settings → Notifications**. You need a [UnifiedPush](https://unifiedpush.org) app such as [ntfy](https://ntfy.sh). The app then sets up [herdr-push](../host/herdr-push/README.md) on each machine, which sends sealed pushes within seconds. The app also looks at its machines itself every 15 minutes, in case a push is lost. + +Each notification has buttons: **Approve**, **Deny** or **Reply** for an agent, **Accept** or **Answer** for a task. A tap opens the chat. A notification goes away once it is answered anywhere. + +**What reaches the phone** has a switch for each kind: + +- **Needs you**: an agent asks, a machine runs low. +- **Finished**: an agent ended its turn. Off by default. +- **Went wrong**: a review or check failed. +- **Limits**: an AI provider near or past a limit. + +Also there: + +- **Hold while I'm at the machine**: notifications are silent while you type in herdr or a machine's screen is unlocked. +- **Spaces**: the spaces with their own Notify, each with **Reset**. +- Inside the app, a short banner drops from the top when another agent starts asking. + +**Live Updates** show each working agent in the notifications, with a **Stop** button. On Android 16 they sit at the top of the shade with a chip in the status bar. + +**Quiet hours** hold what can wait (finished agents, Live Updates) during Do Not Disturb, Bedtime, Driving, car mode, or a schedule you set. Questions still come through. When quiet hours end, what was held comes as one summary. + +**On Windows**, herdr-push runs in your signed-in session without administrator rights. The PC has to be awake and signed in. + +## The brief + +The brief says what happened since you last looked, on every machine: what merged, what waits for you, which agents finished, and what is still running. Each line opens its task or chat. **Last 24 hours** is there too. + +Open it in **Settings → Brief → Open the brief**. + +- **When the brief comes**: get it as a notification at up to four times a day, on the days you pick. +- **Read aloud**: the phone reads it over your music, also with the screen off. **Read it aloud as it comes** reads the scheduled brief by itself, in the car or always. + +## From outside the app + +- **Share** a link, text or screenshot to **Herdr Assistant** from any app. It lands in the Assistant's message box for you to send. +- **Send to the Assistant** is in the menu of selected text, next to Copy. +- **Quick Settings tile**: **Assistant** opens the chat and says what needs you. +- **Launcher shortcuts** (long press the icon): Needs you, the Assistant, Start work, and your two latest chats. +- **Widgets**: **Ask the Assistant**; **Herdr** with counts across machines; **Needs you** with each item's buttons on its row. +- **Floating chats**: **⋯ → Float over other apps** in an agent chat floats it as an Android bubble. The first time, allow **All conversations can bubble**. + +## Change the app from the phone + +The app can change itself, with the work done on one of your machines. + +1. Long-press a screen's title (or **⋯ → Change this screen**), say what should change, and **Send**. It goes, with a screenshot, to the **herdr-android** space's Assistant. +2. The machine makes it a task. You review and accept it as any task. +3. Once merged, the app offers **Update ready**. **Install** hands it to Android, which asks you first. +4. **Undo** reverts the last change as a new task. + +**Settings → Change the app** picks the build machine and says what it is missing. It needs a herdr-android checkout, the Android SDK, JDK 17 or newer, Python 3, git, and Shepherdr 3 with a **herdr-android** space. To build updates signed with the release key, keep the key in herdr-secret ([`docs/RELEASING.md`](RELEASING.md#building-updates-on-a-machine)); without it, each update is CI's release for that merge. + +## Settings and security + +**Fingerprint and app lock** asks for your fingerprint (or face, or PIN) before what can't be undone: **Always allow** on a question, **Approve and merge**, **Stop a process**, **Remove a worktree**, **Agent logins** and **Replace the SSH key**. Each can be turned off. + +**App lock** asks when the app opens, and again after it was away for a while. It hides the app in recent apps, and a locked phone's notifications only say "Something needs you". + +**SSH key**: copy the public key, **Back up** to a passphrase-protected file, **Restore or import**, or make a **New key**. The app never replaces a key without asking. + +Settings also has notifications, the brief, Autopilot, text sizes, the Assistant's voice (a voice server of your own, else the phone's voice), quick replies and Change the app. + +## Big screens and accessibility + +On a window 840 dp wide or more (a tablet, an open foldable, desktop windowing) the drawer stays open as a sidebar. Rotating or resizing keeps what is open. + +- TalkBack gets every long-press action as an action on the row: **Answer yes**, **Answer no**, **Reply…**, **Open terminal**, **Why this state?** and more. +- Chats and the terminal have their own text size, on top of the phone's (**Settings → Text size**). +- Back is predictive, and answering or sending gives a light haptic. diff --git a/docs/RELEASING.md b/docs/RELEASING.md new file mode 100644 index 00000000..4d107d59 --- /dev/null +++ b/docs/RELEASING.md @@ -0,0 +1,42 @@ +# Releasing + +## How releases are made + +CI builds the APK on every push and pull request. Only the default branch publishes a release, tagged `v0.1.`, with `herdr-android.apk` attached. Other branches and pull requests leave the APK as the `herdr-android-` artifact on their Actions run. + +The version code is the Actions run number, so every release installs over the one before. + +## Signing key (once, for the repo owner) + +Android installs an update only when it is signed with the same key as the installed app. Make the key once and keep a copy somewhere safe: losing it means everyone has to uninstall once (and restore their SSH key backup). + +```sh +keytool -genkeypair -keystore herdr-release.jks -alias herdr \ + -keyalg RSA -keysize 4096 -validity 10000 -dname "CN=Herdr for Android" +base64 -w0 herdr-release.jks > herdr-release.jks.b64 +``` + +Add these secrets under **Settings → Secrets and variables → Actions**, or with `gh secret set NAME`: + +| Secret | Value | +| --- | --- | +| `HERDR_KEYSTORE_BASE64` | the contents of `herdr-release.jks.b64` (`gh secret set HERDR_KEYSTORE_BASE64 < herdr-release.jks.b64`) | +| `HERDR_KEYSTORE_PASSWORD` | the keystore password | +| `HERDR_KEY_ALIAS` | `herdr` | +| `HERDR_KEY_PASSWORD` | optional: the key password, only if it differs from the keystore password | + +Without these secrets the APK is signed with the debug key and the default branch publishes no release. + +## Signing a local build + +Local builds use the debug key. To sign one with the release key, set `HERDR_KEYSTORE_FILE`, `HERDR_KEYSTORE_PASSWORD` and `HERDR_KEY_ALIAS`, then: + +```sh +./gradlew :app:assembleRelease +``` + +## Building updates on a machine + +[Changing the app from the phone](GUIDE.md#change-the-app-from-the-phone) builds updates on one of your machines with [`herdr-app-build`](../host/herdr-app-build/README.md). To sign them with the release key, keep `HERDR_KEYSTORE_FILE` (the keystore's path on that machine), `HERDR_KEYSTORE_PASSWORD` and `HERDR_KEY_ALIAS` in herdr-secret (Settings › Secrets in Herdr, or `herdr-secret set NAME`). The build runs under `herdr-secret run`, so the values reach only Gradle, never a log, a chat or git. + +Without them, the update is CI's release for that merge, downloaded on the machine. Either way the version code is the merge's Actions run number, so a local build and CI's release of the same merge are the same version. diff --git a/host/herdr-push/README.md b/host/herdr-push/README.md index 427a75fc..4df65307 100644 --- a/host/herdr-push/README.md +++ b/host/herdr-push/README.md @@ -38,7 +38,7 @@ The app also **looks at its machines itself** while it is closed (every 15 minut ## On Windows -herdr-push runs on a Windows PC with Git Bash under OpenSSH Server (see the README's "A Windows machine"), with Python 3.6 or newer. The app installs it as it does elsewhere; by hand, `python herdr-push install` does the same. +herdr-push runs on a Windows PC with Git Bash under OpenSSH Server (see [Windows](../../README.md#windows) in the README), with Python 3.6 or newer. The app installs it as it does elsewhere; by hand, `python herdr-push install` does the same. It starts without administrator rights, in two ways: @@ -99,7 +99,7 @@ curl -fsSLO https://raw.githubusercontent.com/makefunstuff/herdr-android/HEAD/ho python3 herdr-push pair ``` -It prints the code, what it names (the machine, its host key) and the link the code holds, to paste in the app when the phone can't scan. Then it waits until a phone paired, five minutes passed, or Ctrl-C. In the app: **Machines → Add machine → Scan the code**. +It prints the code, what it names (the machine, its host key) and the link the code holds, to paste in the app when the phone can't scan. Then it waits until a phone paired, five minutes passed, or Ctrl-C. In the app: the spaces drawer's **Add machine → Scan the code**. | Option | | | --- | --- | diff --git a/tools/ui-preview/README.md b/tools/ui-preview/README.md index 7a63d5ac..359b5273 100644 --- a/tools/ui-preview/README.md +++ b/tools/ui-preview/README.md @@ -14,6 +14,21 @@ Needs JDK 17 and, the first time, network for Gradle to fetch Compose Desktop. E In CI the Android workflow's four `render (k/4)` jobs each render a quarter of the scenes (`-Pshard=k/4`), about 10 minutes apiece, and the `screens` job puts their shots together as the `herdr-screens-` artifact; it fails when any of them did. Each job stops after 25 minutes at most. +## The README's screenshots + +The README's gallery is these scenes, made smaller as WebP: + +```sh +./gradlew -p tools/ui-preview render -Ponly=agents-v2,chat-tab,chat,chat-blocked,task-v2-ready,task-v2-pr,task-v2-browser,doc,machines,machine,terminal +cd tools/ui-preview/build/shots +# the tall ones, cut to their first screen first +for s in agents-v2 agents-v2-light doc; do magick $s.png -gravity north -crop 824x1784+0+0 +repage $s.png; done +shot() { magick "$1.png" -strip -resize 540x -quality 82 "../../../../docs/screenshots/$2.webp"; } +shot agents-v2 tasks; shot agents-v2-light tasks-light; shot chat-tab assistant; shot chat chat +shot chat-blocked blocked; shot task-v2-ready task; shot task-v2-pr pull-request; shot task-v2-browser browser +shot doc doc; shot machines spaces; shot machine machine; shot terminal terminal +``` + ## How it works - `build.gradle.kts` compiles `app/src/main/kotlin` and `core/src/main/kotlin` as they are, for Compose Multiplatform on the desktop. The androidx libraries the app uses (navigation, lifecycle) have desktop builds under the same package names. From fa40546623354e5c54d3e903a72b8dd3125d370b Mon Sep 17 00:00:00 2001 From: Iurii Plugatarov Date: Sun, 8 Nov 2026 18:25:29 +0200 Subject: [PATCH 2/2] HERD-38 Guide: no worktree guard, swap; screenshot recipe keeps the full shots Co-Authored-By: Claude Opus 5.5 --- docs/GUIDE.md | 4 ++-- tools/ui-preview/README.md | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/GUIDE.md b/docs/GUIDE.md index 4f901362..5449ff46 100644 --- a/docs/GUIDE.md +++ b/docs/GUIDE.md @@ -165,7 +165,7 @@ Each card shows which tasks and agents used it most. You get notifications at 80 **Logins.** Which agent CLIs are signed in (Claude Code, Codex, pi, opencode, Gemini CLI) and when each login runs out. Sign in again, paste a key, or sign out from the phone. A key found in a shell profile gets a warning. -**Spaces and panes.** Each space lists its panes: an agent and its state, or a shell and its folder. Tap a pane to open its chat or terminal. On classic herdr, a long press renames, moves, zooms or closes panes, tabs and workspaces. herdr-core's spaces open their panes only. +**Spaces and panes.** Each space lists its panes: an agent and its state, or a shell and its folder. Tap a pane to open its chat or terminal. On classic herdr, a long press renames, moves, swaps, zooms or closes panes, tabs and workspaces. herdr-core's spaces open their panes only. **Edit machine** (in **⋯**) changes its name, address, jump host, herdr session, board and **Allow host scripts**. Turn that off for a machine where the app should copy no helper scripts. @@ -225,7 +225,7 @@ The app can change itself, with the work done on one of your machines. ## Settings and security -**Fingerprint and app lock** asks for your fingerprint (or face, or PIN) before what can't be undone: **Always allow** on a question, **Approve and merge**, **Stop a process**, **Remove a worktree**, **Agent logins** and **Replace the SSH key**. Each can be turned off. +**Fingerprint and app lock** asks for your fingerprint (or face, or PIN) before what can't be undone: **Always allow** on a question, **Approve and merge**, **Stop a process**, **Agent logins** and **Replace the SSH key**. Each can be turned off. **App lock** asks when the app opens, and again after it was away for a while. It hides the app in recent apps, and a locked phone's notifications only say "Something needs you". diff --git a/tools/ui-preview/README.md b/tools/ui-preview/README.md index 359b5273..aa67669f 100644 --- a/tools/ui-preview/README.md +++ b/tools/ui-preview/README.md @@ -21,12 +21,12 @@ The README's gallery is these scenes, made smaller as WebP: ```sh ./gradlew -p tools/ui-preview render -Ponly=agents-v2,chat-tab,chat,chat-blocked,task-v2-ready,task-v2-pr,task-v2-browser,doc,machines,machine,terminal cd tools/ui-preview/build/shots -# the tall ones, cut to their first screen first -for s in agents-v2 agents-v2-light doc; do magick $s.png -gravity north -crop 824x1784+0+0 +repage $s.png; done +# the tall ones, cut to their first screen into a copy +for s in agents-v2 agents-v2-light doc; do magick $s.png -gravity north -crop 824x1784+0+0 +repage $s-top.png; done shot() { magick "$1.png" -strip -resize 540x -quality 82 "../../../../docs/screenshots/$2.webp"; } -shot agents-v2 tasks; shot agents-v2-light tasks-light; shot chat-tab assistant; shot chat chat +shot agents-v2-top tasks; shot agents-v2-light-top tasks-light; shot chat-tab assistant; shot chat chat shot chat-blocked blocked; shot task-v2-ready task; shot task-v2-pr pull-request; shot task-v2-browser browser -shot doc doc; shot machines spaces; shot machine machine; shot terminal terminal +shot doc-top doc; shot machines spaces; shot machine machine; shot terminal terminal ``` ## How it works