CLI - solutionscay/skiff GitHub Wiki

Skiff CLI

skiff is the command-line client for skiffd. It controls the same sessions and groups as the desktop app.

Install

skiff ships with the app and updates with it.

  • The .deb and .rpm packages install it to /usr/bin.
  • The AppImage copies it to ~/.local/bin when it starts.
  • The macOS app links it into /usr/local/bin, or /opt/homebrew/bin when it can write there. Otherwise, run Help › Install skiff command… once.

To build it from a checkout, run cargo install --path crates/skiff-cli.

A release build uses the installed daemon. A debug build uses the development daemon.

Run skiff status first. If it reports that skiffd is not running, open Skiff or start the daemon. Its first line shows the version and the path of the skiff you ran. It warns when another skiff comes first on PATH, or when the running skiffd is not the version this skiff was built for.

Session names

Most session commands accept a full session ID, a unique ID prefix, or the name shown in the sidebar. Use a full ID in scripts. Names can change.

Inside a Skiff terminal, . means the current session. For commands that accept a group, . means the group that contains the current session.

skiffd sets two variables in every terminal it starts:

Variable Value
SKIFF_SESSION The terminal's session ID.
SKIFF_SOCKET The socket of the daemon that started the terminal, so skiff reaches that daemon.

Terminal commands

Command Description
skiff ls [--json] List sessions.
skiff new [--name NAME] [--cwd DIR] Start a shell session.
skiff new -- CMD [ARGS...] Start a program in a terminal.
skiff new --agent ID [--name NAME] Start an installed agent. Use skiff agents to list IDs.
skiff send SESSION TEXT [--enter] Type text into a terminal. --enter sends Enter after the text.
skiff send SESSION - --enter < FILE Send standard input to a terminal.
skiff peek SESSION [-n LINES] Print the terminal screen. -n includes recent history.
skiff attach SESSION Run a session in this terminal. Press Ctrl+\ to detach.
skiff rename SESSION NAME Set the session name. An empty name clears it.
skiff seen SESSION Clear the session unread state.
skiff wait SESSION... --for STATE [--any] [--timeout SECONDS] Wait for idle, waiting, done, or any-not-working.
skiff events [SESSION...] [--json] [--timeout SECONDS] Print one line for each state change, exit, or removal.
skiff kill SESSION... Stop sessions and remove them.

skiff send checks the session before it types anything. A refusal types nothing.

Exit Meaning
0 Sent.
1 Error, for example no such session.
3 The session is waiting for a person (an approval prompt or a bell).
4 The session is working.
5 A shell, or a program Skiff cannot identify, is in front. --shell sends anyway.
6 The session is done.
7 The session is your own (SKIFF_SESSION).
8 --enter went to a known agent, but no turn started within 5 seconds. The text can be in its input box.
9 A known agent shows text in its input box or its busy line ("esc to interrupt"), or the session took input after the check.

For a known agent, send reads the screen first. It types the text only if no key reached the session since that check, so text a person types at the same time is never mixed with it.

skiff wait exits with code 0 when every session reaches the selected state. It exits with code 1 on timeout, 2 if a session ends or is removed first, and 3 if a session is waiting for a person.

With --for idle, a known agent that is quiet but still shows its busy line does not count as idle. wait reads its screen again every 2 seconds until the line goes.

In a Skiff terminal (SKIFF_SESSION is set), or when standard output is not a terminal, wait stops after 45 seconds when you give no --timeout, and a longer --timeout is cut to 60 seconds. An agent's shell tool stops a call after about a minute.

idle means the terminal has been quiet for a short time. Read the screen with skiff peek before you treat an agent as complete. A program started in a terminal returns to an interactive shell when it exits, so it normally becomes idle. A terminal becomes done only when its shell exits.

Attach and detach

skiff attach SESSION runs a session in this terminal. The session keeps running in skiffd. attach needs a terminal on standard input and standard output. skiff skipper attaches in the same way. See Skiff agent commands.

Press Ctrl+\ to detach. The session continues.

Detach hint

After the session's screen first draws, attach shows one line on the bottom row, in reverse video:

attached to NAME (ID). Ctrl-\ detaches.

ID is the first 8 characters of the session ID. After 3 seconds, attach gets a new screen from skiffd, and the redraw removes the line. The line does not go to the session.

Terminal size

While you are attached, the session takes the size of this terminal. The pane in the app shows the same size. When this terminal changes size, the session changes with it.

