Skip to content

TUICommander v1.7.1

Choose a tag to compare

@github-actions github-actions released this 03 Aug 18:52
v1.7.1
f8f5a8a

What's Changed

Added

  • tuic run and a tuic ls you can readtuic run pnpm dev creates a session and starts the command in it. tuic ls prints short IDs instead of full UUIDs (targets already accept prefixes) and gained --json for scripts, tuic capture -n 50 returns just the tail, and tuic status names the sessions that are waiting on you. Session targets now also match a name prefix, case-insensitively.

  • pi is a tracked agent — A pi session was invisible to TUICommander: the process path resolves to the Node interpreter, so classification saw node, there was no screen adapter, and a finished turn left the tab busy forever — reproduced live at 71.6 seconds with the composer already back. pi is now classified from its own command line, its animated Working… footer and bare composer row drive busy and idle, and resume runs pi --continue. Session discovery over ~/.pi/agent/sessions/ stays unwired on purpose: --continue already resumes the newest session for the working directory, which is exactly what discovery would compute.

  • Completion chimes can be silenced for MCP-spawned sessions — An agent orchestration turned every finished remote worker into a beep. Settings > Notifications gains an Orchestration toggle: remote-spawned sessions still land in Activity and bump the badge, they just do not chime. Locally created terminals are unaffected.

Changed

  • An off-domain MCP authorization server is now a warning, not a block — Discovery hard-failed with an issuer mix-up error whenever a gateway served metadata naming the real upstream identity provider — the normal topology for gateways, corporate proxies, and hosted tenants, with no way past it. Both issuer gates are advisory now: discovery warns and continues, and the consent dialog names the authorization server's origin so the decision belongs to the user. Discovery also tries the standard insertion form before the non-normative append form for path-bearing issuers (they describe different servers), falls through on any non-2xx candidate instead of aborting the chain, and reports the response body of a failed client registration instead of discarding it.

  • agent send and register now report whether a message will actually surfacesend answered ok/accepted for a message that only landed in the inbox, and register said nothing about whether the identity had a terminal behind it; together that is how an orchestrator's reply to an agent launched outside a TUIC PTY vanished with both sides believing it had been delivered. send returns delivered, a warning, and recipient_has_terminal; register returns terminal and, when false, states the consequence and the way out.

  • Debug builds carry line-tables-only debug info — Full DWARF cost 82 GiB of target/ for this crate alone. Panic backtraces keep file and line, and profilers keep their symbols; only debugger variable inspection is dropped. Measured over a full rebuild: 82.6 GiB down to 8.4 GiB.

