Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

agentic-terminal

A multi-tab desktop terminal built for workflows where programs drive other programs. From inside any tab you can open, run, close, archive, rename, or badge other tabs in the same window through a small CLI — including sub-tabs hosted inside another tab. Tabs persist across restarts, follow their shell's working directory, and can be grouped into named collapsible folders — so a long-running watcher, an LLM agent loop, and a one-shot build command can all live side by side without spawning new windows.

Features

  • Multi-tab in one window — vertical tab strip, browser-style; middle-click closes a tab, the strip is mouse-resizable.
  • Sub-tabs (tab-in-tab) — any tab can host a horizontal mini-strip of full-fledged sub-tabs at its bottom edge (own PTY, own session id, persisted in state.yml, archivable, DnD promote/demote between the mini-strip and the main strip). A program inside a tab opens one with run --sub / new-tab --sub, and every id-command works on sub ids too.
  • Tabs are addressable from other tabs — a built-in CLI lets any program inside a tab open new tabs, run commands in them, close them, rename them, or set an icon or badge — all in the same window.
  • Tabs persist across restarts — the tab list, groups, working directories, and reopen commands are saved on close and restored on launch.
  • Tabs follow the shell's working directory — for interactive bash, a sidecar tracks the shell's cwd and updates the tab title automatically (needs a one-line hook in ~/.bashrc, see docs/persistence.md).
  • Named groups — bundle related tabs into a collapsible group with a default working directory.
  • Tab archive — Archive closes a tab but shelves its full state (title, reopen command, cwd, icon/badge) in state.yml; restore is one click from the group's "Unarchive (N)" submenu or the always-visible unarchive (N) row at the top of the strip (grouped entries return to the bottom of their group, ungrouped to the end of the strip; the tab keeps its Terminal N number). Archiving a tab with sub-tabs shelves the whole subtree; restore brings the subs back.
  • Per-tab icons and badges — a single Unicode code point per tab (e.g. ●, ○, 🚀); badges auto-clear on the first user interaction with the tab.
  • run blocks until done — runs a command in an ephemeral tab, streams its stdin/stdout back to the caller, and returns the command's exit code; the tab is never persisted.
  • Custom shell — config.yml defines which shell is spawned (command, arguments, environment) and, on Windows, the PTY backend (pty: conpty | winpty, default conpty; winpty is the byte-identical rollback to the old behavior — see docs/persistence.md).
  • Font fallback — glyphs the bundled JetBrains Mono does not cover (⎿, emoji, CJK…) are drawn at runtime with the first covering font of the terminal-font.fallback chain in config.yml (default [Segoe UI Symbol, Segoe UI Emoji, Microsoft YaHei, Dialog]; an explicit fallback: [] disables — see docs/persistence.md) — in the terminal cells and in the tab strip's icons and badges alike.

Screenshot

agentic-terminal screenshot

(The screenshot predates the tab archive — the strip now also always shows a unarchive (N) row pinned at the top.)

CLI

Every tab injects three environment variables — AGENTIC_TERM_CMD (path to the launcher), AGENTIC_TERM_SESSION_ID (the tab's id), and AGENTIC_TERM_HOME (the app's state directory, where messenger.port and runtime files live; see docs/persistence.md). Any program inside a tab can call the launcher to talk back to the running window:

agentic-terminal run --cwd <dir> [--title <t>] [--tab-id <n> | --sub | --group-id <n> | --group-name <s> [--create-group-if-missing]] <cmd...>
agentic-terminal new-tab --cwd <dir> [same placement options] <cmd...>
agentic-terminal close-tab <id>
agentic-terminal archive-tab <id>                     # close but shelve the tab's state; NOT idempotent
agentic-terminal rename-tab <id> <title...>          # empty title reverts to "Terminal N"
agentic-terminal get-current-group-id [sessionId]   # default: $AGENTIC_TERM_SESSION_ID
agentic-terminal set-reopen-cmd --cwd <dir> <cmd...>
agentic-terminal clear-reopen-cmd                    # clears only the cmd; cwd stays
agentic-terminal set-icon <symbol>
agentic-terminal set-badge <symbol>
agentic-terminal clear-icon
agentic-terminal clear-badge