attach records the pane's size before it starts. It sets that size again when the attach ends in one of these ways:

  • You press Ctrl+\.
  • attach gets SIGTERM, SIGHUP, SIGINT, or SIGQUIT. For example, you close the terminal or the SSH connection drops.
  • An error stops attach.

attach does not set the size again in these cases:

  • The session exits, or someone removes it.
  • skiffd stops.
  • The last size that skiffd reported is not a size that this attach set. Another client, such as the app, sized the pane, and the pane keeps that size.
  • The size did not change.

Ctrl+C guard

Ctrl+C goes to the session. A second Ctrl+C within 2 seconds of the last one that went through is held back, so a quick double press does not stop the agent. The bottom row then shows this line for 3 seconds:

Ctrl-C held back. Ctrl-\ detaches. Press Ctrl-C again to send it.

The next Ctrl+C goes through, at any time. After that, the count starts again. The guard also applies to Ctrl+C in the kitty keyboard protocol form.

Group commands

A group is a named layout with up to four terminal panes. A terminal can belong to one group at a time.

Command Description
skiff group ls [--json] List groups and their terminal layouts. skiff g ls is an alias.
skiff group new [--name NAME] [SESSION...] Create a group. Without --name, it takes its agents' names. An empty group needs --name, and starts its sessions in --cwd DIR or the current folder.
skiff group new SESSION... --dir row Create a side-by-side group. Use --dir col for a vertical layout.
skiff group add GROUP SESSION [--split SIDE] [--of SESSION] Move a terminal into a group. The default side is right.
skiff group rm GROUP SESSION Remove a terminal from a group. The terminal keeps running.
skiff group rename GROUP NAME Rename a group.
skiff group delete GROUP... Delete groups. Their terminals keep running.

SIDE is left, right, up, or down. --of SESSION selects the pane to split. Adding a terminal to another group removes it from its previous group.

Daemon commands

Command Description
skiff status Show the version and path of this skiff, and the daemon version, protocol, process ID, and socket path. Warn on a PATH or version mismatch.
skiff daemon reload [--binary PATH] Move skiffd onto a new binary. Every session keeps its process, screen, and scrollback. The command then checks that each session survived.
skiff daemon stop Stop skiffd. It saves the workspace and ends every session. The command waits until the socket stops answering.

skiffd ignores SIGTERM and SIGINT from a process in one of its own sessions, and logs the sender's process ID and command line. So a pkill or kill in a pane cannot stop the daemon that runs the pane. From a pane, use skiff daemon stop. A signal from outside the session tree, such as from systemd or a shell outside Skiff, stops the daemon as before.

Skiff agent commands

The Skiff agent is one agent session in a folder of its own. It sends tasks to other sessions and records them in a ledger. See Skiff agent for the folder, the watcher, and the ledger.

Command Description
skiff skipper Attach to the Skiff agent. If it does not run, start it first. Press Ctrl+\ to detach. The agent keeps running.
skiff skipper --no-attach Find or start the Skiff agent, then print its session ID.
skiff skipper --move-to PATH [--empty] [--dry-run] Move the Skiff agent's folder, as Settings › Skiff agent › Folder › Change… does. A relative PATH is resolved against this shell's folder. --empty sets up a new ledger in place of a copy. --dry-run shows the preview and changes nothing. The command asks before it changes the folder, so it needs a terminal. See Skiff agent › The folder.
skiff watch SESSION [--no-notify] Follow the open tasks in the ledger for the Skiff agent in SESSION. Skiff starts it for you.
skiff ledger add --pane PANE --task TEXT [options] Add a task. It prints the row ID, the uid, and the result path.
skiff ledger add --pane PANE --task TEXT --send [--enter] Add the task as pending and send it at once, as ledger send does.
skiff ledger send ID [--enter] [--shell] [TEXT | -] Send a pending or interrupted task to its pane, then mark it sent.
skiff ledger add --plan --task TEXT [options] Add a plan: a row with no pane that holds tasks.
skiff ledger update ID [options] Change a row. Only the given fields change.
skiff ledger ls [--state STATE] [--json] List rows, oldest first. Each plan's tasks show under it.
skiff ledger show ID [--json] Show one row.
skiff ledger rm ID Remove one row. Its result file stays.