Fixed

  • tuic . opens the repository again — a directory used to create a stray terminal session and leave the sidebar untouched; the repo-opening branch behind it could never run, and even when reached it refused any folder that was not already registered. A directory is now handed to the app as a repo: known ones activate silently, a new one is confirmed once and then added through the same path as the sidebar's "Add Repository". tuic . also stops registering the repo as /path/..

  • tuic send no longer mangles text containing a key name — key names were substituted anywhere in the argument, so tuic send build "Enter the room" pressed Return and typed "the room". Arguments are now matched as whole tokens, and the key set covers arrows, Home/End, PageUp/PageDown and the full C-<letter> range.

  • Workspace crates are actually testedmake check and CI ran only the main package, silently skipping 267 tests: the entire tuic-cli crate plus our patched alacritty_terminal and vte forks. Both now run the whole workspace.

  • Fullscreen terminal output can be scrolled without corrupting shell history — Alternate-screen applications now receive their own bounded, ephemeral scrollback, while persistent logs remain tied to the primary grid. Enter/exit transitions invalidate every renderer cache as one generation, and resizing a live fullscreen app no longer suppresses the first shell lines after it exits. Oversized refreshes remain byte-faithful, so repeated snapshots are expected rather than silently deduplicated. Editors and monitors that paint in place (vim, htop) produce no history at all; a pager like less paints its first page by printing lines, so it leaves roughly one screen of history behind — the same artifact iTerm2's equivalent option has.

  • Updating mdkb no longer leaves TUICommander connected to the old daemon.
    The detached daemon now identifies its version through the existing ping, and
    TUICommander compares it with the installed binary before accepting a cached
    or global socket. A missing or mismatched version triggers mdkb daemon restart; the in-app installer performs that check before reporting success.

  • Quiet dictation no longer types "Grazie." — Whisper answers near-silence with a subtitle credit learned from YouTube, in whatever language it guessed the silence was, and the filter behind the RMS gate only knew English phrases: an Italian bare Grazie. went straight into the terminal. All eleven offered languages are covered now, and the matching was split in two — a short thanks is dropped only when it is the entire transcript, so a dictated sentence that contains one is no longer destroyed, while channel boilerplate is dropped wherever it appears. The Amara credit is matched by its domain, which catches every translation of it at once.

  • Scrolled-off output is no longer deleted from the mobile log — Agent chrome was trimmed from each batch of lines as it left the screen, anchored on any separator row. Tool output, markdown tables and Codex's own divider lines all carry box-drawing runs, so a false positive silently dropped the rest of the batch from history for good: the mobile log showed paragraphs starting on their second line, and user messages echoed on a prompt row vanished. Chrome is now marked rather than truncated — readers skip it, the lines stay — and only a genuinely empty prompt row anchors the cut, never a blockquote or a table rule.

  • Renaming a file to a different case now worksrename_path canonicalized the destination, and on a case-insensitive filesystem (macOS APFS, Windows NTFS) readme.md resolves to the existing README.md, so the rename collapsed to rename(X, X) and did nothing at all — no error, no change. The destination now keeps the spelling that was asked for; only its parent directory is resolved, so the repo boundary check is unaffected.

  • A scrollbar column no longer pins a tab busy forever — Grok paints a scrollbar down the right edge once its output outgrows the viewport, which trims every otherwise-blank row to a single block glyph. Spinner detection read that as Aider's animated bar, so the screen never classified as ready and the tab stayed busy for the rest of the process — reproduced live on a finished turn still reporting busy two minutes later. Aider's bar always carries more cells plus its status text, so it is untouched.

  • agent detect reports every supported agent — The surface hardcoded four binaries, leaving Grok, Gemini, OpenCode, Amp, Cursor, and Droid invisible to an orchestrator even when installed, and the HTTP route rejected them as unknown. Both routes read one shared constant, and a test parses the frontend registry to fail the build if it gains an agent the backend does not report.

  • A finished Grok turn goes idle again — Grok moved its composer inside a rounded box, and the ready-screen adapter only matched a bare prompt character, which no current build emits. Ready never fired and the tab stayed busy — observed live 132 seconds after the turn had visibly finished, while the fixtures still encoded the older layout and passed. The spinner check still runs first, so a mid-turn screen remains working.

  • A worktree is no longer torn down mid-rebase — During a rebase a linked worktree sits on a detached HEAD and reports no branch, which two paths misread as dead: the sidebar row vanished and its terminals closed mid-conflict-resolution, and the orphan scan raised an archive prompt. A worktree with a rebase, merge, cherry-pick, revert, or bisect in progress is never an orphan, and the pre-rebase branch is recovered from Git's own record — applied to both the CLI parser and the default gix backend.

  • Reverse video swaps both default colors — Foreground and background resolution each received a single default and could only fall back to the one it was handed, so a cell with default foreground and background under reverse video painted an invisible block. That is the Pi composer caret, which made mid-text editing guesswork. Both now read the theme defaults, so no call site can get the pairing wrong.

  • Cmd+C copies a selection made inside a panel iframe — Text highlighted in a same-origin panel (plugin dashboards, HTML previews, srcdoc panels) lives in that frame's document, so the host copy path found nothing and the keystroke silently did nothing after the native Edit > Copy accelerator had swallowed it. The focused frame is now walked into first; cross-origin frames are left to the browser.

  • Duplicate MCP registrations no longer flood the log — Two bridge processes claiming the same session make the loser retry every three seconds forever, and each attempt logged a warning — 8149 lines in one day from three pairs, burying everything else. The first rejection per pair is reported, then one summary every five minutes carrying the suppressed count. Distinct pairs never hide each other, and idle entries are evicted so the map cannot grow unbounded.

  • Notes are no longer silently destroyed by a failed load — Config loading returned an empty default on any read or parse error and hydration swallowed the exception, so a failed load left the store empty and the first edit atomically overwrote notes.json with an empty array — permanent, silent loss. A missing file is now distinguished from a corrupt one (which is moved aside rather than replaced), the HTTP route answers 500 on failure exactly as IPC does, and every mutation path is a no-op until a load has succeeded.

  • A worktree removed by the backend disappears from the sidebar — Backend-initiated removals had no path to the store, so a removed worktree stayed listed and could even acquire a fresh terminal in its now-missing directory. Every removal path emits a worktree-removed event, bridged to SSE; the frontend closes the branch terminals and drops the row idempotently, and never touches the main checkout.

  • One blocking MCP wait no longer stalls every other request — The bridge dispatch loop awaited each proxied request inline, so a single agent wait or session wait — parked server-side for up to 300 seconds — held back everything queued behind it, and an unrelated call sent in the same parallel block inherited the whole wait before its own transport timeout even started counting. Requests are now spawned per line and complete out of order, with whole-line stdout locking so responses cannot interleave. initialize stays inline and shares a lock with reconnection, collapsing concurrent reconnects into one upstream initialize; in-flight requests get a five-second grace period on stdin close.

  • A peer resolves to the terminal behind it instead of assuming a shared id — An orchestrator running in a TUIC tab could not be woken by its own subagents: every send came back inbox-only while still answering ok, so messages piled up unread. The peer identity was serving as both mailbox address and terminal handle with only equality between them, and that equality never held — PTYs are keyed by a freshly minted uuid while the caller-supplied session id is exported separately. Delivery now resolves an announced identity to whatever PTY currently backs it, per call, so a respawn is picked up without re-registering. Four of the five session-creating paths injected no identity at all; all five now bind one, reusing the caller's stable identity where it exists and otherwise making the PTY key the identity. Teardown retires peer registrations filed under an announced identity, closing a leak where a self-registered peer outlived its terminal for the whole process lifetime.

  • Wake ownership is settled from proven delivery, not from the session existing — Managed-PTY delivery returned whether the session existed, which is not a receipt: a busy composer only parks the message, yet the call still reported success. Every call site marked the message terminal-dispatched, and the waiter filter hides terminal-owned messages — so a message that had never been typed was both unowned by the waiter and unsent by the terminal. Delivery now reports what it actually did (typed, queued, or unavailable), and a queued message deliberately stays pending.


📦 Downloads

Pick the right build for your system:

  • 🍎 macOS (Apple Silicon)
    TUICommander_1.7.1_aarch64.dmg
    (alt: TUICommander_aarch64.app.tar.gz)
  • 🪟 Windows (64-bit)
    TUICommander_1.7.1_x64-setup.exe
  • 🐧 Linux (Debian / Ubuntu)
    TUICommander_1.7.1_amd64.deb
  • 🐧 Linux (RHEL / Fedora / CentOS)
    TUICommander-1.7.1-1.x86_64.rpm
  • 🐧 Linux (portable)
    TUICommander_1.7.1_amd64.AppImage

🔐 Verify (optional)

Each release includes .sig files for signature verification.


💻 Install via terminal

# macOS (Homebrew)
brew install sstraus/tap/tuicommander

# macOS / Linux
curl -fsSL https://tuicommander.com/install.sh | sh
# Windows (PowerShell)
irm https://tuicommander.com/install.ps1 | iex