API

The Outer Shell API is used for registering and querying apps and the files they can open. It runs via socket messages, with a convenience outerctl command line tool wrapper. Use the socket API directly for more speed and efficiency, or use outerctl for convenience.

Where the socket and outerctl live

The socket lives in:

  • macOS:
    • User: DARWIN_USER_TEMP_DIR/outershelld-api
      • See it via ls "$(getconf DARWIN_USER_TEMP_DIR)outershelld-api"
    • Root: /var/run/outershelld-api
  • Linux:
    • User: $XDG_RUNTIME_DIR/outershelld-api
    • Root: /run/outershelld-api

outerctl lives in:

  • macOS:
    • User: $HOME/Library/Application Support/outershell/bin/outerctl
    • Root: /Library/Application Support/outershell/bin/outerctl
  • Linux:
    • User: $XDG_STATE_HOME/outershell/bin/outerctl
    • Root: /var/lib/outershell/bin/outerctl

Common socket message format

Every frame starts with a 4-byte little-endian length prefix:

Prefix bytes 0..3 UInt32 message length, L

Every message begins with:

bytes 0..1: UInt16 messageType

Strings are encoded as offset-based references into the same message:

StringRef32:
bytes 0..3: UInt32 offset to UTF-8 bytes
bytes 4..7: UInt32 UTF-8 byte length

Offsets are relative to byte 0 of the message, not the frame. Empty optional strings are encoded as a zero-length StringRef32.

Lists of strings use a compact offset to a list payload in the variable region:

StringListRef32:
bytes 0..3: UInt32 offset to first StringRef32 entry, or 0 when item count is 0
bytes 4..7: UInt32 item count

List payload:
bytes 0..: item count consecutive StringRef32 entries

The nested StringRef32 entries use the same offset rule as every other reference: offsets are relative to byte 0 of the full message.

Mutating command requests return:

commandResponse, messageType = 100
bytes 2..5:    UInt32 process-style exit status
bytes 6..13:   StringRef32 stdout bytes
bytes 14..21:  StringRef32 stderr bytes

Mutating commands usually return an empty stdout on success and diagnostics in stderr on failure.

List requests return dedicated binary list response messages. outerctl converts those responses into TSV for command-line output.

All list response messages start with:

bytes 2..5:    UInt32 status, 0 for success
bytes 6..13:   StringRef32 error message
bytes 14..17:  UInt32 row count
bytes 18..21:  UInt32 row size in bytes
bytes 22..:    row count consecutive rows of row size bytes, followed by referenced string data

Commands that aren’t just trivial getters/setters

Most of the API is simple getters and setters that update Outer Shell’s private database. Currently there is one that has more complicated logic:

Openers Query

Get an ordered list of openers for a given file. This asks Outer Shell to infer or use a content type, expand conformance relationships, and return all matching openers.

outerctl helper

No outerctl equivalent.

Socket message

Socket request:
fileOpenersQuery, messageType = 25
bytes 2..9:    StringRef32 file path
bytes 10..17:  StringRef32 content type, optional
bytes 18..25:  StringRef32 requester user, optional

Socket response:
fileOpenersResponse, messageType = 106
bytes 2..5:    UInt32 status, 0 for success
bytes 6..13:   StringRef32 error message
bytes 14..17:  UInt32 opener row count
bytes 18..:    opener rows, 44 bytes each

Opener row:
bytes 0..7:    StringRef32 content type
bytes 8..15:   StringRef32 service id
bytes 16..23:  StringRef32 display name
bytes 24..31:  StringRef32 socket path
bytes 32..39:  StringRef32 resolved URL
bytes 40..43:  UInt32 capability flags

Backend getters and setters

Apps are always owned by backends. A backend may have multiple apps. Most backends have associated launchd plists (macOS) or systemd units (Linux) units, but this isn’t required if you want to manage their lifetime in some other way.

In the common case, app frontends and backends will be 1-to-1, and they will use the same display name.

Backend Upsert

outerctl helper

outerctl backend upsert \
  --backend <service-id> \
  --name <display-name> \
  [--systemd-unit <unit>] \
  [--launchd-plist <plist-path>] \
  [--outershell-owns true]

Socket message

Request: backendUpsertRequest, messageType = 10
bytes 2..3:    UInt16 flags
bytes 4..11:   StringRef32 backend service id
bytes 12..19:  StringRef32 display name
bytes 20..27:  StringRef32 launchd/systemd entry

Response: commandResponse, messageType = 100
bytes 2..5:    UInt32 process-style exit status
bytes 6..13:   StringRef32 stdout bytes
bytes 14..21:  StringRef32 stderr bytes

