| name | danterm |
| description | Drive the DanTerm terminal from the shell. Use when the user asks to rename or close this tab, open or split panes, launch commands in new tabs or panes, read output from another pane, send keys into another pane, switch the theme, or work with DanTerm todos. DanTerm is a macOS-only terminal; only applies when the `danterm` command is on PATH. |
| allowed-tools | Bash(danterm *) |
danterm CLI
danterm is the shell client for the DanTerm terminal. Run
danterm help for the authoritative command list. The recipes below cover the
cases agents hit in practice.
CLI API
Keep this section synced with danterm help and the parser in
lib/DanTermProtocol/Sources/DanTermProtocol/CLIParser.swift.
danterm ls
danterm tab new [--group <group-id>] [--cmd <s>] [--cwd <p>] [--title <s>] [--background] [--foreground] [--after-selected | --at-group-end | --after-tab <tab-id>]
danterm tab rename [--tab <tab-id>] <name>|--clear
danterm tab close [--tab <tab-id>]
danterm pane focus <pane-id>
danterm pane info [--pane <pane-id>]
danterm pane split [--pane <pane-id>] -h|-v [--cmd <s>] [--cwd <p>] [--title <s>] [--background] [--foreground]
danterm pane input [--pane <pane-id>] [--literal] -- <token>...
danterm pane read --pane <pane-id> [--lines <n>]
danterm theme set [--pane <pane-id>] <name>|--clear
danterm agent attach --kind <kind> --id <session-id>
danterm doctor
danterm todo list [--pane <pane-id>]
danterm todo add [--pane <pane-id>] <text>
danterm todo edit [--pane <pane-id>] <todo-id> <text>
danterm todo done [--pane <pane-id>] <todo-id>
danterm todo open [--pane <pane-id>] <todo-id>
danterm todo delete [--pane <pane-id>] <todo-id>
danterm todo clear-completed [--pane <pane-id>]
CLI defaults are agent-safe: tab new opens in the background at the target
group end, and pane split opens in the background. The interactive app UI
keeps its own defaults. Use --foreground only when the user asked for focus:
for tab new, it selects the new tab; for pane split, it focuses the new pane
within its tab without selecting that tab.
tab new position flags are mutually exclusive:
- No position flag: append at the end of the target group.
--after-selected: insert after the currently selected tab in the target
group, falling back to the end of that group.
--at-group-end: explicit form of the default append behavior.
--after-tab <tab-id>: insert immediately after the referenced tab. If
--group is also supplied, it must name the referenced tab's group. If
--group is omitted, the referenced tab's group is the target and
$DANTERM_PANE is not required.
Targeting rule
Assume the user may keep using DanTerm while you run commands. Do not rely on
the app's currently focused group, tab, or pane.
$DANTERM=1 means the agent originated inside a DanTerm pane.
$DANTERM_PANE is the originating pane id. Use it only as input to
danterm pane info --pane "$DANTERM_PANE" when deriving live ids.
danterm may still work outside DanTerm if the app is running, but agents
must use explicit ids for mutation commands.
- If
$DANTERM_PANE is absent, start with danterm ls and select targets only
from explicit user-provided criteria visible in the JSON: id, exact group
name, exact tab customTitle, exact pane title, or cwd. If the criteria do
not produce one unique target, ask the user.
- Never target by
selectedTabId, current focus, list order, display title, or
a guessed id.
For agent commands:
tab new: always pass --group <group-id> or an explicit
--after-tab <tab-id> anchor. The default opens in the background at the
target group end. Pass --foreground only when the user asked to switch to
the new tab. Pass --after-tab <tab-id> or --after-selected only when the
user gave that placement anchor.
tab rename: always pass --tab <tab-id>.
tab close: always pass --tab <tab-id>.
pane split: always pass --pane <pane-id>. The default opens in the
background. Pass --foreground only when the user asked to focus the new pane
within its tab.
pane input, theme set, and todos: always pass --pane <pane-id>.
agent attach: hooks may use the implicit $DANTERM_PANE context; ordinary
agent recipes should not call it.
pane focus and pane read already require explicit pane ids; keep them
explicit.
Context env vars
DanTerm sets these per pane:
DANTERM -- set to 1 when the process originated inside DanTerm.
DANTERM_PANE -- caller's pane id. Humans may omit explicit targets inside
DanTerm; agents should use it only to derive live ids.
DANTERM_SOCK -- control socket path. Rarely needed; the CLI resolves it.
If these are absent, the user may be outside DanTerm. You may still use
danterm only with explicit ids derived from danterm ls and unique
user-provided criteria.
Derive targets
Inside DanTerm, derive the originating pane, tab, and group:
INFO=$(danterm pane info --pane "$DANTERM_PANE")
PANE_ID=$(jq -r '.pane.id' <<<"$INFO")
TAB_ID=$(jq -r '.tab.id' <<<"$INFO")
GROUP_ID=$(jq -r '.group.id' <<<"$INFO")
Outside DanTerm, do not use implicit app state:
danterm ls
Filter only by explicit user-provided criteria visible in the JSON, and require
exactly one matching pane, tab, or group before running any mutation command.
When to reach for this skill
| User says | Command |
|---|
| "rename this tab to X" / "label this tab" | tab rename --tab <tab-id> |
| "close this tab" / "close tab X" | tab close --tab <tab-id> |
| "open a new tab" / "...and run X in it" | tab new --group <group-id> with optional --cmd / position flags |
| "split the pane" / "...and run X in it" | pane split --pane <pane-id> with optional --cmd |
| "what's the build doing in the other pane?" | pane read --pane <pane-id> |
| "type X into pane " / "send Ctrl-C to..." | pane input --pane <pane-id> |
| "what tabs/panes are open?" | ls |
| "which tab/group contains this pane?" | pane info --pane <pane-id> |
| "switch the theme to X" | theme set --pane <pane-id> |
| "add/check off/edit a todo" | todo ... --pane <pane-id> |
| "check DanTerm integration health" | doctor |
Recipes
Rename or clear a tab
danterm tab rename --tab "$TAB_ID" "fix scrollbar math"
danterm tab rename --tab "$TAB_ID" --clear
Close a tab
danterm tab close --tab "$TAB_ID"
Closing the only remaining tab is refused so the CLI does not quit DanTerm as a
side effect.
Open a new tab and optionally run a command in it
danterm tab new --group "$GROUP_ID"
danterm tab new --group "$GROUP_ID" --cmd 'vim notes.md' --title notes
danterm tab new --group "$GROUP_ID" --cmd 'cargo test --workspace' --cwd ~/proj --title tests
danterm tab new --group "$GROUP_ID" --foreground --cmd 'vim .' --title work
danterm tab new --group "$GROUP_ID" --at-group-end --cmd 'just test' --title tests
danterm tab new --after-tab "$TAB_ID" --cmd 'just test' --title tests
--cmd runs inside your login shell, so shell config is sourced, PATH matches
your interactive panes, and shell integration reports cwd. The pane returns to
a shell prompt when the command exits.
By default, the user's current tab stays focused. Use --foreground only when
the user asked to switch to the new tab.
Use --after-tab <tab-id> when the user names an exact tab to place the new tab
after; this is an explicit target and can work without $DANTERM_PANE. Use
--after-selected only when the user explicitly wants selected-tab-relative
placement.
Launch Claude with an initial prompt
To open Claude Code in the new tab and seed its first prompt, keep stdout
attached to the terminal and pass simple prompts as an argument. This is Claude
Code's documented interactive initial-prompt form. Claude stays interactive
unless you use --print or send its stdout somewhere other than the terminal.
For serious agent work -- implementing a plan, verifying an issue, reviewing a
plan, reviewing an implementation, or similarly high-judgment tasks -- launch
Claude with --effort max.
danterm tab new --group "$GROUP_ID" --title review \
--cmd 'claude --effort max "review the staged diff"'
For shell-hostile or multi-line prompts -- backticks, $, quotes, parens,
newlines, which are common in code-review findings and file:line refs -- stage
the prompt in a file and feed it on stdin. This is not the docs' primary example,
but it is verified on Claude Code 2.1.152 and avoids shell-quoting the prompt
contents:
# write $PROMPT to the file first (heredoc/editor/agent write, not inline quoting)
danterm tab new --group "$GROUP_ID" --title verify \
--cmd 'claude --effort max < /tmp/danterm-prompt.txt'
Single-quote the whole --cmd value so the redirection is interpreted by the
tab's login shell. Use a unique filename per tab when launching several at once.
Split a pane and run a command in the new one
Orientation:
-h = horizontal split = side by side. The new pane opens to the right.
-v = vertical split = stacked. The new pane opens below.
Prefer --cmd over splitting and then sending keys; it avoids the
shell-prompt race.
danterm pane split --pane "$PANE_ID" -h --cmd 'just test' --title tests
danterm pane split --pane "$PANE_ID" -h --foreground --cmd 'just test' --title tests
By default, the caller's pane stays focused inside its tab. Use --foreground
only when the user asked to focus the new pane within that tab.
To capture the new pane id for later:
NEW=$(danterm pane split --pane "$PANE_ID" -v --cmd 'just test' | jq -r '.pane.id')
To navigate to a new pane that was split in another tab:
NEW=$(danterm pane split --pane "$PANE_ID" -v --cmd 'just test' | jq -r '.pane.id')
danterm pane focus "$NEW"
Read another pane's output
pane read prints raw text, not JSON. Without --lines, it returns the
visible viewport. With --lines N, it returns the last N lines of scrollback.
danterm pane read --pane "$PANE_ID"
danterm pane read --pane "$PANE_ID" --lines 200
Send keys to another pane
Use this for interrupts, replies to prompts, or scripted interaction with an
already-running program. For starting fresh commands in a new pane, prefer
tab new --group <group-id> --cmd or pane split --pane <pane-id> --cmd.
danterm pane input --pane "$PANE_ID" -- C-c
danterm pane input --pane "$PANE_ID" -- "y" Enter
Find ids
For the originating pane inside DanTerm:
danterm pane info --pane "$DANTERM_PANE"
For broader discovery:
danterm ls | jq -r '.. | objects | select(.type == "leaf") | .pane | "\(.id)\t\(.title // "")\t\(.cwd // "")"'
ls returns {groups, selectedTabId}. Each pane lives inline at a split-tree
leaf: groups[].tabs[].rootNode is the per-tab tree, and every
{ "type": "leaf" } node carries its pane under .pane ({id, title, cwd, agentSession?, ...}). agentSession, when present, is {kind, sessionId} for
the agent currently reported in that pane. The jq above recurses the tree to
list every pane. Treat selectedTabId as display state, not as a targeting
source.
Check integration health
doctor is local-only and does not require the app to be running. Use it when
the user asks whether DanTerm's shell command, agent hooks, agent skill, or jq
setup is healthy:
danterm doctor
The output reports all rows (INFO/SKIP/WARN/ERROR/OK) plus a summary footer.
Exit status is 1 only when a check is an ERROR; WARN/INFO/SKIP still exit 0.
Todos
danterm todo list --pane "$PANE_ID"
ID=$(danterm todo add --pane "$PANE_ID" "write the failing test first" | jq -r '.todo.id')
danterm todo edit --pane "$PANE_ID" "$ID" "write the failing test first, then implement"
danterm todo done --pane "$PANE_ID" "$ID"
danterm todo open --pane "$PANE_ID" "$ID"
danterm todo delete --pane "$PANE_ID" "$ID"
danterm todo clear-completed --pane "$PANE_ID"
Theme
danterm theme set --pane "$PANE_ID" Dracula
danterm theme set --pane "$PANE_ID" --clear
pane input token grammar
pane input takes tmux-style tokens after --. Each shell arg is one token,
sent as a separate event; there is no implicit space-joining. Quote a single
arg when spaces or newlines must be preserved.
- Bare words (
"ls", "cargo") are typed as text.
- Named keys are key presses:
Enter, Tab, BSpace, Escape, Up,
Down, Left, Right, Home, End, PgUp, PgDn, Delete, F1
through F12.
C-<x> is Ctrl-x and M-<x> is Alt-x, such as C-c, C-d, and M-b.
--literal disables key parsing. Every token after -- is emitted as text.
Each token is still a separate event; pass one quoted argument if you need
spaces inside the literal text.
Example: run a command and press enter:
danterm pane input --pane "$PANE_ID" -- "ls -la" Enter
Example: paste literal text containing a word that would otherwise be parsed as
a key:
danterm pane input --pane "$PANE_ID" --literal -- "Type Enter to continue"
CLI stdout shapes
Only these subcommands print to stdout. Pipe to jq accordingly. Everything
else prints nothing on success and exits 0.
| Command | Stdout |
|---|
ls | JSON: {groups, selectedTabId} (each pane embedded at its rootNode leaf under .pane, optionally with agentSession: {kind, sessionId}) |
pane info --pane <pane-id> | JSON: {pane: {id, title, cwd}, tab: {id, title, groupId}, group: {id, name}} |
tab new ... | JSON: {tab: {...}, panes: [{id}], group?: {id, name}} |
pane split --pane <pane-id> | JSON: {pane: {id}} |
todo list --pane <pane-id> | JSON: {todos: [{id, text, isDone}, ...]} |
todo add --pane <pane-id> | JSON: {todo: {id, text, isDone}} |
pane read --pane <pane-id> | Raw text from the requested pane, not JSON |
agent attach --kind <kind> --id <session-id> is a silent mutation: no stdout
on success.
Rules for agents
- Never
pane input into your own pane ($DANTERM_PANE) without an explicit
user request; you would be typing into your own input stream.
- Prefer
tab new --group <group-id> --cmd and
pane split --pane <pane-id> --cmd over the
split-then-pane input pattern. --cmd seeds the command at surface
creation time and avoids racing the shell prompt.
- To launch Claude with an initial prompt, keep its stdout attached to the
terminal. For serious agent work (implementing a plan, verifying an issue,
reviewing a plan, reviewing an implementation, etc.), use
claude --effort max. Pass simple prompts as arguments. For shell-hostile prompt text, stage
it in a file and use the DanTerm-verified stdin form
--cmd 'claude --effort max < /tmp/danterm-prompt.txt'. See the recipe above.
tab new and pane split default to background behavior for autonomous work.
Pass --foreground only when the user explicitly asked you to switch to the
new tab or focus the new split pane within its tab.
- For
tab new, the default position is the target group end. Use
--after-tab <tab-id> or --after-selected only when the user gave that
placement anchor.
- When a recipe needs an id, derive it from
pane info or ls using the
targeting rule above; do not guess UUIDs.
- Errors print to stderr as
danterm: <message> and exit non-zero. Surface
them rather than retrying blindly.
- macOS only. If
danterm is not on PATH, stop.