Skip to content

IPC Socket

pagedmov edited this page Sep 24, 2026 · 5 revisions

In interactive sessions, shed exposes a Unix socket that other processes can use to interact with the shell directly. The path to this socket is held per-instance in the $SHED_SOCK environment variable. Subscribing to the socket gives you a stream of event data, and there are several requests that can be written to the socket to control shed remotely in various ways.

The mode (permissions) of the socket file itself are determined by the $SHED_SOCK_MODE environment variable. The default mode is read/write for only the owner, or 0600.

Protocol

Each request is one message per connection, terminated by receiving EOF from the client.

The general format is: {command}{separator}{arg1}{separator}{arg2}...{separator}{argN}

Where {command} is the name of the command to execute, and {arg1}...{argN} are the arguments to that command.

The "separator" is any sequence of non-alphanumeric ASCII graphic characters appearing after the command word. The same separator is then used for all subsequent arguments. This allows for arbitrary argument content, including whitespace and newlines, without requiring any escaping. Some examples:

msg/status/hello world
msg::status::hello world
msg/\/\/\/status/\/\/\/hello world
msg<>status<>hello world

Requests that take no arguments require no separator.

The shell responds with a result followed by \n. Mutation requests respond with ok. Query requests respond with the requested value.

Connecting

shed ships with builtins that can interact with sockets; the sock builtin can be used to connect to the IPC socket from another terminal or a backgrounded command:

# one-shot form
sock -U "$SHED_SOCK" --oneshot "msg::status::hello world" &

# open connection on a file descriptor
sock -U "$SHED_SOCK" '5'
printf "msg::status::hello world" >&5

Any tool that speaks with Unix sockets (socat, nc -U etc) will work just as well for this purpose.

The one thing to keep in mind is that the shell cannot query its own socket. This is because shed is single threaded, and only acts on socket requests while polling at the prompt. Socket connections must be opened from a separate process. Backgrounding commands with & makes this simple.

Messages

The msg request lets you post a message to the shell. This request works identically to the msg builtin. The request structure looks like this: msg::{kind}::{text}

There are two kinds of messages:

  • Status messages - These appear below the prompt and are short-lived, disappearing after a few seconds.
  • System messages - These appear above the prompt, similar to job status notifications. They persist across redraws.

Examples: msg::system::Build complete msg::status::3 files changed

Queries

The query namespace lets you ask the shell about its current state. Currently, there are three different members of this namespace:

  • cwd - returns the current working directory of the shell.
  • var - lets you get or set shell variable values. See below for argument details
  • status - returns information about the last command. See below for argument details

var Request Structure

The var request can take one of two arguments:

  • get - returns the value of the variable named by the next argument, e.g. query::var::get::PATH
  • set - sets the value of the variable named by the next argument to the value contained in the third argument, e.g. query::var::set::VAR_NAME::some_value
    • The set query can also take flags to give to the resulting variable, and these are to be given after the value argument:
      • export - marks the variable for export
      • local - marks the variable as local
      • readonly - marks the variable as readonly
      • A variable set with a couple flags would look like query::var::set::VAR_NAME::some_value::export::readonly
    • The socket responds with ok on success.

status Request Structure

The status query returns space-separated info about the last command. Specific fields can be requested using arguments:

  • code - returns the exit code of the last command
  • command - returns the command string of the last command
  • runtime - returns the wall time of the last command in milliseconds
  • pid - returns the process ID of the last command
  • pgid - returns the process group ID of the last command

Multiple fields can be requested, and they will be returned in the order specified. For instance:

query::status::code::runtime::command

would return something like

0 475852 vice

Line Editor

The line namespace provides read/write access to the interactive line editor state. This enables external programs to inspect and manipulate the command line asynchronously.

get Requests

The get request allows you to query and read specific line editor fields:

  • line::get::buffer - returns the current content of the editor's buffer
  • line::get::cursor - returns the position of the cursor as a flat index into the buffer content
  • line::get::hint - The current hint/autosuggestion text, if any.
  • line::get::mode - The current edit mode (e.g. normal, insert, emacs, etc)
  • line::get::anchor - The visual selection anchor position, if a selection is active. Provided in the same form as cursor.