Creates or updates a backend service record. This is the root record that app, log, opener, and content-type rows refer to by service id.

Flags:

0x01 owns platform launchd/systemd entry

The launchd/systemd entry is a systemd unit name on Linux and a launchd plist path on macOS.

Backend Remove

outerctl helper

outerctl backend remove --backend <service-id>

Socket message

Request: backendRemoveRequest, messageType = 11
bytes 2..9: StringRef32 backend service id

Response: commandResponse, messageType = 100
bytes 2..5:    UInt32 process-style exit status
bytes 6..13:   StringRef32 stdout bytes
bytes 14..21:  StringRef32 stderr bytes

Removes one backend service record. Removing a backend also removes the app, log, opener, and content-type rows owned by that backend.

Backend List

outerctl helper

outerctl backend list [--backend <service-id>]

Socket message

Request: backendListRequest, messageType = 12
bytes 2..9: StringRef32 backend service id, empty for all backends

Response: backendListResponse, messageType = 101
Uses the common list response header. Current row size: 36 bytes.

Backend row:
bytes 0..3:    UInt32 flags
bytes 4..11:   StringRef32 service id
bytes 12..19:  StringRef32 display name
bytes 20..27:  StringRef32 systemd unit name
bytes 28..35:  StringRef32 launchd plist path

Lists registered backend records. If --backend is present, the response contains only the record whose service id exactly matches that value; if it is empty, all backend records are returned. outerctl prints these rows as TSV columns service_id, display_name, unit_name, unit_path, and owns_unit.

App getters and setters

Apps are user-facing entry points for a backend. Each app has either a TCP endpoint, a Unix socket endpoint, or no endpoint yet. The endpoint scheme and URL path are stored separately from the endpoint-specific payload.

App Upsert

outerctl helper

outerctl app upsert \
  --backend <service-id> \
  --name <display-name> \
  [--frontend-id <id>] \
  [--scheme http|https] \
  [--host <host>] \
  [--path <url-path>] \
  [--url <legacy-url-or-path>] \
  [--icon-path <path>] \
  [--list <suggested-list>] \
  [--port <port> | --socket-path <socket-path>]

Socket message

Request: appUpsertRequest, messageType = 13
bytes 2..3:    UInt16 endpoint kind
bytes 4..5:    UInt16 scheme
bytes 6..7:    UInt16 endpoint flags, currently 0
bytes 8..9:    UInt16 TCP port, 0 when not a TCP endpoint
bytes 10..17:  StringRef32 backend service id
bytes 18..25:  StringRef32 frontend id
bytes 26..33:  StringRef32 display name
bytes 34..41:  StringRef32 URL path
bytes 42..49:  StringRef32 TCP host
bytes 50..57:  StringRef32 Unix socket path
bytes 58..65:  StringRef32 icon path
bytes 66..73:  StringRef32 frontend list

Response: commandResponse, messageType = 100
bytes 2..5:    UInt32 process-style exit status
bytes 6..13:   StringRef32 stdout bytes
bytes 14..21:  StringRef32 stderr bytes

Creates or updates a user-facing app entry point for a backend. The entry can target a TCP host and port or a Unix socket. --path is the URL path opened inside the endpoint; --url is accepted as command-line convenience and is parsed into scheme, host, port, and path before the socket request is sent. Endpoint kinds are 0 none, 1 TCP, and 2 Unix socket. Schemes are 0 default, 1 HTTP, and 2 HTTPS. Set either TCP fields or Unix socket path, not both. If frontend id is empty, outershelld derives <backend>:main from the backend id. If frontend id is present, it is used exactly as provided.

App Remove

outerctl helper

outerctl app remove \
  --backend <service-id> \
  [--frontend-id <id>] \
  [--port <port> | --socket-path <socket-path>]

Socket message

Request: appRemoveRequest, messageType = 14
bytes 2..5:    UInt32 port
bytes 6..13:   StringRef32 backend service id
bytes 14..21:  StringRef32 frontend id
bytes 22..29:  StringRef32 socket path

Response: commandResponse, messageType = 100
bytes 2..5:    UInt32 process-style exit status
bytes 6..13:   StringRef32 stdout bytes
bytes 14..21:  StringRef32 stderr bytes

Removes one app entry point for a backend. The command identifies the entry by frontend id when provided, otherwise by socket path or port.

App List

outerctl helper

outerctl app list [--backend <service-id>]

Socket message

Request: appListRequest, messageType = 15
bytes 2..9: StringRef32 backend service id, empty for all app rows

