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"
- See it via
- Root: /var/run/outershelld-api
- User: DARWIN_USER_TEMP_DIR/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.