set Requests

The set request allows you to arbitrarily set line editor fields to a new value.

  • line::set::buffer::{text} - allows you to replace the buffer contents. The cursor is moved to the end.
  • line::set::cursor::{pos} - Set the cursor position. Has some nuances:
    • The given position can be either a flat index into the buffer, or a row/column position in the form row:col.
    • If the position would within the currently active hint text, the hint is partially accepted up to that point.
  • line::set::hint::{text} - Set a hint override. Override hints have unique behavior compared to history-based hints:
    • Override hints take priority over history-based hints.
    • If the buffer is a prefix of the hint text, the prefix is stripped for display.
    • The override hint persists until the buffer diverges from it, at which point it will switch back to sourcing hints from command history.
  • line::set::anchor::{pos} - Set the visual selection anchor, if there is an active selection. Accepts a flat index or a row/column position in the form row:col. This doesn't make the editor enter visual mode if it's not already in it.
  • line::set::mode::{mode} - Switch the current mode of the editor. The argument is not case sensitive. Possible values are:
    • insert
    • normal
    • command
    • visual
    • replace
    • verbatim
    • emacs
    • remote - Remote mode causes the shell to stop handling keys itself. Every key event is broadcast over the socket as a line>>key_event>> event, and the shell does nothing else. A subscriber can listen in on these key events, and dispatch its own logic via the line::set::* requests. Using this loop, it is even possible to write your own line editor implementation from scratch.

Sending keys

Using line::keys it is possible to post key sequences for the editor to execute. These requests look like line::keys::{value}, where {value} uses the same vim key notation syntax used by the keymap builtin. The events pass through the full editor dispatch chain (completion, history search, mode handling, keymaps), exactly as if the user had typed them. Some examples, assuming the editor is in vim mode:

line::keys::<ESC>5J - Enters normal mode, then joins the next 5 lines to the current line. line::keys::<ESC>3cw - Enters normal mode, then deletes the next 3 words and enters insert mode.

Subscriptions

The subscribe request opens a persistent connection to the shell's event stream. The shell pushes events to all subscribers as they occur. The connection stays open until the client disconnects or the shell exits.

Events are delivered as lines in the format: {namespace}>>{event}>>{data}

Example event stream:

autocmd_event>>pre-cmd
autocmd_event>>post-cmd
line>>buffer>>echo foo
line>>cursor>>8

Key events

Key events are broadcast only while the editor is in remote mode. Events look like line>>key_event>>{key}.

{key} is a single key event in keymap notation, e.g. <C-a>, a, <ESC>, etc. If a key sequence is sent to the editor via line::keys, it will be broadcast as a series of individual key events, one per key. This is what enables the "custom line editor" loop mentioned above; subscribers read key events, then dispatch line::set requests to the socket in response.

Job events

Job events are broadcast when a job completes. job>>begin>>{id} {n} announces the start of the job report, and contains both the job id and the number of job>>child events that follow. Each job>>child event reports a single pipeline stage.

Child events come in the form job>>child>>{pid} {status} {cmd}. {status} is one of:

  • done - exit code 0
  • failed:{n} - exit code n (non-zero)
  • signaled:{signal} - killed by signal {signal}, e.g. signaled:SIGTERM

Example report for a single stage job:

job>>begin>>1 1
job>>child>>12345 done sleep 2

Example report for a pipeline:

job>>begin>>2 3
job>>child>>12346 done cat /etc/hosts
job>>child>>12347 failed:1 grep missing
job>>child>>12348 done wc -l

Broadcast messages

The msg builtin is capable of broadcasting arbitrary messages over the shell's event stream. The messages come in the form msg>>{text}. Each individual line of a message is prefixed with msg>> over the wire, so multi-line messages produce multiple events.

Example:

# in the broadcasting shell
msg -b $'build complete\nstarting ci...'

lands on the event stream as:

msg>>build complete
msg>>starting ci...

Misc requests

  • redraw - Triggers a full prompt redraw. Equivalent to sending SIGUSR1 to the shell, which also triggers a prompt redraw. (that's all for now)

Clone this wiki locally