Response: appListResponse, messageType = 102
Uses the common list response header. Current row size: 80 bytes.

App row:
bytes 0..1:    UInt16 endpoint kind
bytes 2..3:    UInt16 scheme
bytes 4..5:    UInt16 endpoint flags, currently 0
bytes 6..7:    UInt16 TCP port, 0 when not a TCP endpoint
bytes 8..15:   StringRef32 frontend id
bytes 16..23:  StringRef32 backend service id
bytes 24..31:  StringRef32 display name
bytes 32..39:  StringRef32 TCP host
bytes 40..47:  StringRef32 Unix socket path
bytes 48..55:  StringRef32 URL path
bytes 56..63:  StringRef32 derived URL
bytes 64..71:  StringRef32 icon path
bytes 72..79:  StringRef32 frontend list

Lists app entry points. If --backend is present, the response contains only apps whose backend service id exactly matches that value. outerctl prints these rows as TSV columns frontend_id, service_id, display_name, endpoint_kind, scheme, host, port, socket_path, path, url, icon_path, and list. The url column is derived from the structured endpoint fields for compatibility and display.

Log getters and setters

Log rows tell Outer Shell which files belong to a backend.

Log Add

outerctl helper

outerctl log add --backend <service-id> --path <log-path>

Socket message

Request: logAddRequest, messageType = 16
bytes 2..9:    StringRef32 backend service id
bytes 10..17:  StringRef32 log path

Response: commandResponse, messageType = 100
bytes 2..5:    UInt32 process-style exit status
bytes 6..13:   StringRef32 stdout bytes
bytes 14..21:  StringRef32 stderr bytes

Registers a log file path as belonging to a backend.

Log Remove

outerctl helper

outerctl log remove --backend <service-id> --path <log-path>

Socket message

Request: logRemoveRequest, messageType = 17
bytes 2..9:    StringRef32 backend service id
bytes 10..17:  StringRef32 log path

Response: commandResponse, messageType = 100
bytes 2..5:    UInt32 process-style exit status
bytes 6..13:   StringRef32 stdout bytes
bytes 14..21:  StringRef32 stderr bytes

Removes one registered log path for a backend.

Log List

outerctl helper

outerctl log list [--backend <service-id>]

Socket message

Request: logListRequest, messageType = 18
bytes 2..9: StringRef32 backend service id, empty for all log rows

Response: logListResponse, messageType = 103
Uses the common list response header. Current row size: 16 bytes.

Log row:
bytes 0..7:   StringRef32 log path
bytes 8..15:  StringRef32 backend service id

Lists registered log paths. If --backend is present, the response contains only log rows whose backend service id exactly matches that value. outerctl prints these rows as TSV columns path and service_id.

Content Type setters and getters

Content types are normalized, lowercase dotted identifiers such as public.text or org.example.source. A type can conform to one or more other types, and it can match extensions or MIME type hints.

Content Type Add

outerctl helper

outerctl content-type add \
  --backend <service-id> \
  --content-type <identifier> \
  --name <display-name> \
  [--conforms-to <comma-separated-types>] \
  [--extensions <comma-separated-extensions>] \
  [--mime-types <comma-separated-mime-types>]

Socket message

Request: contentTypeAddRequest, messageType = 19
bytes 2..9:    StringRef32 backend service id
bytes 10..17:  StringRef32 content type
bytes 18..25:  StringRef32 display name
bytes 26..33:  StringListRef32 conforms-to content types
bytes 34..41:  StringListRef32 filename extensions
bytes 42..49:  StringListRef32 MIME types

Response: commandResponse, messageType = 100
bytes 2..5:    UInt32 process-style exit status
bytes 6..13:   StringRef32 stdout bytes
bytes 14..21:  StringRef32 stderr bytes

Creates or updates one backend-owned custom content type definition. These definitions augment the built-in content types used for file matching. backend remove clears the custom definitions owned by that backend.

Content Type Remove

outerctl helper

outerctl content-type remove --backend <service-id> --content-type <identifier>

Socket message

Request: contentTypeRemoveRequest, messageType = 20
bytes 2..9:    StringRef32 backend service id
bytes 10..17:  StringRef32 content type

Response: commandResponse, messageType = 100
bytes 2..5:    UInt32 process-style exit status
bytes 6..13:   StringRef32 stdout bytes
bytes 14..21:  StringRef32 stderr bytes

Removes one backend-owned custom content type definition. Built-in content types are not stored in the registry and cannot be removed.

Content Type List

outerctl helper