A few things worth knowing up front:

  • run is blocking and ephemeral — it opens a temporary tab, runs the command, tees the PTY output to your stdout, pipes your stdin into the PTY, and exits with the command's own exit code. Its tab is never written to state.yml and never restored. Use it for one-shot tasks whose output you want to see right in the current shell.
  • new-tab is the persistent version — the tab survives a restart (reopened as a plain shell in the recorded cwd); the command itself is not persisted.
  • --tab-id / --sub place the new tab as a SUB-TAB — inside the mini-strip of the tab with that id; --sub resolves the parent from the tab's own AGENTIC_TERM_SESSION_ID (from inside a sub it means the parent tab, so the new sub arrives as a sibling). One placement option per command.
  • archive-tab shelves instead of deleting — it closes the tab but keeps its state for a one-click restore from the strip's Unarchive menus. Unlike close-tab it is NOT idempotent: an unknown or already-archived id exits 1.
  • --cwd is mandatory for run, new-tab, and set-reopen-cmd.
  • Icons and badges take exactly one printable Unicode code point. The Windows JVM launcher mangles non-ASCII argv through the ANSI codepage (● → ?, ○ → 0 on cp1251), so prefer the U+XXXX hex token form (e.g. U+25CF) when calling from a Windows tab.
  • Badges auto-clear on the first user interaction with the tab — a click or any keystroke — and the clear is persisted, so a badge never reappears after a restart until something explicitly sets it again.
  • Exit codes: 0 ok, 1 operational failure (no running instance / unknown tab), 2 usage or parse error, 3 get-current-group-id on an ungrouped tab.

From cygwin/git-bash, cygpath -u the launcher before exec and cygpath -w any POSIX path you pass to --cwd.

Full reference (syntax, edge cases, wire format): docs/cli.md.

Claude Code plugin

The claude-plugin/ directory ships a small Claude Code plugin with three pieces that make agentic-terminal and Claude Code work well together:

  1. agentic-terminal skill — when a Claude Code session runs inside an agentic-terminal tab (i.e. AGENTIC_TERM_SESSION_ID is set), the skill teaches the agent the CLI above, so it can open, run, close, archive, rename, and badge tabs from inside the window while it's working.
  2. Reopen-sync hook — keeps the tab's reopen command pointed at the live Claude session id. Without it, every claude -r <id>, /clear, /compact, or /fork would resume a stale fork of the previous session.
  3. Badge-sync hook — sets a tab badge when the agent finishes a turn (●) or asks the user for a decision / has been idle (○), so a glance at the tab strip shows which session is waiting for input.

Install (from a bash tab where AGENTIC_TERM_CMD is exported and you have a working claude):

claude --plugin-dir <repo>/claude-plugin

The hooks auto-detect whether they're running inside an agentic-terminal tab via the env vars; outside one, they're silent no-ops. Hook script details, environment contract, tests, and known limitations: claude-plugin/README.md.

Messenger notifications (messenger-plugin/)

A second, independent plugin ships alongside: messenger-plugin/ forwards Notification/Stop/SessionStart/SessionEnd/UserPromptSubmit/PreToolUse(AskUserQuestion) events over localhost UDP to the app's messenger bridges. On Telegram an attached tab becomes a live console: a "⏳ …думаю…" placeholder with an elapsed timer edits itself every ~10 s while Claude works and becomes the final answer in place, AskUserQuestion and permission prompts arrive as inline-keyboard buttons (multi-select toggles, «✍ Другое…» answers free text straight from the chat), and you can type into the tab, /stop it, or /tail its screen right from the messenger. Two transports implement the same remote-control surface, and both can run at once (a tab can be attached to both):

  • VK Teams — enabled by a hand-edited teams-config.yml next to state.yml; one attached tab = one chat thread.
  • Telegram — enabled by a hand-edited telegram-config.yml next to state.yml (token, groupId, optional proxy:); one attached tab = one forum topic of the configured supergroup (created at the first attach and reused on every re-attach — even across restarts and archive→restore; a hand-deleted topic is self-healed).

Placeholder examples only — real credentials live in the gitignored local_scripts/bot_data.txt; full command list and hook install: messenger-plugin/README.md:

claude --plugin-dir <repo>/messenger-plugin

Upgrading from teams-plugin/ (the pre-Telegram name): the rename is breaking for existing installs — re-point installed hook paths in ~/.claude/settings.json to messenger-plugin/ and delete the stale teams.port next to state.yml (one-time, see docs/known-issues.md).

Build & Run

./gradlew run                # launch the terminal
./gradlew build              # compile and run all tests
./gradlew jpackageAppImage   # build a standalone Windows EXE with an embedded JRE

Requires a JDK 25 toolchain; the Gradle wrapper auto-provisions one.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages