Implementation Contract

May 28, 2026 ยท View on GitHub

Summary

This record captures the current implementation-level contract that cuts across the narrower subsystem records. A clean-room implementation should be able to reconstruct global flags, config shape, command precedence, plugin protocol families, and output ownership from this document plus the specialized records.

Global Flags And Environment

Command-line flags override environment variables. Environment variables override built-in defaults only when the matching flag was not set.

FlagShortTypeEnvDefaultNotes
--rsh-header-Hrepeat Name: ValueRSH_HEADERemptyEnv is comma-separated, supports \, for literal commas, and is prepended.
--rsh-query-qrepeat key=valueRSH_QUERYemptyEnv is comma-separated, supports \, for literal commas, and is prepended.
--rsh-server-sstringemptyOverrides scheme/host; path prefixes request path.
--rsh-output-format-ostringRSH_OUTPUT_FORMATautoFormats the rendered body/value selected by --rsh-print=b; lines for scalar line output; no raw format.
--rsh-printstringRSH_PRINTautoChooses stdout parts: H request headers, B request body, h response status/headers, b rendered body, p pretty, c color. auto is hbpc on a terminal, body bytes for redirected unfiltered responses with no explicit output transform, and bp for filters, metadata shortcuts, and formatted/collected output.
--rsh-silent-SboolfalseSuppress output.
--rsh-columnsstringemptyTable columns.
--rsh-sort-bystringemptyTable sort column.
--rsh-content-type-cstringemptyEmpty means JSON default for bodies unless operation media type applies.
--rsh-filter-fstringRSH_FILTERemptyShorthand/jq auto-detected.
--rsh-filter-langstringautoshorthand or jq.
--rsh-headersboolfalseShorthand for -f headers.
--rsh-statusboolfalseShorthand for -f status.
--rsh-verbose-vcount0-v headers, -vv TLS details.
--rsh-insecureboolRSH_INSECUREfalseWarns, then disables TLS verification.
--rsh-client-certstringemptymTLS cert.
--rsh-client-keystringemptymTLS key.
--rsh-tls-signerstringemptyTLS signer plugin name/path.
--rsh-tls-signer-paramrepeat key=valueemptyPlugin params.
--rsh-ca-certstringemptyExtra trusted CA.
--rsh-tls-min-versionstringTLS1.2TLS1.2 or TLS1.3.
--rsh-ignore-status-codeboolfalseSuppresses status-derived non-zero exit.
--rsh-timeout-tdurationRSH_TIMEOUTnoneBounded request lifetime; for streams, header wait timeout before switching to stream cancellation rules.
--rsh-profile-pstringRSH_PROFILEdefaultActive API profile.
--rsh-authstringRSH_AUTHemptyGenerated-operation credential alternative override, e.g. UserOAuth+PartnerKey.
--rsh-no-cacheboolRSH_NO_CACHEfalseBypass reads and writes.
--rsh-no-browserboolfalseOAuth auth-code browser suppression.
--rsh-retryintRSH_RETRY20 disables retries. Internally, -1 may be used as the unresolved-default sentinel.
--rsh-retry-unsafeboolRSH_RETRY_UNSAFEfalseReplay POST/PUT/PATCH/DELETE on retryable failures.
--rsh-retry-max-waitdurationRSH_RETRY_MAX_WAIT5mCap server-provided retry waits.
--rsh-no-paginateboolfalseDisable automatic pagination.
--rsh-collectboolfalseCollect pages before filtering.
--rsh-max-pagesint250 means unlimited.
--rsh-max-itemsint0Paginated item or streamed event/line cap; 0 means unlimited.
--rsh-max-body-sizeint MiBformatter defaultBounded response cap.
--rsh-configstring pathRSH_CONFIGdefault config pathSelects one complete config file. Missing explicit files error.

Config file location precedence is --rsh-config, RSH_CONFIG, RSH_CONFIG_DIR/restish.json, XDG_CONFIG_HOME/restish/restish.json, then the default config path. The default config path is ~/.config/restish/restish.json on macOS, Linux, and other Unix-like systems, and %APPDATA%\restish\restish.json on Windows. --rsh-config and RSH_CONFIG are source-of-truth selectors: Restish does not merge them with the default config. Token and external-tool approval sidecars live next to the selected explicit config. HTTP response and spec caches stay under the cache root, with a namespace derived from the explicit config path. When no explicit config file is selected, Restish may discover .restish.json from the current directory or an ancestor. A discovered project config is used only after trust is recorded with restish config trust or an interactive TTY prompt. Trust is stored outside the repository in user Restish state and is keyed by canonical project config path plus content hash, so a changed project config must be trusted again.

