Surfaces

Open and read Changes, Settings, pull requests, a worktree's tabs and a file's text from a script.

sirioctl surface drives the views Sirio draws itself: the Changes tab, Settings, the right panel’s pull and merge requests, a request’s own tab, the list of a worktree’s tabs and the file in the active tab. Most commands answer with a report of what the view shows once they have acted.

What a command acts on

Most of these commands work on the selected worktree, the one the sidebar highlights, and on its active tab, the tab you or a script selected last. surface change-request needs a change request in the active tab for every verb but open, surface file read needs a file there, and surface changes read needs the Changes tab. Otherwise each one stops with a message such as the active tab is not a change request. surface change-requests works on the right panel’s view instead, and surface settings on the Settings screen.

A script that moves between worktrees selects one first with select-workspace. See Workspaces and panes.

Output

Without --json, a command prints a fixed set of fields on one line, with a tab between each. With --json it prints the whole report as a JSON array that holds one object, and every value in it is a string. A list inside a report, such as the files of a Changes section, arrives as JSON text inside that string, so decode it a second time, with fromjson in jq.

Use --json for surface tabs, which prints an empty line without it, and for surface file read, which prints only its state and error. surface change-request prints its state, row count, label, title and inner tab, and --json adds the rest.

The views load in the background, and a command answers at once with what its view holds at that moment. After an open or a show, read the view again until its state settles.

Changes

bash
sirioctl surface changes open [--worktree w]
sirioctl surface changes read
sirioctl surface changes stage <path> [--worktree w]
sirioctl surface changes unstage <path> [--worktree w]
sirioctl surface changes discard <path> [--worktree w]
sirioctl surface changes stage-all [--worktree w]
sirioctl surface changes discard-all [--worktree w]

open opens the worktree’s Changes tab in the right pane, or switches to it when it is already open, and reports it. Give --worktree a worktree’s id or path and open selects that worktree first. read reports the Changes tab while it is the active tab.

The other five run git in the worktree themselves and need no tab. The path is the one git status prints, relative to the worktree. stage runs git add -A on the file and stage-all on everything. stage refuses a file in conflict, and stage-all refuses to run while any file is in conflict. discard and discard-all run git restore --worktree, which puts back what the index holds and leaves untracked files alone. They run at once, where the buttons in the app confirm a discard first. With --worktree they take a worktree that list-workspaces --json shows as mounted, which means the selected one or one you selected since Sirio started and kept open. Any other answers unknown worktree, so run select-workspace first.

bash
sirioctl surface changes stage src/parser.rs
sirioctl surface changes read --json | jq -r '.[0] | .stagedCount, .changedCount, .untrackedCount'
sirioctl surface changes read --json | jq -r '.[0].staged | fromjson | .[].path'

The report carries surfaceId (changes), tabId, worktree, loading, ready, error, and the counts stagedCount, changedCount and untrackedCount. staged, changed and untracked list each section’s files with path, additions, deletions and binary, and sections repeats the three with their names and counts.

Settings

bash
sirioctl surface settings open [--section s]
sirioctl surface settings select <section>
sirioctl surface settings read

open shows Settings, on the section you name. select switches section, and opens Settings first when it is off screen. read fails unless Settings is on screen. The sections are ai-providers, agents, language-servers, git-hosting, general, permissions and appearance. Case makes no difference, and a space or an underscore counts as a hyphen, so "Git Hosting" works too.

bash
sirioctl surface settings open --section git-hosting
sirioctl surface settings read --json | jq -r '.[0] | .sectionId, .controlSocketEnabled, .socketPath'

The report names the section twice, as section with its title and as sectionId. availableSections lists every section, and providers lists the AI providers Sirio looked for, each with id, name, available, status and path. The settings themselves come both inside values and as fields of their own, among them theme, interfaceFontSize, terminalFontSize, controlSocketEnabled, socketPath and resumeAgentSessions.

Pull and merge requests

bash
sirioctl surface change-requests show|read
sirioctl surface change-requests filter <mine|to-review|all-open|closed>
sirioctl surface change-requests token --host h --forge github|gitlab --token t

show opens the right panel on its pull request view for the selected worktree, the view behind the panel’s last icon, and the view starts asking the forge. read and filter need that view to exist, so run show first, or open the view yourself. Before then they stop with the change request view has not been shown. The four filters are Mine, To review, All open and Closed & merged. See Pull and merge requests.

token checks a personal access token against the forge. When the forge accepts it, Sirio keeps it where Settings → Git Hosting keeps tokens, connects the view again, and prints the account the token belongs to. Your shell history keeps whatever you type, so hand the token over in a variable.

bash
sirioctl surface change-requests token --host github.example.com --forge github --token "$GHE_TOKEN"
sirioctl surface change-requests filter all-open --json
json
[{"card":"#578","error":"","filter":"all-open","host":"github.com","labels":"#581,#578,#574","rows":"3","state":"ready","toReview":"1"}]

state reads idle, connecting, unavailable, no-remote, unknown-forge, not-connected, loading, error or ready. labels lists the rows’ numbers, #578 on GitHub and !231 on GitLab. card holds the request on the worktree’s own branch, or none when the branch has none, loading, error, or - when the card hides. toReview counts the requests that wait on your review.

A request’s tab