outerctl content-type list [--backend <service-id>] [--content-type <identifier>]

Socket message

Request: contentTypeListRequest, messageType = 21
bytes 2..9:    StringRef32 backend service id, empty for all backends and built-ins
bytes 10..17:  StringRef32 content type identifier, empty for all content types

Response: contentTypeListResponse, messageType = 104
Uses the common list response header. Current row size: 48 bytes.

Content-type row:
bytes 0..7:    StringRef32 backend service id, empty for built-ins
bytes 8..15:   StringRef32 identifier
bytes 16..23:  StringRef32 display name
bytes 24..31:  StringListRef32 conforms-to content types
bytes 32..39:  StringListRef32 filename extensions
bytes 40..47:  StringListRef32 MIME types

Lists built-in and custom content type definitions. If --content-type is present, the response contains only the definition whose identifier exactly matches the normalized value. If --backend is present, the response contains only custom definitions owned by that backend; built-ins have no backend owner and are omitted. outerctl prints these rows as TSV columns service_id, identifier, display_name, conforms_to, extensions, and mime_types, joining each list column with commas for display.

Opener setters and getters

Openers connect a backend to a content type. When a file matches the content type, or any type that conforms to it, Outer Shell can present the backend as an app that can open the file.

Opener Upsert

outerctl helper

outerctl opener upsert \
  [--backend <service-id>] \
  [--frontend-id <frontend-id>] \
  --content-type <identifier> \
  [--url-template <template>] \
  [--rank <non-negative-integer>] \
  [--capabilities view,edit]

Socket message

Request: openerUpsertRequest, messageType = 22
bytes 2..5:    UInt32 rank
bytes 6..9:    UInt32 capability flags
bytes 10..17:  StringRef32 backend service id, optional shorthand
bytes 18..25:  StringRef32 frontend id, optional if backend is present
bytes 26..33:  StringRef32 content type
bytes 34..41:  StringRef32 URL template

Response: commandResponse, messageType = 100
bytes 2..5:    UInt32 process-style exit status
bytes 6..13:   StringRef32 stdout bytes
bytes 14..21:  StringRef32 stderr bytes

Creates or updates an opener record for a frontend and content type. The opener record tells Outer Shell that the frontend can view, edit, or otherwise open files that match that content type. If --frontend-id is omitted, outershelld derives <backend>:main from --backend. If both are present, the frontend must belong to that backend. If --capabilities is omitted, outerctl sends both flags.

Capability flags:

0x01 view
0x02 edit

The URL template can include {file}. outershelld percent-encodes the file path and substitutes it into the template when resolving opener URLs.

Opener Remove

outerctl helper

outerctl opener remove \
  [--backend <service-id>] \
  [--frontend-id <frontend-id>] \
  --content-type <identifier>

Socket message

Request: openerRemoveRequest, messageType = 23
bytes 2..9:    StringRef32 backend service id, optional shorthand
bytes 10..17:  StringRef32 frontend id, optional if backend is present
bytes 18..25:  StringRef32 content type

Response: commandResponse, messageType = 100
bytes 2..5:    UInt32 process-style exit status
bytes 6..13:   StringRef32 stdout bytes
bytes 14..21:  StringRef32 stderr bytes

Removes one opener record for the frontend and registered content type.

Opener List

outerctl helper

outerctl opener list \
  [--backend <service-id>] \
  [--frontend-id <frontend-id>] \
  [--content-type <identifier>]

Socket message

Request: openerListRequest, messageType = 24
bytes 2..9:    StringRef32 backend service id, empty for all openers
bytes 10..17:  StringRef32 frontend id, empty for all openers
bytes 18..25:  StringRef32 registered content type, empty for all openers

Response: openerListResponse, messageType = 105
Uses the common list response header. Current row size: 32 bytes.

Opener row:
bytes 0..3:    UInt32 rank
bytes 4..11:   StringRef32 content type
bytes 12..19:  StringRef32 frontend id
bytes 20..27:  StringRef32 URL template
bytes 28..31:  UInt32 capability flags

Lists registered opener records. If --backend is present, only opener rows whose frontend belongs to that backend are returned. If --frontend-id is present, only opener rows for that exact frontend are returned. If --content-type is present, only opener rows whose registered content type exactly matches the normalized value are returned. This is a registry listing filter; it does not expand content type conformance and does not infer a file’s type. outerctl prints these rows as TSV columns content_type, frontend_id, url_template, rank, and capabilities.

Use fileOpenersQuery when you want “which apps can open this file?” behavior with content-type inference, optional explicit content type, conformance expansion, and resolved URLs.