Trusted project config is layered over the selected/global config for reads. The first project layer honors only apis and theme: project APIs shadow global APIs by name without deep-merging individual API definitions, and project theme entries override global theme entries by key. Other top-level keys such as auth_profiles, plugins, cache, and theme_source are invalid in auto-discovered project config and produce a clear diagnostic when the project config is otherwise trusted. Project config may use normal repository file permissions, but it must not contain inline secret values; secret-bearing auth params must be omitted or use env:NAME references. Non-interactive runs do not prompt for trust; when they discover an untrusted project config, they continue with the selected/global config and print a concise stderr warning naming the file and restish config trust. Auto-discovered project config is read-only for normal mutation commands; writes continue to target the selected/global config unless --rsh-config or RSH_CONFIG explicitly selects the project file.

Project-local machine state is not written into the repository. OAuth/token caches, HTTP response caches, and spec/generated-command caches for trusted project APIs live in user Restish state/cache roots under a namespace derived from the trusted project config path and content hash.

Windows ACL inspection is not implemented for the first v2 release. Existing config and token-cache files on Windows therefore report permission diagnostics as unknown, not ok. Startup remains non-blocking on Windows because Restish cannot yet prove the ACL is insecure, but doctor must not imply that a secret-bearing file was checked successfully. A future hardening pass may add native ACL inspection and turn broad read access into the same warning/failure path used for Unix mode bits.

Config Schema

Top-level config is JSONC with strict decoded fields:

PathTypeMeaning
apismapAPI registrations keyed by short name.
auth_profilesmapShared auth configs referenced by profile or credential auth_ref.
cache.max_sizestringDisk cache size such as 100MB.
thememapAuto-output and terminal transcript style entries.
pluginsmapRaw per-plugin JSON config.

HTTP response-cache entries are written with temp-file plus rename semantics, and LRU eviction is guarded by an advisory sibling lock so separate Restish processes can share the same cache directory.

API fields:

FieldTypeMeaning
base_urlstringDefault API URL prefix.
spec_urlstringExplicit spec URL.
spec_filesarrayOrdered local/remote specs to merge.
allow_cross_origin_specboolPermit safe cross-origin Link spec discovery.
operation_basestringAbsolute path prefix resolved against base_url for generated operations.
command_layoutstringflat or tags; empty means flat.
server_variablesmapExplicit OpenAPI server URL variable values used for generated operation paths.
retry_max_waitstring durationAPI-local cap for Retry-After/X-Retry-In when no flag/env override is set.
preserve_header_caseboolOpt-in HTTP/1.x compatibility mode for broken servers that treat request header names as case-sensitive.
pagination.items_pathstringItem extraction path.
pagination.next_pathstringNext URL extraction path.
pagination.page_paramstringQuery parameter to increment for APIs without next links.
profilesmapProfile configs keyed by name.

Profile fields are base_url, headers, query, tls_signer, tls_signer_params, server_variables, auth, auth_ref, and credentials. Profile server variables override API-level server variables for command generation. Inline auth and auth_ref are mutually exclusive.

Credential fields under profiles.<name>.credentials.<id> are auth, auth_ref, and satisfies. Credential inline auth and auth_ref are also mutually exclusive. Auth fields are type plus string params.

Config files are written private. On Unix-like systems, group/world-readable config permissions are fatal because profiles and auth parameters can contain secrets. Users should repair them with chmod 600.

config set <patch> [patch...] applies shorthand patch expressions to the whole config object. api set <name> <patch> [patch...] applies the same language rooted at apis.<name>. Both commands reject the unreleased pre-v2 key value form. Shorthand patch supports recursive object merge, scalar replacement, array set/append/insert, undefined deletion, and ^ swap/move operations. API-scoped patches cannot escape the selected API root.

Command-line config patching validates the final patched object in layers: Huma-backed structural validation, typed config decode, config.Validate semantic validation, then CLI/runtime checks such as registered auth handlers and TLS signer plugins. Writes are atomic and are skipped entirely when any validation layer fails.

Command Surface And Precedence

Public built-ins own: get, head, options, post, put, patch, delete, api, cache, cert, config, doctor, edit, help, links, plugin, shell, and version.

The public completion generator is shell completion <shell>. A top-level completion command may exist as a hidden compatibility alias, but design 037 owns the published command surface and user-facing docs should not advertise the alias. There is no public flags command in v2; global flag discovery is through command help and --help-all.

