@emptyos/client
v0.1.12
Published
External EmptyOS client
Readme
EmptyOS external client
This package is the workstation-side empty command, published to npm as
@emptyos/client. Run every empty command in the local terminal: the client
either handles it locally or transports it to the selected EmptyOS computer.
It requires Node.js 20 or newer.
Installing the client, connecting to a computer, putting files on it, sharing, and publishing are described at https://emptyos.com/docs/. This README keeps the contracts behind those pages: profile format, transport, publish mechanics, and development notes.
The companion agent skill is at skills/emptyos-computer/ and ships in the
package. empty skill install copies it to ~/.agents/skills/emptyos-computer/;
--dir <path> chooses another skills directory, and a re-run replaces the
earlier copy. It is a small bootstrap into this client and the resident
~/EMPTY.md contract.
For checkout development, the repository root provides a managed local
computer and isolated client profile. Run emptyos-dev from that worktree,
then pair with emptyos-dev empty connect <printed-owner-url> --as owner and approve
the request in its managed browser. Use emptyos-dev empty <command>
after pairing. This exercises the normal client transport without linking the
client or editing your real profiles. ./bin/emptyos-dev works from the root
without installing the optional global dispatcher. The separate resident-only
workflow uses npm run dev:native with npm run empty -- <command>. See the root
README.md for setup, persistence, testing,
and limitations.
Computer profiles
Registering a computer makes one noninteractive RPC call to confirm that the computer speaks protocol version 2. It does not make the new profile the default automatically.
empty computer add personal \
--host [email protected]
empty computer use personal
empty computers
empty computer show --json
empty computer remove personalempty connect <computer-url> pairs with an EmptyOS-managed computer without
an exe.dev account; the walkthrough is at https://emptyos.com/docs/start/. On
approval the client stores a new Ed25519 identity, tunnel token, and pinned
host key under its private config directory. Pairing secrets and tunnel token
contents are never placed in URLs, process arguments, or logs. connect
selects the new profile when no default computer exists; otherwise the
existing default remains unchanged.
Approval requests expire after 15 minutes. Retry connect with the same URL
and alias to resume a valid pending request or finish saving a valid approval
already retained locally, including after its request expires. Approval creates
a grant on the computer before local installation finishes; it does not imply
an active session. Verify saved access with the read-only
empty --computer <alias> status --json. Report partial-save errors and inspect
Client access for potentially unused grants after expired or unknown requests;
do not keep requesting fresh approvals unattended.
empty computer remove <alias> removes only the local profile, preserving its
credentials and pinned trust. To connect to an authorized replacement at the
same hostname, inspect empty computers --json, remove the intended old
profile, then run empty connect <computer-url> --as <alias> for a new owner
approval. The new pairing uses a separate credential directory without
overwriting old files. Do not manually delete key directories; a different
hostname alone does not justify deleting existing credentials or trust.
Use an SSH config Host name when a connection needs a port, proxy, key, or
other SSH option. Stored targets intentionally accept only a hostname or
user@hostname so they cannot inject SSH options or remote shell text.
An EmptyOS-managed tunnel profile can use the same version 1 config format without requiring an exe.dev account for the person using the client. Its four tunnel fields are all required together:
{
"target": "[email protected]",
"ownerOrigin": "https://computer.example",
"tunnelTokenPath": "/Users/me/.config/emptyos/computer.token",
"sshIdentityPath": "/Users/me/.ssh/emptyos_computer",
"sshKnownHostsPath": "/Users/me/.ssh/emptyos_known_hosts"
}ownerOrigin must be a credential-free HTTPS origin. All three configured
files must be regular files owned by the current user and use absolute paths.
The token and private key must have mode 0600; the known-hosts file must not
be group- or other-writable. The client reads the bearer token only
inside its SSH proxy helper. Token contents never appear in process arguments,
URLs, profile output, or logs. Tunnel SSH ignores user SSH configuration,
uses only the configured identity, and requires the pinned known-hosts entry.
Existing profiles containing only target continue to use direct SSH.
Profiles live at $XDG_CONFIG_HOME/emptyos/client.json, or at
~/.config/emptyos/client.json when XDG_CONFIG_HOME is unset. The directory
is mode 0700 and the atomically replaced file is mode 0600.
--release-catalog is optional. It replaces the official HTTPS catalog used
by empty computer update for that profile. Every profile can also update
from an explicitly supplied catalog or local release artifact.
--computer <alias-or-url> overrides the default for a command that operates
on a computer. An HTTPS URL selects the paired profile for that owner origin:
empty --computer work things --json
empty --computer https://work.example things --jsonIt is rejected for connect and the local profile-registry commands
computers, computer add, computer use, and computer remove, where an
override would otherwise be silently ignored.
Commands and help
empty --help is the complete command inventory. Help is resolved locally, so
it works without a selected computer or SSH connection:
empty --help
empty thing --help
empty put --help
empty help dataset describeThe client handles computer profiles, help, and version output locally. put,
clone, platform updates, and ssh combine local state with Git or SSH.
A route outside the client's command families is checked against the
computer's command inventory: a registered resident owner command is forwarded
as structured argv so it works without a client release, while anything else
fails locally with client usage. empty commands merges both command
inventories.
Restricted sharing is transported like the other resident Thing mutations.
empty share <id> --with <email> grants one existing EmptyOS account access
to an eligible private static Thing, while empty unshare <id> --with <email>
or --all revokes it. empty shared, empty shared add <url>, and
empty shared remove <url> manage links saved under Shared with me on the
selected computer. Services remain unshareable, and expose remains the
separate anonymous-public operation.
Human catalog commands use the selected computer alias in useful empty states:
Computer personal is empty — no Things yet
Put its first Thing:
empty put <path> [--as <id>]The client checks emptiness through the existing JSON form. Non-empty tables
remain resident-rendered and pass through unchanged. Explicit --json output
preserves valid resident results; workstation, Git, transport, and resident
failures return one JSON object with a stable code and human-readable
error. thing add <path> remains unavailable from a workstation because its
argument would be a path on the computer; its help explains the supported
put workflow.
Forwarded commands use one fixed SSH remote command with a base64url-encoded
JSON request. A Thing import uses a separate fixed endpoint for the same
structured metadata and streams a Git bundle on stdin, so file bytes never
enter the bounded argv payload. SSH runs with BatchMode=yes, a ten-second
connection timeout, and no TTY. Human-mode remote output passes through;
structured commands preserve valid resident JSON and normalize local or
transport failures at the client error boundary. When SSH itself fails (exit
255), SSH or the tunnel helper reports the connection error, and the client
ends with a hint naming the computer profile. DNS failures can result from
an incorrect hostname, network trouble, or sandbox network restrictions; they
do not prove that a computer was retired. Agents running in a restricted
sandbox should request network access through their runtime's approval
mechanism and retry a read-only check on the same computer before continuing.
Before put, the client reads the resident version response. put requires
the type-neutral thingImport capability. The client and computer use
protocol 3; unsupported forms stop before staging or upload with an explicit
empty computer update remediation.
empty ssh without arguments opens the ordinary owner SSH session. Arguments
after -- are an explicit raw remote command and consequently have the normal
remote-shell semantics of ssh host command...; use transported empty
commands for structured, injection-resistant RPC.
Koans
At a terminal, the client's quiet moments end with one of the landing page's
koans: empty computers with no profiles, empty things / projects /
data / changes on a computer that has none yet, and the top-level help.
The koan rotates daily. Piped output, --json, errors, command help, and the
resident CLI never carry one; set EMPTYOS_PLAIN=1 to turn them off entirely.
EmptyOS updates
empty status reports the computer's complete EmptyOS release version and a
short release hash. empty status --json includes software with state,
displayVersion, and the full release hash. The states are current,
updating, resident-changed, and unknown; current means the completed
vendor baseline, not that owner-editable files are unmodified. Older computers
may report an unnumbered release or unavailable version. empty --version
continues to identify the workstation client and protocol.
empty computer update updates Home, Gateway, the resident CLI and agent
guidance, Recovery, and EmptyOS system services together. The complete version
advances only after both the editable and protected components finish. This
does not upgrade the underlying Linux distribution or kernel.
Check the selected computer without changing installed software, then apply the configured release when the plan is acceptable. Checks may retain an update stage on the computer; use its printed resume command to apply that exact plan:
empty computer update --check
empty computer update--computer <alias-or-url> selects a different profile for either command by
its local alias or paired HTTPS URL. --json returns the selected computer,
release and artifact identifiers, status, per-path plan, warnings, updater
output, and exit status without decorating them.
If apply reports needs-reconciliation, the structured result includes a
retained complete-update stage, a resident candidate location, and exact next
command. Continue from an agent: inspect that candidate's RECONCILE.md through empty ssh, merge only
the declared conflicting paths, run the candidate tests, then resume the same
transaction:
empty computer update --resume <stage-name>Resume does not fetch a newer release. It reuses the retained target and rejects stale live state, edits outside the conflict set, or failed validators.
The default source is the official catalog at
https://release.emptyos.com/emptyos/v1/catalog.json. A profile-level
--release-catalog replaces it for that computer. Supply --catalog to
override either catalog for one invocation, or use an immutable artifact that
was obtained through a trusted operator channel:
empty computer update --catalog https://releases.example/emptyos/catalog.json
empty --computer work computer update --release ./emptyos-release.jsonThe client validates HTTPS on every redirect, bounds downloads, and checks the catalog's artifact digest and release identity. It then verifies the release's publisher signature against the public trust already installed on the selected computer, read through that profile's SSH connection. The root verifier on the computer independently checks its own pinned trust before executing signed update code. A different catalog or local file cannot supply a replacement key.
The owner account must have working noninteractive sudo, as on provisioned EmptyOS computers. Older computers also need one operator update to install the protected update configuration before this command can run. Losing the client connection does not stop the detached update: use the retained stage's resume command to inspect and finish or recover the same transaction.
Do not use resident-only tooling to fulfill empty computer update. That
path is not an alias of the signed complete update, does not update Recovery
or services, and does not advance the complete EmptyOS version.
Publish contract
put, clone, and put . are described at https://emptyos.com/docs/publish/.
This section keeps the mechanics behind them.
put snapshots the selected content as one parentless commit without source
history, remotes, hooks, or Git configuration, and streams it as a Git bundle
on stdin to a fixed import endpoint. Upload happens before the resident
mutation lock; installation is revalidated and atomic, and a failed validation
leaves no partial Thing behind. Successful initial --json output reports the
detected sourceKind, content counts, installed head and manifest facts,
generated scaffolding, preparation, verification, installation, and
service registration state.
clone first confirms the Thing exists, clones <ssh-target>:things/<id>,
validates the top-level id in thing.yaml, and stores three clone-local Git
keys:
emptyos.computeremptyos.profileTargetemptyos.target(thing://<id>)
The profile target snapshot prevents an alias that was later retargeted from
silently publishing a checkout to another computer. Clone JSON reports the
local root plus the computer, immutable profile target, and thing://<id>
binding.
Publish never rewrites local history. It requires:
- a bound repository on an attached branch;
- a matching profile target and
thing.yamlID; - no tracked or untracked worktree changes;
- a regular, executable, non-symlink
./verifythat succeeds locally; and - exactly one non-merge commit on top of the current origin default-branch HEAD.
After fetching and validating the live head, publish pushes HEAD to
refs/emptyos/candidates/<full-sha> and requests:
thing activate <id> --sha <sha> --expected-base <live-head> [--json]The activation response and exit status are passed through to the caller.
Development
cd client
npm install
npm link # optional: run this checkout as `empty`
npm testTests use temporary real Git repositories and fake ssh/git executables;
they do not require a network connection or an EmptyOS computer.
client/lib/updater/ holds byte-identical copies of the complete update
scripts and their dependencies. The published package must not reach outside
its own tree. client/scripts/sync-updater.mjs declares the exact source file
list; refresh copies from the repository root with
node client/scripts/sync-updater.mjs. client/test/vendor.test.js rejects
drift, and the packed-package test checks imports and executable availability.
