zidane/ ipc/ remote
The client side of remote zidane hosts, over WebSocket with saved servers and settings sync.
import { … } from 'zidane/ipc/remote'src/ipc/remote.ts
Use zidane/ipc/remote to connect to a zidane-server from Node 22 or later, or Bun. connectWsClient returns the same typed client as the unix-socket path, and the saved-hosts helpers keep a registry of the servers you connect to. It sits outside zidane/ipc, so an app loads none of it until remote hosts are on. Server clients covers errors, reconnection and settings sync.
import { connectWsClient } from 'zidane/ipc/remote'
const client = await connectWsClient({
url: 'wss://zidane.example.com/',
token: process.env.ZIDANE_HOST_TOKEN,
})
const sessions = await client.request('sessions.list', {})
console.log(sessions.length)
client.close()Functions#
classifyWsFailureFunction#
A refused token stays refused until the user changes something: no retry loop. Another protocol version stays until one side is upgraded: a slow re-check.
function classifyWsFailure(err: unknown): HostFailureKindParameters
| Name | Type | Description |
|---|---|---|
| err | unknown | The error a connect attempt failed with. |
Returns
HostFailureKindauth for a refused token, identity for another host answering, protocol for a version mismatch, retry otherwise.
connectWsClientFunction#
Connects to a remote zidane server over WebSocket and completes the ipc.hello handshake.
A refused upgrade rejects with HostAuthError for a 401 or 403, and HostBusyError for a 429. A hello from another host than expectHostId rejects with HostIdentityError. A version mismatch rejects with IpcVersionMismatchError and leaves the server running.
function connectWsClient(options: WsClientOptions): Promise<IpcClient>Parameters
| Name | Type | Description |
|---|---|---|
| options | WsClientOptions | Server URL, token and connection settings. |
Returns
Promise<IpcClient>A typed client.
Example
import { connectWsClient } from 'zidane/ipc/remote'
const client = await connectWsClient({
url: 'wss://zidane.example.com/',
token: process.env.ZIDANE_HOST_TOKEN,
})
const sessions = await client.request('sessions.list', {})
console.log(sessions.length)
client.close()createSyncEngineFunction#
Creates a sync engine for this computer. Starts file watchers once a server is set to keep in sync.
function createSyncEngine(options: SyncEngineOptions): SyncEngineParameters
| Name | Type | Description |
|---|---|---|
| options | SyncEngineOptions | Paths, providers and timing for the engine. |
Returns
createWsHostRegistryFunction#
The registry in dataDir, connecting over WebSocket.
function createWsHostRegistry(options: { dataDir: string }): HostRegistryParameters
| Name | Type | Description |
|---|---|---|
| options | { dataDir: string } | Where the registry keeps its data. |
Returns
HostRegistryThe host registry.
hostCaBundleFunction#
The runtime's roots, the OS store's and NODE_EXTRA_CA_CERTS's, read once.
null when the OS store adds nothing (or can't be read): the runtime's
default then applies as it is.
function hostCaBundle(): string[] | nullReturns
string[] | nullkeepHostConnectedFunction#
Connects a host and keeps it connected: reconnects with backoff when the connection drops, unless the failure is classified as final.
function keepHostConnected(options: HostConnectionLoopOptions): HostConnectionLoopParameters
| Name | Type | Description |
|---|---|---|
| options | HostConnectionLoopOptions | How to connect, where to report, and the callbacks. |
Returns
HostConnectionLoopA handle to stop the loop or retry now.
probeUnsavedWsHostFunction#
The add form's "test": the registry's own checks and probe over WebSocket, saving nothing.
function probeUnsavedWsHost(
target: { token: string; url: string },
): Promise<HostProbeResult>Parameters
| Name | Type | Description |
|---|---|---|
| target | { token: string; url: string } | URL and token to try. |
Returns
Promise<HostProbeResult>The probe result.
probeWsHostFunction#
Connect, read the host's identity and daemon.info, disconnect. Never throws.
function probeWsHost(
target: { timeoutMs?: number; token: string; url: string },
): Promise<HostProbeResult>Parameters
| Name | Type | Description |
|---|---|---|
| target | { timeoutMs?: number; token: string; url: string } | URL, token and an optional timeout in milliseconds. The timeout defaults to |
Returns
Promise<HostProbeResult>Who answered and how fast, or a classified failure.
startSavedHostsFunction#
Starts the connections of a client's saved servers and keeps the directory in step with the registry.
Each saved server gets a directory entry and a connection loop. A connection whose host id differs from the saved one is closed and reported as identity.
function startSavedHosts(options: SavedHostsOptions): SavedHostsControllerParameters
| Name | Type | Description |
|---|---|---|
| options | SavedHostsOptions | Registry, directory and how to dial. |
Returns
SavedHostsControllerA controller over the saved servers.
startWsSavedHostsFunction#
Keeps the saved servers of a client connected over WebSocket.
Uses the registry in dataDir unless registry is given, and dials with connectWsClient unless connect is given.
function startWsSavedHosts(options: WsSavedHostsOptions): SavedHostsControllerParameters
| Name | Type | Description |
|---|---|---|
| options | WsSavedHostsOptions | Data directory, host directory and callbacks. |
Returns
SavedHostsControllerA controller to list, add, remove, test and retry hosts, and to stop them all.
trustSystemCertificatesFunction#
Node: every TLS client of this process trusts hostCaBundle from now
on (the desktop app's main process). Whether it applied.
function trustSystemCertificates(): booleanReturns
booleanClasses#
HostAuthErrorClass#
The server refused this client's credential: 401 (no valid token) or 403
(not allowed here). reason only when the server sent a code clients know.
class HostAuthError extends ErrorConstructor
new HostAuthError(
url: string,
status: 401 | 403,
reason?: 'origin' | 'insecure-transport' | 'untrusted-proxy' | 'scope',
)Parameters
| Name | Type | Description |
|---|---|---|
| url | string | The server URL. |
| status | 401 | 403 | HTTP status of the refusal. |
| reason? | 'origin' | 'insecure-transport' | 'untrusted-proxy' | 'scope' | Refusal code the server sent, when any. |
Returns
Properties
| Name | Type | Description |
|---|---|---|
| reason?readonly | 'origin' | 'insecure-transport' | 'untrusted-proxy' | 'scope' | Refusal code the server sent, when any. |
| statusreadonly | 401 | 403 | HTTP status of the refusal. |
| urlreadonly | string | The server URL, scheme, host and path only. |
HostBusyErrorClass#
The server turned the upgrade down with a 429: too many open connections (per token, per address) or upgrades (per address). Transient.
class HostBusyError extends ErrorConstructor
new HostBusyError(url: string)Parameters
| Name | Type | Description |
|---|---|---|
| url | string | The server URL. |
Returns
MissingHostTokenErrorClass#
No token is saved for this server (removed by hand, or another window removed it).
class MissingHostTokenError extends ErrorConstructor
new MissingHostTokenError()Returns
Constants#
HOST_PROBE_TIMEOUT_MSConstant#
Bound on one probe: the upgrade, the handshake and daemon.info.
const HOST_PROBE_TIMEOUT_MS = 10_000Interfaces#
HostConnectionLoopInterface#
Handle returned by keepHostConnected to stop the loop or retry at once.
interface HostConnectionLoopProperties
| Name | Type | Description |
|---|---|---|
| retry | () => void | Try now (a manual retry, or after an auth fix). |
| stop | () => void | Stops the loop, aborts a pending connect and closes the live client. |
HostConnectionLoopOptionsInterface#
Options for keepHostConnected: how to connect, where status goes, and the backoff bounds.
interface HostConnectionLoopOptionsProperties
| Name | Type | Default | Description |
|---|---|---|---|
| classify? | (err: unknown) => HostFailureKind |
| |
| connect | (options: { onClose: () => void; signal: AbortSignal }) => Promise<IpcClient> | One connect attempt. | |
| maxDelayMs? | number | 30000 | Upper backoff bound in milliseconds. |
| minDelayMs? | number | 1000 | Lower backoff bound in milliseconds (tests shrink it). |
| onConnected? | (client: IpcClient) => void | Connected (also after a reconnect). | |
| onDisconnected? | () => void | The live connection dropped (a reconnect is scheduled). | |
| onSignal? | (signal: HostSignal) => void | Every signal of the live connection. | |
| recheckMs? | number | PROTOCOL_RECHECK_MS | Re-check interval for a |
| update | (patch: HostConnectionPatch) => void | Receives each status change of the host. |
HostConnectionPatchInterface#
What the loop says about its host, applied by the owner to its directory entry.
interface HostConnectionPatchProperties
| Name | Type | Description |
|---|---|---|
| client? | IpcClient | null | The live client, or |
| error? | string | Why the last connect failed; |
| serverName? | string | The host's own name, from its |
| status? | HostConnectionStatus | Connection status to show. |
SavedHostsControllerInterface#
Handle returned by startSavedHosts to list, add, remove, rename, test and retry saved servers.
interface SavedHostsControllerProperties
| Name | Type | Description |
|---|---|---|
| add | (input: { name?: string; token: string; url: string }) => Promise<HostEntry> | Verify, save, connect (in place when already saved). Throws a |
| list | () => HostEntry[] | The saved hosts, read fresh (other windows share the file). |
| remove | (id: string) => boolean | Removes a server from the registry and its entry from the directory. Returns whether it existed. |
| rename | (id: string, name: string) => HostEntry | Renames a saved server and returns the updated entry. |
| retry | (id: string) => void | Reconnect now (after an outage or a token fix). |
| retryAll | () => void | Reconnect every saved server that is not connected now, its backoff reset (the computer woke up). |
| setUrl | (id: string, url: string) => Promise<HostEntry> | Move a saved server to a new URL (its saved token; the same server must answer), then reconnect it in place. Throws a |
| stop | () => void | Disconnects every saved server and removes their directory entries. The registry is not changed. |
| test | (target: string | { id: string } | { token: string; url: string }) => Promise<HostProbeResult> | A saved server (by id), or an unsaved URL + token (the add form's test). |
SavedHostsDirectoryInterface#
The client's host directory, as the saved servers see it. Keys are host ids.
interface SavedHostsDirectoryProperties
| Name | Type | Description |
|---|---|---|
| remove | (key: string) => void | The server left the registry, or the saved servers stopped. |
| saved | () => { key: string; url: string }[] | The saved servers it holds now, with their URL (a |
| update | (key: string, patch: HostConnectionPatch & { name?: string; serverVersion?: string }) => void | Applies a connection patch, a new name or a server version to the entry. |
| upsert | (entry: HostEntry, token: string | undefined) => void | Add the entry, or reconfigure the one under its key in place (status |
SavedHostsOptionsInterface#
Options for startSavedHosts: the registry, the host directory, the connect function and callbacks.
interface SavedHostsOptionsProperties
| Name | Type | Default | Description |
|---|---|---|---|
| classify? | (err: unknown) => HostFailureKind |
| |
| connect | (entry: HostEntry, token: string, options: { onClose: () => void; signal: AbortSignal }) => Promise<IpcClient> | One connect attempt to a saved server, with its token read fresh. | |
| directory | SavedHostsDirectory | The client's host directory the entries are kept in. | |
| maxDelayMs? | number | 30000 | Upper backoff bound in milliseconds. |
| minDelayMs? | number | 1000 | Lower backoff bound in milliseconds. |
| onConnected? | (key: string, client: IpcClient) => void | Called when a saved server connects, also after a reconnect. | |
| onDisconnected? | (key: string) => void | Called when a saved server's connection drops. | |
| onSignal? | (key: string, signal: HostSignal) => void | Called with every signal of a connected saved server. | |
| probeUnsaved? | (target: { token: string; url: string }) => Promise<HostProbeResult> | The add form's test of an unsaved URL + token ( | |
| registry | HostRegistry | The registry of saved servers. | |
| skip? | (entry: HostEntry) => boolean | Saved entries the directory already reaches another way (the TUI's |
ServerSyncConfigInterface#
Saved sync settings for one server, kept in hosts-sync.json.
interface ServerSyncConfigProperties
| Name | Type | Description |
|---|---|---|
| categories | SyncCategory[] | Categories sent to the server. |
| keys | string[] | Providers whose stored API key goes to this server. |
| lastError? | string | Message of the last failed push. |
| lastSyncAt? | number | Time of the last successful push in epoch ms. |
| mode | SyncMode | Whether and how this computer syncs to the server. |
| pending | SyncCategory[] | Categories changed here and not pushed yet (offline queue). |
| results | Partial<Record<SyncCategory, SyncPushResult>> | What the last push of each category did there. |
| sent | Partial<Record<SyncCategory, string>> | Per category, a hash of what was last pushed (a restart finds what changed meanwhile). |
SyncClientInterface#
The part of a host connection sync uses (an IpcClient is one).
interface SyncClientProperties
| Name | Type | Description |
|---|---|---|
| hello | { sync?: boolean; syncCategories?: SyncCategory[] } | What the server announced when it connected. |
| request | ((method: 'sync.status', params: Record<string, never>) => Promise<SyncStatus>) & ((method: 'sync.claim', params: { categories?: SyncCategory[]; deviceId: string; deviceName: string; keys?: string[]; takeover?: boolean }) => Promise<SyncClaimResult>) & ((method: 'sync.push', params: { category: SyncCategory; deviceId: string; payload: SyncPayload }) => Promise<SyncPushResult>) & ((method: 'sync.reset', params: { field: string }) => Promise<{ reset: boolean }>) & ((method: 'sync.release', params: { deviceId: string }) => Promise<{ released: boolean }>) | Sends a sync RPC to the server and resolves with its reply. |
SyncEngineInterface#
Pushes this computer's settings to saved servers and tracks the state each one reports. Servers are identified by key.
interface SyncEngineProperties
| Name | Type | Description |
|---|---|---|
| configure | (key: string, patch: Partial<Pick<ServerSyncConfig, 'mode' | 'categories' | 'keys'>>) => Promise<void> | Change the mode, categories or opted-in keys; pushes what that calls for. |
| connected | (key: string, client: SyncClient) => void | Reports that a connection to the server opened. Reads its record and pushes what its mode calls for. |
| device | DeviceIdentity | Identity of this computer. |
| disconnected | (key: string) => void | Reports that the connection to the server closed. |
| localInfo | () => SyncLocalInfo | What this computer has that the UIs mention: API keys to opt in, extensions that can't sync, stdio MCP servers. |
| notifyLocalChange | (category?: SyncCategory) => void | This computer changed something (the app's settings stream): check, push if it differs. |
| pushAll | (key: string) => Promise<void> | Push every selected category now (whatever the mode, but off). |
| refresh | (key: string) => Promise<void> | Read the server's record again. |
| removed | (key: string) => void | The server was removed here: forget its sync config (the server keeps what it has). |
| reset | (key: string, field: string) => Promise<void> | Drop an override on the server (the next push writes the field again). |
| serverChanged | (key: string) => void | A server's |
| stop | () => void | Stops the file watchers and pending pushes. |
| subscribe | (listener: () => void) => () => void | Calls |
| takeOver | (key: string) => Promise<void> | Become the source of a server another device syncs to, then push. |
| view | (key: string) => SyncView | Returns what a UI shows for the server. |
| watching | () => boolean | File watchers are running (some server keeps in sync). |
SyncEngineOptionsInterface#
Options for createSyncEngine.
interface SyncEngineOptionsProperties
| Name | Type | Description |
|---|---|---|
| dataDir | string | Directory holding the sync state files and the device identity. |
| debounceMs? | number | Wait after a change before checking (a save is often several writes). Default 500 ms. |
| deviceName? | string | Default: this computer's host name. |
| home | string | User home directory, where user-scope files and skills are read from. |
| prefix | string | Storage prefix, such as |
| providers | () => Readonly<Record<string, ProviderDescriptor>> | Returns the provider descriptors used to list API keys that can be opted in. |
| statePath | string | Path of the file holding the app's saved settings. |
| watch? | boolean | File watchers (default on); off, only |
SyncLocalInfoInterface#
What this computer has that sync UIs mention.
interface SyncLocalInfoProperties
| Name | Type | Description |
|---|---|---|
| accounts | LocalProviderAccounts[] | Every provider with an API key or a sign-in here (the GUI's accounts list). |
| keyProviders | { key: string; label: string }[] | Providers with an API key that can be opted in, with display labels. |
| localExtensions | string[] | Names of extensions that cannot sync because they are not installed from npm or GitHub. |
| stdioMcps | string[] | Names of stdio MCP servers, whose commands may be missing on the server. |
SyncViewInterface#
What a UI shows for one server.
interface SyncViewProperties
| Name | Type | Description |
|---|---|---|
| busy | boolean | A push or refresh is running. |
| config | ServerSyncConfig | Saved sync settings for the server. |
| conflict | string | null | Another device syncs to this server: its name. |
| connected | boolean | Whether a connection to the server is open. |
| status | SyncStatus | null | The server's record, when read. |
| supported | boolean | The server takes sync ( |
WsClientOptionsInterface#
Options for connectWsClient.
interface WsClientOptionsProperties
| Name | Type | Description |
|---|---|---|
| appVersion? | string | Client zidane version, surfaced in the host's diagnostics. |
| ca? | readonly string[] | Certificate authorities (PEM) a |
| expectHostId? | string | The host id a saved host answered with when it was added: a hello with
any other id closes the connection and rejects with a
|
| heartbeatMs? | number |
|
| onClose? | () => void | The connection dropped (server exit, network error, a missed deadline or heartbeat). Not called for |
| openTimeoutMs? | number | Bound on the upgrade (not the handshake, which has its own). Default |
| serve? | { notification?: (method: string, params: unknown) => void; request: (method: string, params: unknown) => Promise<unknown> } | Answer the host's requests and notifications on this connection: a
machine serving its host ( |
| signal? | AbortSignal | Abandon the connect (upgrade and handshake): it rejects with the signal's reason. |
| token? | string | Bearer token for the server, see |
| tokenCarrier? | WsTokenCarrier | How the token travels. Default: the |
| url | string |
|
Types#
WsSavedHostsOptionsType#
Options for startWsSavedHosts: SavedHostsOptions with the registry, dialer and classifier filled in.
dataDir holds the saved-hosts registry file.
type WsSavedHostsOptions = Omit<SavedHostsOptions, 'registry' | 'connect' | 'classify' | 'probeUnsaved'> & { appVersion?: string; connect?: SavedHostsOptions['connect']; dataDir: string; registry?: SavedHostsOptions['registry'] }