API short names must not collide with public built-ins or hidden compatibility commands. Removed pre-release command names are not held in reserve unless an actual hidden command remains. In particular, completion is reserved because the hidden alias exists, while content-types and flags are available as API short names.

api auth logout accepts either one API argument or --auth-profile <name>. The API argument is required unless --auth-profile is supplied. --all-profiles applies only to API-scoped cache clearing.

Generated API commands are registered under API short names when cached spec metadata is available. Short-name generic fallback commands are registered for APIs without generated command groups. Plugin commands are top-level commands but must not collide with built-ins, generated APIs, configured API names, or other plugin commands.

Bare URLs and registered API short names at root infer the generic request method from body presence: no body sends GET; shorthand or stdin body input sends POST. Generated command startup uses a fast path that skips value-taking global flags but does not consume bool/count flags such as -v or --rsh-insecure.

Plugin Wire Protocol Families

All plugin messages use CBOR. The stable message families are:

FamilyDirectionPurpose
Manifest/startup flagshost -> plugin processDiscover hooks, loaders, formatters, and commands.
Hook messageshost <-> short-lived pluginAuth, request middleware, response middleware, loader, formatter hooks.
Command messageshost <-> long-lived pluginInit, stdin, HTTP delegation, formatting delegation, stderr, done.
Config messagesplugin -> hostRead/list config and prompt/confirm where allowed.
Formatter messageshost <-> formatter pluginNormalize host response and stream or document formatting.
TLS signer messageshost <-> signer pluginCertificate discovery and signing for mTLS.

Protocol changes that alter message meaning require a plugin API version bump or explicit compatibility handling.

Output Ownership

Design 009 owns the normalized response schema and bounded response formatting contract. Design 028 owns the planner that decides document vs record framing across pagination, streaming, filters, and explicit formats.

ConcernOwner
Decode body and preserve raw bytes009
Normalize status, headers, links, body009
Select default formatter for TTY/non-TTY009 and 028
Decide document vs record execution028
Paginated collection vs streaming behavior028 and 011
SSE/NDJSON event rendering028 and 012

Local Mutation Inventory

Commands that mutate local state must name what they changed and, for config writes, print Wrote config: <path> in human output. This inventory is the release contract for which commands touch disk and what they intentionally preserve.

CommandLocal state touchedSafety contract
api connectrestish.json, spec cache, generated-operation cacheWithout --replace, refresh API-level metadata and cache state while preserving existing profiles and credentials. With --replace, regenerate replaceable profiles while preserving values that cannot be rediscovered safely.
api syncspec cache, generated-operation cache, sometimes restish.json API metadataRefresh discovered API metadata and generated operations without replacing credential-containing profiles.
api setone API section in restish.jsonPatch only the requested API fields and preserve comments/formatting when possible.
api removerestish.json, API-owned HTTP cache namespaces, API-scoped auth token cache entriesRemove the API and clean API-owned local state. Shared auth-profile tokens are removed only when no remaining API references that shared profile.
api auth add / api auth removeprofile credential entries in restish.jsonAdd or remove only the named credential binding. Empty additions are allowed as an easy escape hatch before filling details with api set.
api auth logoutauth token cacheClear cached OAuth/auth tokens only; do not mutate config.
config set / config editrestish.jsonValidate runtime config before keeping changes. Preserve comments/formatting for targeted edits when possible.
config theme set / config theme resettheme fields in restish.jsonRemote theme sources require trust confirmation unless --yes is explicit. Reset removes only theme override fields.
plugin install / plugin removeplugin directory and plugin manifest cacheInstall only after manifest inspection and trust confirmation unless --yes is explicit. Remove only installed plugin files selected by name/path.
shell setup / shell completion installshell rc files and completion filesSupport dry-run, explicit confirmation, duplicate detection, and atomic writes where possible.
cache clearHTTP response cacheClear all HTTP entries, one API/namespace, or direct URL requests. Do not clear spec cache or auth token cache.

Intentional v2 Breaks

The v1 habit of whole-config editing through api edit is retired. v2 config is edited through restish.json, api connect, api set, config set, and config edit. Legacy x-cli-config.prompt is not retired: api connect <name> <url> prompts for those values while writing local config, then normal requests use the saved config without extension-driven prompting.

The restish-mcp --http flag is not part of v2; MCP currently uses stdio as a command plugin.

The v2 command surface is intentionally not preserving pre-release aliases such as api show, api edit, api clear-auth-cache, api content-types, a top-level setup command, or a direct mcp <api...> service invocation. Design 037 owns the exact accepted command tree and v1-to-v2 command move table.