@affinity-os/substrate
v0.2.3
Published
Affinity OS substrate node — local filesystem, LAN, and storage capabilities for a cloud-hosted system.
Downloads
1,341
Readme
@affinity-os/substrate
The Affinity OS substrate node: a local capability add-on for a cloud-hosted system. It runs on your machine, connects outbound-only to the Affinity OS mesh, and serves allowlisted capabilities (filesystem sources, LAN egress, extra knowledge storage) to your actor's system in the cloud.
- Never a system host: no metamodel, no actor database, no placement.
- No inbound ports; capability and surface traffic is mesh-only, with loopback health and token-gated surface serving.
- Deny by default: the operator holds per-node scopes; local config can only narrow them.
- Requires Bun (the CLI runs as a Bun service).
Pair and run
In the cockpit: Settings → Substrate → Add node. Copy the command it shows, or build it yourself:
bunx @affinity-os/substrate@latest enroll \
--operator-url https://<operator-domain> \
--code <PAIRING_CODE> \
--name "Office Mac" \
--startenroll exchanges the one-time pairing code for device credentials (per-device
mesh seed, the operator's mesh bootstrap seed, and the actor-context verifier)
and stores them in the OS keychain (0600 file fallback). --start then runs the
node in the same terminal (keep it open); without it, run
bunx @affinity-os/substrate@latest start afterwards. start joins the mesh through
the operator seed, serves loopback health on 127.0.0.1:19100, and polls the
operator for capability scopes and revocation.
Other commands:
bunx @affinity-os/substrate@latest status # enrollment + daemon state
bunx @affinity-os/substrate@latest start --fs-roots /Users/me/Documents
bunx @affinity-os/substrate@latest start --health-port 0 # no loopback health
bunx @affinity-os/substrate@latest start --no-open-windows # discover surfaces without launching windowsLocal configuration never widens the platform scope: --fs-roots intersects
with the roots the operator grants the node.
Surfaces
A node advertises its displays (viewport, color depth, input, refresh) and can
back surfaces: the actor creates a kind: "node" surface in the cockpit,
binds it to one advertised display, and projects compatible views onto it. The
node discovers its bound surfaces through the owning system, serves the surface
app on loopback, and opens a browser window per surface (disable with
--no-open-windows). All rendering traffic is bridged to the system over the
mesh; the loopback server is token-gated and never reaches the LAN.
bunx @affinity-os/substrate@latest surfaces list # what this node advertises (auto-enumerated)
bunx @affinity-os/substrate@latest surfaces add --id desk --name "Desk" --width 3024 --height 1964 --density 2
bunx @affinity-os/substrate@latest surfaces remove --id deskDisplay enumeration is automatic on macOS (system_profiler), Linux (xrandr)
and Windows (PowerShell CIM), refreshed while the node runs so hotplug updates
surface availability. Adding a display pins the advertised set (surfaces in
the node's config.json); a node that advertises no surface (a backend server)
cannot back one.
If the platform requires a newer surface client than this node runs, bound
surfaces serve an "update required" page and /health reports
bridge.updateRequired; update the package and restart the daemon (surface
traffic also carries a version stamp, so old clients fail fast rather than
rendering against a broken catalogue).
Every command starts with the running version, e.g.
[substrate] @affinity-os/substrate v0.2.1 · bun 1.4.0 · darwin/arm64.
The console stays quiet by default: transient mesh churn (dial-abort teardowns,
peers with no dialable address) and surface-bridge retries only appear with
AFOS_MESH_DEBUG=1 / AFOS_SUBSTRATE_DEBUG=1. When the actor's system is not
on the mesh the node logs one "waiting for the system" line and retries quietly
in the background — the system node alone is enough; no cockpit or gateway
needs to be online.
Publishing (maintainers)
The CLI is bundled, so the workspace-only dependencies (mesh, api) are
inlined; the only runtime dependency is the QUIC transport, whose native binary
npm/bun installs per platform. stage writes a publish-only manifest (no
workspace: devDependencies, which npm cannot publish) into .publish/.
Publishing is automated by .github/workflows/substrate-publish.yml: bump
version here, merge to main, and the workflow tests, stages, and publishes
the package only if that version is not already on npm (workflow_dispatch
re-runs the check). It needs an NPM_TOKEN repository secret: an npm granular
access token with read/write access to the @affinity-os scope.
Manual fallback:
npm login --scope @affinity-os
bun run stage
cd .publish && npm publish --access publicConsumers then run it with bunx @affinity-os/substrate@latest ....