bash
sirioctl surface change-request open <number>|tab <name>|read
sirioctl surface change-request reveal <path> [line]|open-file <path> [line]|open-commit <sha>

open opens request <number> of the view’s project in a tab in the right pane, or switches to its tab when it has one, and makes that tab active. Any number works, whether the list shows it or not, once the view has connected to the forge, so run change-requests show first and wait for ready. The other verbs act on the request in the active tab:

  • tab switches its inner tab to conversation, commits, checks or files.
  • read reports the tab.
  • reveal switches to Files and opens the diff at that file, at the line when you give one. While the commits are still on their way, it waits for the diff.
  • open-file does what the ↗ on a diff row does: it opens your own file when the worktree sits at the request’s head, and a read-only snapshot otherwise. It fails until the diff is ready.
  • open-commit opens a commit from the Commits list, so switch to tab commits first. When your repository lacks the request’s commits, Sirio fetches them, then opens the commit in a read-only Changes tab. When the fetch fails, commit_error says why.
bash
sirioctl surface change-request read --json | jq '.[0]'
json
{
  "commit_error": "",
  "diff_files": "2",
  "diff_summary": "src/lexer.rs +4 -1|src/parser.rs +12 -3",
  "files_error": "",
  "files_focus": "src/parser.rs",
  "files_mode": "diff",
  "head": "a1b2c3d",
  "inner": "files",
  "label": "#578",
  "list_rows": "2",
  "notice": "",
  "rows": "2",
  "state": "loaded",
  "title": "Fix the parser"
}

state reads loading, loaded, error or unreachable, and rows counts the entries of the inner tab on screen. files_mode follows the diff through idle, fetching, diff, error and no-revisions, and head names the head commit it shows. diff_summary gives one path +added -removed per file, with a | between them. files_focus names the file last revealed, notice carries the “Updated to head” line, and list_rows counts the files in the forge’s own list.

Tabs

bash
sirioctl surface tabs read
sirioctl surface tabs select <N>|close <N>

read lists every tab of the selected worktree, the left half’s and the right half’s together, as tab.1, tab.2 and so on. Each entry reads kind|snapshot|title. The kind is terminal, chat, browser, file, diff (Changes and commits), settings (a project’s settings) or change_request, and snapshot says yes for a read-only snapshot of a request’s file. count holds the number of tabs and active the position of the active one.

select and close take a position in that list. select makes the tab active, and shows the right half when the tab lives there and you had hidden it. close works like the tab’s close button: a closed chat moves to Closed in the sidebar’s Sessions tab, and a live terminal, a chat that is still answering or a file with unsaved edits asks in the window first. Both answer with the list as it stands afterwards.

bash
sirioctl surface tabs read --json | jq '.[0]'
json
{
  "active": "3",
  "count": "4",
  "tab.1": "terminal|no|Terminal",
  "tab.2": "chat|no|Fix the parser",
  "tab.3": "change_request|no|#578 Fix the parser",
  "tab.4": "file|yes|parser.rs @ #578"
}

tab select counts only the tabs of the half that has focus. surface tabs select reaches a tab in either half.

A file’s text

bash
sirioctl surface file read

file read reports the file in the active tab: path, read_only, origin, state (loading, loaded or error), error and content. The content is the text the editor holds, unsaved edits included. Sirio doubles each backslash and writes each newline as \n, so the whole file fits on one line. For a snapshot, read_only says true, origin names the request and the commit, such as #578 at a1b2c3d, and path points under sirio-snapshot/ instead of at a file on disk.

bash
sirioctl surface file read --json | jq -r '.[0] | .origin, .state'

A worked script

This one opens pull request 578, reveals a line of its diff, reads that file as the request has it, and closes the file’s tab again.

bash
#!/bin/sh
set -e

# wait_for <field> <value> <sirioctl arguments...>
wait_for() {
  field=$1 want=$2
  shift 2
  for _ in $(seq 60); do
    got=$(sirioctl "$@" --json 2>/dev/null | jq -r ".[0].$field") || got=
    [ "$got" = "$want" ] && return 0
    sleep 1
  done
  echo "gave up waiting for $field=$want" >&2
  return 1
}

sirioctl surface change-requests show
wait_for state ready surface change-requests read

sirioctl surface change-request open 578
sirioctl surface change-request tab files
wait_for files_mode diff surface change-request read

sirioctl surface change-request reveal src/parser.rs 42
sirioctl surface change-request open-file src/parser.rs 42
wait_for state loaded surface file read
sirioctl surface file read --json | jq -r '.[0].content'

sirioctl surface tabs close "$(sirioctl surface tabs read --json | jq -r '.[0].active')"

An older sirioctl

Agent hooks call the sirioctl in ~/.local/share/Sirio/bin, or in $XDG_DATA_HOME/Sirio/bin when you set that variable. Sirio compares that copy with the sirioctl it ships at every launch and refreshes it when they differ, so the copy matches the running Sirio. On your PATH says where Sirio links it.

Releases before 0.33 kept the copy they found without comparing it. After an upgrade from one of them, that copy can be older than the app, and the commands on this page that arrived in 0.27 and 0.28 fail with unknown surface action. Start a newer Sirio once and it replaces the copy. ~/.local/share/Sirio/bin/sirioctl version prints a warning when the copy does not match the running Sirio.