Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions public/blog/banners/shell-online-live-terminal-browser-link.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions public/img/shell-online.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions src/data/blogPosts.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,19 @@
[
{
"slug": "shell-online-live-terminal-browser-link",
"title": "shell.online: A Live Browser Link for Any Terminal",
"description": "shell.online turns a running terminal command into a browser link you can watch or type into from any device. Open source, built by Pilot Protocol.",
"date": "Sep 2",
"category": "Blog",
"tags": [
"shell-online",
"terminals",
"agents",
"open-source"
],
"banner": "banners/shell-online-live-terminal-browser-link.svg",
"iso_date": "2026-09-02"
},
{
"slug": "pilot-raises-4-5m-seed",
"title": "Pilot raises a $4.5M seed round to build the internet for agents",
Expand Down
4 changes: 2 additions & 2 deletions src/layouts/BlogLayout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -254,13 +254,13 @@ const faqSectionId = headingIds.has('frequently-asked-questions')
<span class="blog-nav-label">&larr; Previous</span>
<span class="blog-nav-title">{prevPost.title}</span>
</a>
) : <div />}
) : <div class="blog-nav-empty" />}
{nextPost ? (
<a class="blog-nav-link blog-nav-next" href={`${collectionPath}${nextPost.slug}`}>
<span class="blog-nav-label">Next &rarr;</span>
<span class="blog-nav-title">{nextPost.title}</span>
</a>
) : <div />}
) : <div class="blog-nav-empty" />}
</div>
</nav>
)}
Expand Down
82 changes: 82 additions & 0 deletions src/pages/blog/shell-online-live-terminal-browser-link.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
import BlogLayout from '../../layouts/BlogLayout.astro';

