This document describes the project-scoped remote architecture that is currently implemented in TermAl.
Implemented for SSH-backed remote execution through the local control plane.
The browser always talks to the local TermAl server. The local server owns the browser-facing API, persisted preferences, project list, workspace layouts, and event stream. Projects may be bound to the built-in local remote or to an SSH remote. Sessions, orchestrator instances, terminal commands, file operations, git operations, review documents, and instruction search route from that project ownership.
local remote is always present, enabled, and non-removable.~/.termal/termal.sqlite.remoteId; omitted or local means local.The shipped config shape is intentionally small:
type RemoteConfig = {
id: string;
name: string;
transport: "local" | "ssh";
enabled: boolean;
host?: string | null;
port?: number | null;
user?: string | null;
};
No private keys or secret material are stored. TermAl relies on the user’s
system ssh and ssh-agent setup.
For SSH remotes, the local server creates a forwarded local port from the 47000-56999 range:
Browser -> local TermAl :8787 -> ssh -L local_port:127.0.0.1:8787 -> remote TermAl
Startup attempts:
ssh ... remote termal server, then probe the
forwarded /api/health. This intentionally stays a direct remote command so
Windows SSH hosts do not need a POSIX shell just to start TermAl.ssh -N ... and expect a TermAl server to
already be running on the remote host.Both modes use batch SSH, ExitOnForwardFailure, and keepalive options. Managed
mode starts a remote server only for the lifetime of the SSH process. Saving a
remote definition does not start, stop, install, or upgrade anything.
SSH remotes expose one-shot lifecycle actions from the Remotes preferences panel:
git and cargo are available, creates the remote .termal/bin
directory, and writes remote-install.json with the checkout path/platform.~/.termal/remote-install.json, runs
git pull --ff-only, runs cargo build --release --bin termal, and installs
the resulting binary to ~/.termal/bin/termal on POSIX remotes or
%USERPROFILE%\.termal\bin\termal.exe on Windows remotes.Registration is intentionally based on an existing remote enlistment/checkout.
TermAl does not clone the repository, install Rust, configure SSH keys, or manage
a system service. The local server invokes both actions as one-shot SSH
commands, using sh -lc for POSIX checkout paths and encoded PowerShell for
Windows checkout paths, then captures stdout/stderr and returns the sanitized
output to the browser.
Build / upgrade can run for several minutes on a slow remote checkout. The preferences panel keeps the action pending during that request, but build output is returned only after the remote command finishes; progress streaming is not part of the current lifecycle action API.
Managed startup still runs termal server from the remote command environment.
If you want the managed server path to pick up the binary installed by
Build / upgrade, put ~/.termal/bin on the remote user’s PATH.
Project-scoped routes resolve the project first:
Session-scoped routes resolve the session’s remote mapping:
The UI passes canonical local project/session ids. Remote-native ids stay inside the backend mapping layer.
The local server subscribes to each active remote’s /api/events stream and
merges remote snapshots/deltas into local browser-facing state.
Important invariants:
/api/state shape and one /api/events stream.DELETE /api/projects/{id} removes the local project reference only.
For local and remote-backed projects:
originProjectId referencesThis is deliberate. Project deletion in TermAl is a local organization action, not a remote filesystem or remote database deletion.
Terminal commands use the same project/session routing rules as file and git operations.
output, complete, and error events.git, and SSH auth
already exist. TermAl verifies and uses them, but does not provision them.C:\src\TermAl; POSIX ~/src/TermAl paths keep using POSIX
shell scripts.