Docs

Command line

sightline-cli runs every Sightline tool from the terminal and from scripts, and prints the result as JSON.

Setup

The CLI ships inside the app, so it is always the version of the Sightline you have:

/Applications/Sightline.app/Contents/MacOS/sightline-cli

Make it short with an alias in ~/.zshrc, or a link in a folder on your PATH. The CLI follows the link back to the app.

alias sightline-cli='/Applications/Sightline.app/Contents/MacOS/sightline-cli'

# or
mkdir -p ~/bin && ln -s "/Applications/Sightline.app/Contents/MacOS/sightline-cli" ~/bin/sightline-cli

The same rules as for agents apply: Sightline Pro, Screen Recording permission and Allow AI agents to use Sightline in Settings > AI Agents. If Sightline isn't running, the CLI starts it in the background and waits up to 8 seconds.

The first call

The first call opens Sightline's approval dialog. A command you run yourself is shown as coming from your Terminal, such as zsh. Allow This Session lasts while that shell runs; Always Allow keeps the grant, and also allows anything else you run from that shell, so prefer the session grant or tick Limit to ... for one kind of action.

Commands

CommandWhat it does
sightline-cli <tool> --json '{...}'Call one tool with its arguments as one JSON object and print the result. Leave out --json for a tool without required arguments.
sightline-cli toolsList every tool, one per line, with its description.
sightline-cli mcpRun the MCP server over stdio, for AI agents. See MCP server.
sightline-cli helpShow the usage. -h and --help work too.

The tool names and arguments are exactly those of the tool reference. One difference: the CLI sets include_image to false unless you pass it, so no base64 image floods the terminal.

Output and exit codes

On success the CLI prints the tool's MCP result, pretty printed, and exits with 0. The tool's own JSON is a string inside content[0].text:

{
  "content": [
    {
      "text": "{\"height\":1640,\"path\":\"/Users/you/Pictures/Screenshots/Agent/Sightline 2026-10-24 14.03.11.png\",\"scale\":2,\"width\":2560}",
      "type": "text"
    }
  ],
  "isError": false
}

On an error it prints the message to stderr and exits with 1, for example no window matches. An unknown tool (Unknown tool: ...) and a --json value that isn't a JSON object also exit with 1.

A jq helper

This shell function calls a tool and prints only its JSON, ready for jq. Put it in ~/.zshrc:

sl() {
  sightline-cli "$@" | jq -e '.content[0].text | fromjson'
}
sl capture_window --json '{"app": "Safari", "name": "safari-now"}' | jq -r .path
sl list_windows | jq -r '.[] | "\(.id)\t\(.app)\t\(.title)"'

Recipes

Copy the text of a window

Reads a window with on-device text recognition and puts the text on the clipboard.

sl capture_text --json '{"window": {"app": "Preview"}}' | jq -r .text | pbcopy

Capture every window of an app

Lists the windows, keeps the ones of one app and captures each by id, named after its title.

sl list_windows | jq -c '.[] | select(.app == "Xcode")' | while read -r w; do
  id=$(jq -r .id <<<"$w")
  name=$(jq -r '.title | gsub("[^A-Za-z0-9]+"; "-")' <<<"$w")
  sl capture_window --json "$(jq -nc --argjson id "$id" --arg n "xcode-$name" '{id: $id, name: $n}')" | jq -r .path
done

Blur every match of a word

Captures a window, finds every line with a token and blurs those boxes. jq builds the annotations from the boxes.

img=$(sl capture_window --json '{"app": "Terminal", "name": "terminal-safe"}' | jq -r .path)
marks=$(sl find_text --json "$(jq -nc --arg p "$img" '{text: "token", path: $p}')" \
  | jq -c '[.matches[] | {type: "blur", blur_style: "pixelate", x, y, width, height}]')
sl annotate --json "$(jq -nc --arg p "$img" --argjson m "$marks" '{path: $p, annotations: $m}')" | jq -r .path

Record a demo and make a GIF

Records the frontmost Simulator window for 12 seconds, then writes a 960 pixel GIF that zooms in on every click.

mp4=$(sl record_screen --json '{"window": {"app": "Simulator"}, "seconds": 12}' | jq -r .path)
sl annotate --json "$(jq -nc --arg p "$mp4" '{path: $p, annotations: [], zoom_on_clicks: true,
  pointer: {click_highlight: "ripple"}, export: {format: "gif", width: 960}}')" | jq -r .path

Lists the five newest starred screenshots whose text contains both words, with the date and the matching snippet.

sl search_captures --json '{"query": "invoice total", "favorites_only": true, "limit": 5}' \
  | jq -r '.results[] | "\(.date)  \(.path)\n    \(.snippet // "")"'

Wait until the app matches the design

Compares the Simulator with a design every 5 seconds while you work, and prints the differences until there are none.

cp ~/Designs/signup.png ~/Pictures/Screenshots/Agent/signup-design.png
while :; do
  n=$(sl compare_with_design --json '{"design": "/Users/you/Pictures/Screenshots/Agent/signup-design.png", "window": {"app": "Simulator"}}' \
    | tee /tmp/diff.json | jq '.differences | length')
  [ "$n" -eq 0 ] && { echo "matches the design"; break; }
  jq -r '.differences[] | "#\(.number) \(.hint) at \(.x),\(.y)"' /tmp/diff.json
  sleep 5
done

Shrink and convert a recording

Makes a 720p MOV copy of your newest recording for a slide deck. The original stays as it is.

mp4=$(sl last_recording | jq -r .path)
sl annotate --json "$(jq -nc --arg p "$mp4" '{path: $p, annotations: [], export: {format: "mov", size: "720p", quality: "medium"}}')" | jq -r .path

These use the sl helper from above and set -o pipefail is a good idea in scripts, so a failing tool stops the script. Paths like /Users/you/... stand for your own home folder.

On a Mac build runner

Sightline is a desktop app, so it runs on a self-hosted Mac runner with a logged-in user session, not on a hosted cloud runner. Set that Mac up once by hand:

  1. Sign in to the Mac App Store, install Sightline and start Sightline Pro.
  2. Allow Screen Recording, turn on Allow AI agents to use Sightline, and keep Launch at login on in Settings > General.
  3. Run one tool from the runner's shell and answer the approval dialog. Only choose Always Allow on a machine that runs nothing but your builds.

Then a job can capture the app it just built and fail when it drifts from the design. This GitHub Actions job runs on a runner labeled self-hosted and macOS:

ui-check:
  runs-on: [self-hosted, macOS]
  steps:
    - uses: actions/checkout@v4
    - name: Build and launch
      run: |
        xcodebuild -scheme MyApp -destination 'platform=iOS Simulator,name=iPhone 17' build
        xcrun simctl launch booted com.example.myapp
    - name: Compare with the design
      shell: bash
      run: |
        set -euo pipefail
        sl() { "/Applications/Sightline.app/Contents/MacOS/sightline-cli" "$@" | jq -e '.content[0].text | fromjson'; }
        agent=~/Pictures/Screenshots/Agent
        cp design/signup.png "$agent/signup-design.png"
        sl compare_with_design --json "{\"design\": \"$agent/signup-design.png\", \"window\": {\"app\": \"Simulator\"}}" > diff.json
        cp "$(jq -r .path diff.json)" marked.png
        jq -e '.differences | length == 0' diff.json
    - uses: actions/upload-artifact@v4
      if: always()
      with:
        name: design-check
        path: |
          diff.json
          marked.png

The marked capture numbers each difference, so a failed run shows what moved. Copy results out of the Agent folder, because Sightline moves agent captures to the Trash after 7 days by default.