skiff skipper reads [skiff_agent] in projects.toml. The Skiff agent must be on in Settings › Skiff agent. The command does these steps:

  1. It connects to skiffd. If no daemon runs, it starts one, as the app does. A lock beside the socket lets only one skiff skipper start the daemon.
  2. It finds the Skiff agent's session. skiffd marks that session, so the app and the CLI find the same one. Two calls at the same time start one session.
  3. If no session runs, it sets up the folder and starts the configured agent with the configured name.
  4. If the session is a bare shell, it starts the agent again. When Settings continues on restore, the agent continues its last conversation. Otherwise it starts a new one.
  5. It starts skiff watch for the session, unless a watcher already runs for this daemon.
  6. It attaches, unless you give --no-attach.

skiff skipper works from an SSH login. The daemon and the watcher that it starts do not belong to the login, so a logout does not stop them. See Skiff agent.

skiff skipper refuses a daemon with an older protocol. Run skiff daemon reload first.

Ledger options

Option Command Meaning
--pane PANE add, update The pane that has the task: an ID, a unique prefix, or a name. The pane must exist.
--task TEXT add, update The task text.
--plan add Make a plan. A plan has no pane.
--parent ID add, update Put the task under this plan. Give its row ID or uid.
--state STATE add, update, ls sent, pending, blocked, or done. For add, a task defaults to pending and a plan to sent. ls also takes interrupted and gone.
--project NAME add, update The default is the project that holds the pane's folder.
--result PATH add, update Where the child writes its result. The default is a path in results/.
--ref TEXT add, update Free text that names the work somewhere else, such as a URL or a ticket key. Skiff stores it and never reads it.
--send add Add the task as pending and send it. It cannot go with --plan or --state.
--enter add --send, send Press Enter after the text.
--shell add --send, send As skiff send --shell.

ID is the row number or the 8-character uid that add prints. Skiff never gives a row number or a uid again, also after rm.

ls and show print interrupted in place of sent or blocked when the task's pane restarted after the send. LIVE stays yes. A restore runs a new shell under the old pane ID, so the task's process is gone.

They print gone in place of an open state when the task's pane no longer exists. LIVE is no.

A plan with tasks shows done when every task is done, and sent while one is open. A plan with no task shows the state it was given. Neither interrupted, gone, nor a plan's state is stored. --json keeps the stored state of a task, with live and interrupted beside it.

Ledger send

skiff ledger send sends only a pending task, or one that shows interrupted, whose pane exists. In other cases it exits with code 1 and sends nothing. With no text, it sends the task text and its result path on one line. - reads the text from standard input.

It runs every skiff send check and exits with the same code. Then it records the outcome:

send exit The task becomes
0 sent, with the pane's current program recorded
8 blocked: the text can be in the input box
any refusal no change

rm refuses a plan that still has tasks. Remove the tasks first.

Configuration commands

Command Description
skiff agents [--json] List known agents and whether each is installed and enabled.
skiff projects [--json] List projects from projects.toml. This command does not need a running daemon.

Theme commands

skiff theme ls [--json] lists available built-in and imported themes. Use the theme ID, such as builtin:ember, in the commands below.

Command Description
skiff theme app set THEME Set the app theme.
skiff theme app clear Clear the app theme. The default applies.
skiff theme project set PROJECT THEME Set a project's theme.
skiff theme project clear PROJECT Clear a project's theme. The app theme applies.
skiff theme session set SESSION THEME Set a terminal theme override. terminal is an alias for session.
skiff theme session clear SESSION Clear a terminal theme override. The project or app theme applies.

A terminal theme overrides its project theme. A project theme overrides the app theme. Theme commands that change app or project settings write projects.toml. Terminal overrides are saved by skiffd. The app applies a new app or project theme when its window gets focus.

Socket selection

Use --socket PATH before any command to select a daemon socket. Without it, skiff uses SKIFF_SOCKET, then the normal skiffd socket path.

The normal socket path is the same from the desktop and from an SSH login. On Linux, Skiff uses $XDG_RUNTIME_DIR. If that variable is not set, it uses /run/user/<uid> when that folder exists. On macOS, it uses the per-user temporary folder that the desktop gets as $TMPDIR. See skiffd.

Examples

Start a Codex agent next to the current terminal:

skiff new --agent codex --group . --split right

Send a prompt and read the result:

skiff send SESSION "Review the current diff." --enter
skiff wait SESSION --for idle --timeout 1800
skiff peek SESSION -n 80

Agent skill

The skiff-cli skill teaches any agent that reads skills to use the CLI. With it, you can ask an agent to "open a codex next to me" or "wait for the reviewer and summarize what it found".

The app does not install the skill. Copy the skiff-cli folder into your agent's skills folder, such as ~/.claude/skills/ for Claude Code or ~/.codex/skills/ for Codex.

⚠️ **GitHub.com Fallback** ⚠️