freshell-orchestration
Use when interacting with Freshell panes, panels, or tabs from the CLI for tmux-style automation and multi-pane workflows, outside external-browser automation tasks.
Freshell tmux-style automation
Start state
Freshell injects FRESHELL_URL and FRESHELL_TOKEN into every spawned terminal. They are already set in your environment.
MCP tool (preferred)
If you have the freshell MCP tool available, use it directly -- it's faster and doesn't require Bash approval for each command.
Quick start:
freshell({action: "help"})-- see all available commandsfreshell({action: "list-tabs"})-- see open tabsfreshell({action: "new-tab", params: {name: "Work", mode: "claude"}})-- create a tabfreshell({action: "send-keys", params: {target: "p1", keys: "hello\n"}})-- send input
The MCP tool accepts the same commands as the CLI below but with structured JSON input instead of positional arguments.
CLI fallback
If the MCP tool is not available (e.g., running outside Freshell or in a context without MCP support), use the CLI:
FSH="npx tsx server/cli/index.ts"
$FSH health
Use absolute paths for --cwd and --editor.
Mental model
- Freshell CLI is an HTTP client over
/api/*, not a local tmux socket client. - Tabs and pane trees live in
layoutStore. - Terminal lifecycle + scrollback live in
terminalRegistry. - Pane kinds:
terminal,editor,browser,agent-chat(Claude/Codex),picker(transient). - Picker panes are ephemeral. A freshly-created tab without
--mode/--browser/--editorstarts as apickerpane while the user chooses what to launch. Once they select, the picker is replaced by the real pane with a new pane ID. Never target apickerpane for splits or other mutations — wait until it resolves to its final kind, or use--mode/--browser/--editorflags onnew-tab/split-paneto skip the picker entirely. - Typical loop:
new-tab/split-pane->send-keys->wait-for->capture-pane/screenshot-*.
Choosing the right action and pane type
- split-pane vs new-tab: When the user says "pane", "split", "alongside", "next to", or "side by side", use
split-pane. Usenew-tabonly when the user explicitly says "tab", "window", or "new [thing]" with no spatial reference. When unsure,split-paneis the safer default. - Prefer specialized pane types: Do NOT open a terminal to run
cat/vim/nano/curl/wgetwhen a dedicated pane type fits.- "open/edit/show a file" ->
split-pane --editor /path/to/file - "open/show a URL" or "view a webpage" ->
split-pane --browser URLoropen-browser URL - "run a command" or "use a CLI tool" ->
split-pane --mode shellornew-tab --mode shell
- "open/edit/show a file" ->
- Sending text: Use
send-keys -lfor natural-language prompts or multi-word text. Do NOT append "ENTER" as literal text — send the command with-l, then sendENTERas a separate call. - Default targeting (MCP only): When no target is specified, the MCP tool resolves to your own pane/tab (set by
FRESHELL_TAB_ID/FRESHELL_PANE_ID), not the user's active viewport.split-panewithout a target splits your own pane. - Default direction:
split-panedefaults to vertical (top/bottom). Use-hfor horizontal (left/right).
Command reference
Output behavior:
- Most commands print JSON.
list-tabsandlist-panesprint TSV unless--json.list-panes --titlesappends a fifth TSV column with the pane title.capture-paneanddisplayprint plain text.
Targets:
- Tab target: tab ID or exact tab title.
- Pane target: pane ID, pane index in active tab, or
tabRef.paneIndex. - Omitted target on
rename-tabmeans the active tab. - Omitted target on
rename-panemeans the active pane in the active tab. - Omitted target on
split-pane(MCP) means your own pane, not the user's active viewport. - Omitted target falls back to active pane in active tab when command supports it (CLI only).
- If a target or name contains spaces, quote it.
- Use the flagged
-t/-nform when you want to make the target and name explicit.
Tab commands:
new-tab [-n NAME] [--claude|--codex|--mode MODE] [--shell SHELL] [--cwd DIR] [--browser URL] [--editor FILE] [--resume SESSION_ID] [--prompt TEXT]list-tabs [--json]select-tab [TARGET]orselect-tab -t TARGETkill-tab [TARGET]orkill-tab -t TARGETrename-tab NEW_NAME- rename the active tabrename-tab TARGET NEW_NAMErename-tab -t TARGET -n NEW_NAMEhas-tab TARGETorhas-tab -t TARGETnext-tabprev-tab
Pane/layout commands:
split-pane [-t PANE_TARGET] [-h] [--mode MODE] [--shell SHELL] [--cwd DIR] [--browser URL] [--editor FILE]list-panes [-t TAB_TARGET] [--json] [--titles]select-pane PANE_TARGETorselect-pane -t PANE_TARGETrename-pane NEW_NAME- rename the active panerename-pane TARGET NEW_NAMErename-pane -t TARGET -n NEW_NAMEkill-pane PANE_TARGETorkill-pane -t PANE_TARGETresize-pane PANE_TARGET [--x X_PCT] [--y Y_PCT]swap-pane PANE_TARGET --other OTHER_PANE_TARGETrespawn-pane PANE_TARGET [--mode MODE] [--shell SHELL] [--cwd DIR]attach TERMINAL_ID [PANE_TARGET]orattach -t TERMINAL_ID -p PANE_TARGET
Terminal interaction:
send-keys [-t PANE_TARGET] [-l] KEYS...capture-pane [-t PANE_TARGET] [-S START] [-J] [-e]wait-for [-t PANE_TARGET] [-p PATTERN] [--stable SECONDS] [--exit] [--prompt] [-T TIMEOUT_SECONDS]display -p FORMAT [-t PANE_TARGET]ordisplay FORMAT [PANE_TARGET]run [--capture|-c] [--detach|-d] [-T TIMEOUT_SECONDS] [-n NAME] [--cwd DIR] COMMAND...summarize PANE_TARGETorsummarize -t PANE_TARGETlist-terminals
Browser/navigation:
open-browser URL [-n NAME]navigate URL [PANE_TARGET]ornavigate --url URL -t PANE_TARGET
Screenshot commands:
screenshot --scope pane|tab|view --name NAME [--path DIR_OR_FILE] [--overwrite] [-t TARGET]- Aliases:
screenshot-pane -t PANE_TARGET --name NAME [--path ...] [--overwrite]screenshot-tab -t TAB_TARGET --name NAME [--path ...] [--overwrite]screenshot-view --name NAME [--path ...] [--overwrite]
--nameis required.--pathis optional; default output root is OS temp dir.- Pane/tab scopes resolve target before capture; view scope captures current app viewport.
Session/service:
list-sessionssearch-sessions QUERYorsearch-sessions -q QUERYhealthlan-info
tmux-style aliases:
new-window,new-session->new-tablist-windows->list-tabsselect-window->select-tabkill-window->kill-tabrename-window->rename-tabnext-window->next-tabprevious-window,prev-window->prev-tabsplit-window->split-panedisplay-message->displayscreenshot-pane,screenshot-tab,screenshot-view->screenshot
tmux differences
- Transport/auth: tmux uses local socket; Freshell uses HTTP API + token auth.
- Pane types: tmux terminal-only; Freshell supports terminal/editor/browser.
- Target model: tmux session/window/pane grammar vs Freshell ID/title/index resolution.
- Runtime model: tmux TTY-local; Freshell browser-first and remote-friendly.
- Feature model: Freshell adds session indexing/search and AI summary workflows.
Playbook: open file in editor pane
New tab:
FILE="/absolute/path/to/file.ts"
$FSH new-tab -n "Edit $(basename "$FILE")" --editor "$FILE"
Split current tab:
FILE="/absolute/path/to/file.ts"
$FSH split-pane --editor "$FILE"
Playbook: create, split, and rename without UI interaction
FSH="npx tsx server/cli/index.ts"
CWD="/absolute/path/to/repo"
FILE="/absolute/path/to/repo/README.md"
WS="$($FSH new-tab -n 'Triager' --codex --cwd "$CWD")"
TAB_ID="$(printf '%s' "$WS" | jq -r '.data.tabId')"
P0="$(printf '%s' "$WS" | jq -r '.data.paneId')"
P1="$($FSH split-pane -t "$P0" --editor "$FILE" | jq -r '.data.paneId')"
$FSH rename-tab -t "$TAB_ID" -n "Issue 166 work"
$FSH rename-pane -t "$P0" -n "Codex"
$FSH select-pane -t "$P1"
$FSH rename-pane "Editor"
Playbook: parallel Claude panes
FSH="npx tsx server/cli/index.ts"
CWD="/absolute/path/to/repo"
PROMPT="Implement <task>. Run tests. Summarize tradeoffs."
SEED_JSON="$($FSH new-tab -n 'Claude x4 Eval' --claude --cwd "$CWD")"
P0="$(printf '%s' "$SEED_JSON" | jq -r '.data.paneId')"
J1="$($FSH split-pane -t "$P0" --mode claude --cwd "$CWD")"
P1="$(printf '%s' "$J1" | jq -r '.data.paneId')"
J2="$($FSH split-pane -t "$P0" -h --mode claude --cwd "$CWD")"
P2="$(printf '%s' "$J2" | jq -r '.data.paneId')"
J3="$($FSH split-pane -t "$P1" -h --mode claude --cwd "$CWD")"
P3="$(printf '%s' "$J3" | jq -r '.data.paneId')"
for p in "$P0" "$P1" "$P2" "$P3"; do
$FSH send-keys -t "$p" -l "$PROMPT"
$FSH send-keys -t "$p" ENTER
$FSH wait-for -t "$p" --stable 8 -T 1800
$FSH capture-pane -t "$p" -S -120 > "/tmp/${p}.txt"
done
Screenshot-specific guidance
- Use a dedicated canary tab when validating screenshot behavior so live project panes are not contaminated.
- Close temporary tabs/panes after verification unless user asked to keep them open.
- Browser panes:
- Same-origin iframe content is captured best-effort.
- If iframe content is not capturable (for example cross-origin/security), screenshots intentionally render a placeholder message with source URL context instead of a silent blank region.
- For assertions, allow either explicit page content or the explicit non-capturable placeholder text depending on origin/security context.
REST API patterns
- Auth header:
x-auth-token: <TOKEN>(not Bearer). POST /api/tabswith{ name, mode: "shell", shell: "wsl", cwd }creates a tab with a terminal, bypassing the picker.POST /api/panes/:id/splitwith{ direction: "horizontal"|"vertical", browser?, editor?, mode?, cwd? }— defaults to vertical; always 50/50.POST /api/panes/:id/resizewith{ sizes: [left, right] }(percentages summing to 100) — call immediately after split to fix proportions.- Editor panes show "Loading..." until visited. When screenshotting multiple tabs, visit each tab once first to trigger editor loading, then loop back for screenshots.
DELETE /api/terminals/:idremoves orphaned terminals. Freshell has a 50 PTY limit; orphans from scripted runs accumulate silently.
Gotchas
- Use
send-keys -lfor natural-language prompts. wait-for --stableis usually more reliable than prompt heuristics across providers.- If target resolution fails, run
list-tabsandlist-panes --json, then retry with explicit IDs.
Files of this skill
This skill has no supporting files.