const bodyContent = `<p>A terminal has always been stuck on one screen, on one machine, in front of one person. Start a build, a training run, or a coding agent, walk away, and whatever it is doing becomes invisible until you come back to that desk.</p>

<p><a class="brand-shell" href="https://shell.online/" target="_blank" rel="noopener">shell.online</a> removes that constraint. Prefix any command with <code>shell</code> and it prints a URL:</p>

<pre><code>$ shell npm run build
https://shell.online/s/&lt;share-id&gt;</code></pre>

<p>Open that URL on a phone, a tablet, or someone else's laptop, and you are looking at the live terminal. Not a video of it, not a screenshot, but the real session, rendering as it runs. The process itself never moves. It keeps running on the machine that started it, with the same files and the same permissions it always had.</p>

<section>
<h2>Watch, or type</h2>

<p>Links are interactive by default: whoever opens one can send keystrokes to the session. Add <code>--read-only</code> and the link becomes view-only, and that decision is fixed when the session is created:</p>

<pre><code>$ shell --read-only python train.py</code></pre>

<p>Read-only is enforced by the server, not by hiding a text box in the page. Browser developer tools or handcrafted WebSocket frames cannot promote a view-only link into a typing one.</p>

<p>Sessions run in the background, so your terminal stays free. <code>shell list</code> shows what is live, including whether the public link is still online independently of whether the local process is. <code>shell attach</code> takes a session over locally without cutting off browser viewers, and <code>shell kill</code> ends it. A share also closes on its own when its task exits.</p>
</section>

<section>
<h2>Why this matters for agents</h2>

<p>Coding agents changed the shape of this problem. An agent can now work unattended for hours, and the only way to know what it is doing is to be sitting at the machine it runs on.</p>

<p>Inside a Claude Code session, <code>shell claude</code> starts a shareable fork of that conversation and its workspace, so you can follow the work from a phone and step in when it needs a decision. Any command-line tool behaves the same way, whether that is <a href="/blog/claude-agent-teams-over-pilot">a Claude agent team</a> or <a href="/blog/openclaw-meets-pilot-agent-networking-one-command">an OpenClaw fleet</a>. For agents driving the CLI themselves, <code>shell --json</code> emits a single structured line containing the share URL, session ID, and access mode, which the agent can hand to its operator through whatever channel it already uses. There is a ready-made skill at <code>shell.online/skill</code> for exactly that, in the same spirit as <a href="/blog/building-custom-pilot-skills-openclaw">the skills Pilot ships for agent tooling</a>.</p>

<p>This is the same problem Pilot Protocol works on from the other side. Pilot gives agents addresses, encrypted tunnels, and a trust model so they can reach each other without a platform in the middle, which we cover in detail in <a href="/blog/overlay-network-ai-agents">the overlay network architecture post</a> and in the <a href="/docs/">documentation</a>. <span class="brand-shell">shell.online</span> covers the moment a person needs to see what one of them is actually doing.</p>
</section>

<section>
<h2>The link is the key</h2>

<div class="callout">
<p><strong>Worth being blunt about,</strong> because it is the first question anyone sensible asks: the share URL is a bearer credential. Anyone holding an interactive one can type into that session with the same operating-system permissions as the process it wraps. That is why view-only links exist, why sessions can be killed on command, and why the documentation says so in plain language rather than burying it. The same posture runs through <a href="/trust">our own trust model</a>.</p>
</div>

<p>For work where the relay should not be able to read the screen at all, <code>--e2ee</code> encrypts terminal frames with AES-256-GCM before they leave your machine. The key travels in the URL fragment, which browsers never send to a server, or is derived in the browser from a password you share separately. What encryption does not hide is equally explicit: connection IPs, timing, frame sizes, and session lifecycle metadata stay visible to the relay.</p>

<p>Terminal contents are never stored server-side in either mode. The machine keeps a bounded 512 KiB replay buffer in memory so a viewer arriving late sees the current screen. When the task exits, the session state is deleted and the link stops working.</p>
</section>

<section>
<h2>What is underneath</h2>

<p>A Go binary owns the local terminal and mirrors its input and output. A Cloudflare Worker handles session creation, rate limiting, and static assets, and one coordinator object per terminal connects the machine to its viewers and enforces that session's access mode. In the browser, xterm.js renders the real thing: ANSI colour, alternate screens, mouse sequences, and a terminal continuously refitted to the actual viewport rather than a CSS box.</p>

<p>That last detail matters more than it sounds. A phone is not a small desktop. The browser tracks the visual viewport through keyboard, rotation, and browser-chrome changes and resizes the actual PTY, one viewer owns sizing at a time so the display does not jump when several people watch, and paste is chunked into 16 KiB frames with backpressure so one large paste cannot flood the session.</p>

<p>Network failures are treated as display failures, never as permission to kill local work. The CLI and browser reconnect on their own, and a disconnected session keeps a 15-minute grace window.</p>
</section>

<section>
<h2>Install</h2>

<pre><code>curl -fsSL https://shell.online/install | sh</code></pre>

<p>macOS and Linux, on arm64 and amd64. The installer verifies the binary's checksum before writing it, never invokes <code>sudo</code>, and never silently edits your shell configuration. There is also a Homebrew tap and a Docker image that keeps one stable encrypted link and workspace across container restarts. No accounts, and nothing for the person opening your link to install.</p>

<p><span class="brand-shell">shell.online</span> is MIT licensed and open source at <a href="https://github.com/TeoSlayer/shell.online" target="_blank" rel="noopener">github.com/TeoSlayer/shell.online</a>. If you want your own tool reachable by agents the same way, see <a href="/app-store">the agent app store</a> and <a href="/blog/build-agent-app-turn-api-into-tool">how to turn an API into an agent app</a>.</p>
</section>

<div class="cta">
<h3>Share your first terminal</h3>
<p>One command turns a running process into a link you can open anywhere. Interactive or read-only, encrypted if you want it.</p>
<a href="https://shell.online/">Try shell.online</a>
</div>`;
---
<BlogLayout
title="shell.online: A Live Browser Link for Any Terminal"
description="shell.online turns a running terminal command into a browser link you can watch or type into from any device. Open source, built by Pilot Protocol."
date="September 2, 2026"
tags={["shell-online", "terminals", "agents", "open-source"]}
canonicalPath="/blog/shell-online-live-terminal-browser-link"
bannerImage="/blog/banners/shell-online-live-terminal-browser-link.svg"
>
<Fragment set:html={bodyContent} />
</BlogLayout>
22 changes: 15 additions & 7 deletions src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,11 @@ const featuredBlurbs: Record<string, string> = {

<!-- ANNOUNCEMENT BAR -->
<div class="announce-bar">
<a class="announce-headline" href="/news/pilot-raises-4-5m-seed"
data-track="announce_click" data-track-target="seed-round">
<a class="announce-headline" href="https://shell.online/" target="_blank" rel="noopener"
data-track="announce_click" data-track-target="shell-online">
<span class="announce-badge">New</span>
<span class="announce-text">Pilot raises a <strong>$4.5M seed round</strong> to build the internet for agents</span>
<img class="announce-logo" src="/img/shell-online.svg" alt="" width="21" height="21" decoding="async" aria-hidden="true" />
<span class="announce-text">Try <strong>shell.online</strong>, a live browser link for any terminal</span>
<span class="announce-arrow" aria-hidden="true">→</span>
</a>
</div>
Expand All @@ -77,7 +78,7 @@ const featuredBlurbs: Record<string, string> = {
background: var(--bg-2);
border-bottom: 1px solid var(--line);
color: var(--ink);
font-size: 0.84rem;
font-size: 0.98rem;
font-weight: 500;
line-height: 1.35;
text-align: center;
Expand Down Expand Up @@ -139,13 +140,20 @@ const featuredBlurbs: Record<string, string> = {
flex: none;
text-transform: uppercase;
letter-spacing: 0.06em;
font-size: 0.65rem;
font-size: 0.72rem;
font-weight: 700;
padding: 0.18rem 0.5rem;
padding: 0.2rem 0.55rem;
border-radius: 999px;
background: var(--ann-line);
color: var(--accent-ink);
}
.announce-logo {
flex: none;
display: block;
width: 21px;
height: 21px;
border-radius: 6px;
}
.announce-text { color: var(--ink); }
.announce-text strong { color: var(--accent-text); font-weight: 650; }
.announce-arrow {
Expand All @@ -160,7 +168,7 @@ const featuredBlurbs: Record<string, string> = {
.announce-bar:hover .announce-arrow { transform: none; }
}
@media (max-width: 560px) {
.announce-bar { font-size: 0.76rem; padding: 0.66rem 1rem; }
.announce-bar { font-size: 0.88rem; padding: 0.7rem 1rem; }
.announce-headline { gap: 0.5rem; }
.announce-badge { display: none; }
}
Expand Down
2 changes: 1 addition & 1 deletion src/pages/plain/index.astro
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
// Auto-generated by scripts/regen-plain.mjs. Edit the marketing source and re-run.
// plain-source: src/pages/index.astro
// plain-source-sha256: 643dd3b8e66698e7c82794b4c5fb5f11090189645689125d82aaa80627c73aa7
// plain-source-sha256: 817fc43f0cb0dce6209ee46a39ebff404c2c9ff005f24bff573eadd84064ed41
import PlainLayout from '../../layouts/PlainLayout.astro';
---
<PlainLayout title="Pilot Protocol — plain" description="Pilot Protocol is an overlay network for AI agents: virtual addresses, ports, and encrypted tunnels over UDP." canonical="https://pilotprotocol.network/plain/">
Expand Down
2 changes: 2 additions & 0 deletions src/styles/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
--accent-ink: #0b0b0a; /* text on accent */
--accent-dim: oklch(0.88 0.22 125 / .18);
--accent-glow: rgba(200, 255, 0, 0.12);
--shell-blue: oklch(0.84 0.08 250); /* shell.online product name */
--term-bg: #060605;
--grid-line: rgba(255, 255, 255, 0.025);

Expand Down Expand Up @@ -57,6 +58,7 @@
--accent-ink: #0b0b0a;
--accent-dim: oklch(0.72 0.19 125 / .14);
--accent-glow: rgba(130, 170, 20, 0.12);
--shell-blue: oklch(0.5 0.15 255); /* accessible on light surfaces */
--term-bg: #0b0b0a; /* terminal stays dark in both modes */
--grid-line: rgba(0, 0, 0, 0.04);

Expand Down
10 changes: 8 additions & 2 deletions src/styles/system.css
Original file line number Diff line number Diff line change
Expand Up @@ -1009,6 +1009,9 @@ a.moat-card-link:hover .num { color: var(--accent); }
============================================================ */
.accent { color: var(--accent-text); }
.accent-text { color: var(--accent-text); }
.brand-shell,
.article a.brand-shell,
.prose a.brand-shell { color: var(--shell-blue); }

h2 em { font-family: var(--serif); font-style: italic; font-weight: 400; }
h2 .accent { color: var(--accent-text); }
Expand Down Expand Up @@ -2187,6 +2190,7 @@ body.nav-mobile-open { overflow: hidden; }
.blog-card img {
display: block;
width: 100%;
height: auto;
aspect-ratio: 1200/630;
object-fit: cover;
border: 1px solid var(--line);
Expand Down Expand Up @@ -2229,7 +2233,7 @@ body.nav-mobile-open { overflow: hidden; }
.blog-card .meta .date { color: var(--ink-dim); }

.blog-hero { position: relative; }
.blog-hero h3 { font-size: 28px; }
.blog-hero h3 { font-size: 28px; padding-right: 84px; }
.blog-hero::after {
content: 'Latest';
position: absolute;
Expand Down Expand Up @@ -2260,7 +2264,7 @@ body.nav-mobile-open { overflow: hidden; }
.blog-list { padding: 96px 20px 56px; }
.blog-card { grid-template-columns: 1fr; gap: 16px; padding: 24px 0; }
.blog-hero::after { top: 16px; }
.blog-hero h3 { font-size: 22px; }
.blog-hero h3 { font-size: 22px; padding-right: 0; }
}

/* ============================================================
Expand Down Expand Up @@ -2626,6 +2630,7 @@ body.nav-mobile-open { overflow: hidden; }
.related-card img {
display: block;
width: 100%;
height: auto;
aspect-ratio: 1200/630;
object-fit: cover;
border-bottom: 1px solid var(--line);
Expand Down Expand Up @@ -2671,6 +2676,7 @@ body.nav-mobile-open { overflow: hidden; }
}
.blog-nav-link:hover { background: var(--bg-2); color: inherit; }
.blog-nav-link:hover .blog-nav-title { color: var(--accent); }
.blog-nav-empty { background: var(--bg); }
.blog-nav-prev { text-align: left; }
.blog-nav-next { text-align: right; }
.blog-nav-label {
Expand Down
Loading