Docs
SSH targets
A desktop or a Chrome on your own machine as an attention.run target: add an SSH key, run one ssh -R command or attn share, and stop it with Ctrl+C.
Last updated 15 October 2026
An SSH target is a screen on your own machine, a VNC server or a Chrome, that the hub reaches through an SSH connection you open. Nothing listens on your machine’s network and no agent is installed: the hub runs an SSH server, and each remote forward (ssh -R) becomes a target. While the connection is up, the target works like any other: live sessions, recordings, cutting, flows and debug runs. Ctrl+C takes it offline.
There are two ways to connect: the standard ssh client, or attn share and the SDK’s share(), which check the local side first, can start a Chrome for you, and connect over the hub’s HTTPS port.
With ssh
-
Add your key in the web app: Settings → SSH keys, paste your public key (the
.pubfile, such as~/.ssh/id_ed25519.pub). Never paste the private one; the page refuses it. -
Verify it. The page gives a one-time code; send it signed in with that key. An unverified key can sign in but can’t share a screen.
ssh -p 22 attn@ssh.attention.run verify XXXX-XXXX -
Connect. The remote port says what you share: 5900 to 5999 is VNC, 9222 to 9299 is Chrome. The local side is wherever it listens on your machine.
# A VNC server on this machine (macOS Screen Sharing, TigerVNC, …) on port 5900: ssh -N -R 5900:localhost:5900 -p 22 -o ServerAliveInterval=15 attn@ssh.attention.run # Chrome started with --remote-debugging-port=9222 --user-data-dir=/tmp/attn-chrome: ssh -N -R 9222:localhost:9222 -p 22 -o ServerAliveInterval=15 attn@ssh.attention.run # Both, named, on one connection: ssh -N -R my-mac:5900:localhost:5900 -R my-chrome:9222:localhost:9222 -p 22 attn@ssh.attention.run
The bind address (my-mac: above) names the target; without one, the name is your key’s name and the port, such as “laptop · VNC :5900”. The targets appear on the Targets page with an SSH badge and an online dot. Reconnecting with the same key brings the same targets back, so flows that name them keep working. A VNC server that asks for a password gets it from the target’s settings.
Without -N, the connection prints which targets came online, with links to the web app. The hub drops a connection after about 45 seconds without an answer to its keepalives, so a laptop that sleeps goes offline within a minute.
To run a flow on the screen you share and get its result in the terminal, add the run command; it exits like attn run --wait (0 passed, 1 failed, 2 error, 3 cancelled):
ssh -R 9222:localhost:9222 -p 22 attn@ssh.attention.run run fixture/sign-in --param user=demo
With attn share
attn share does the same from the CLI, with a key of its own (made on first use in ~/.config/attn/ssh_ed25519, added and verified through the API), over SSH inside a WebSocket to the hub’s normal port, so it works wherever the API does. It checks the local side before it shares: that a VNC server answers on that port, or that the port is Chrome’s DevTools.
attn share --vnc 5900 --name my-mac
--chrome launch starts a Chrome with a temporary profile and stops it when the share ends; --headless without a window. --run <flow> runs a flow on the shared screen, prints its steps like attn run --wait, ends the share and exits with the run’s code. With --run the targets are ephemeral: deleted when the share ends, so CI doesn’t pile them up (--keep keeps them).
export ATTN_HUB=https://app.attention.run
export ATTN_TOKEN=attn_api_…
attn share --chrome launch --headless --run fixture/sign-in
From a script
share() in the SDK is the same as a function: it resolves once the targets are online, share.run(flow) runs a flow on one and resolves with the run and its report, and share.close() takes them offline.
import { createClient } from '@attn/sdk'
const hub = createClient({ url: 'https://app.attention.run', token: process.env.ATTN_TOKEN })
const share = await hub.share({ chrome: { launch: true, headless: true }, ephemeral: true })
try {
console.log('online:', share.targets.map((t) => t.name).join(', '))
const { run, report } = await share.run('fixture/sign-in', { params: { user: 'demo' } })
console.log(run.status, `${report?.steps.length ?? 0} steps`)
if (run.status !== 'passed') process.exitCode = 1
} finally {
await share.close()
}
Other options: vnc: { port } or chrome: { port } for what already runs on this machine, name for the targets’ name, transport: 'tcp' for the hub’s SSH port instead of the WebSocket, and key for another key file. A failure to share is a ShareError whose code and message say what to fix, such as nothing listening on the local port.
Limits
- A VNC target serves one session at a time; another is refused as busy until the first ends. Chrome targets serve several, each in its own window.
- Keys belong to the hub, not to a person: everyone who can use the hub sees them and their targets.
- Not built yet: runners on another machine than the hub reaching SSH targets.