From b63fe5c5ac18ef9ebcbf6d001cd547e4f8a18945 Mon Sep 17 00:00:00 2001 From: Teodor Calin Date: Mon, 14 Sep 2026 18:10:08 +0300 Subject: [PATCH] Document the web app and Refstream alpha --- CHANGELOG.md | 5 ++ README.md | 4 + app/README.md | 4 + docs/content.json | 170 +++++++++++++++++++++++++++++++++++ docs/self-hosting.md | 10 ++- public/llms.txt | 15 ++++ public/sitemap.xml | 2 + scripts/test-landing-seo.mjs | 10 +-- shared/documentation.ts | 4 + tests/documentation.test.ts | 4 + web/documentation.ts | 4 + 11 files changed, 225 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29fee38..61586cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ All notable user-visible changes are recorded here. Versions follow [Semantic Ve - Redrew the landing page phone frame with a titanium-style band, Dynamic Island, iOS status bar, physical side buttons and a home indicator. +- Added versioned Web app and Refstream alpha documentation pages, including + machine linking, session state, personal-vault scope, renderer boundaries, + persistent agent handoffs, backed files, and alpha fallback behavior. +- Kept Keycloak outside the hosted production configuration and documented it + only as a future self-hosted authentication adapter. ## [0.15.1] — 2026-09-14 diff --git a/README.md b/README.md index 527daf3..35334de 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,8 @@ browser tab for up to four hours and still requires the local process to be running. Revoking access is immediate and does not stop the terminal. Files stay unavailable unless the host separately used `--files` or `--files-root`. See the [agent handoff protocol](https://github.com/TeoSlayer/refstream.js/blob/v0.1.0-alpha.5/docs/agents.md). +The shell.online-specific behavior and fallback path are documented in the +[Refstream alpha guide](https://shell.online/refstream/). ## Security @@ -141,8 +143,10 @@ docker compose logs shell-online ## Documentation - [Quick start](https://shell.online/docs/) +- [Web app](https://shell.online/app/) - [CLI reference](https://shell.online/cli/) - [Mobile behavior](https://shell.online/mobile/) +- [Refstream alpha](https://shell.online/refstream/) - [Reliability](https://shell.online/reliability/) - [End-to-end encryption](https://shell.online/e2ee/) - [Containers](https://shell.online/docker/) diff --git a/app/README.md b/app/README.md index ebd09a2..569a213 100644 --- a/app/README.md +++ b/app/README.md @@ -9,6 +9,10 @@ sessions. The CLI and relay work without it. Requirements: Node.js 22, npm, Firebase Authentication, and PostgreSQL for database-backed tests. +The hosted service uses Firebase Authentication. Keycloak is not enabled in the +current release; any future Keycloak adapter is self-hosting-only and must not be +added to the `app.shell.online` production configuration. + ```sh cp .env.example .env.local npm ci diff --git a/docs/content.json b/docs/content.json index c1e0b8b..63adeb6 100644 --- a/docs/content.json +++ b/docs/content.json @@ -23,6 +23,10 @@ "Choose access", "Links are interactive by default. Add --read-only before the command when viewers should monitor without being able to send terminal input." ], + [ + "Refstream alpha", + "xterm.js remains the default renderer. Refstream v0.1.0-alpha.5 is an optional browser setting for search, marked-command inspection, themes, output export, backed file references, and revocable agent handoffs. It changes only the browser renderer; switch back to xterm.js whenever a TUI or input path is not yet compatible." + ], [ "Encryption is automatic", "Every normal share is E2EE. shell prints a ten-character browser password with the URL; set SHELL_ONLINE_E2EE_PASSWORD to choose your own. --no-e2ee is an explicit compatibility opt-out. Use Docker when the same encrypted shell and URL should survive container restarts." @@ -53,6 +57,78 @@ ] ] }, + "app": { + "eyebrow": "Web app", + "title": "One place for your machines, sessions, and team.", + "intro": "The optional app at app.shell.online organizes sessions from linked machines without changing how the CLI, relay, PTY, or end-to-end encryption work. The CLI and anonymous share links remain fully usable without an account.", + "seo": { + "description": "Use the optional shell.online web app to link machines, manage live sessions, collaborate with a team, and keep session passwords in a personal encrypted vault.", + "socialTitle": "Web app guide | shell.online", + "socialDescription": "Link machines, find live sessions, assign teammates, start approved processes, and use an optional personal password vault." + }, + "cards": [ + [ + "Link a machine", + "Run shell login and finish the approval in your browser. The CLI returns the one-time result through a listener bound to 127.0.0.1; shell login --no-browser prints the same local approval URL instead of opening it. Linking is optional and does not change normal share behavior.", + [ + [ + "shell login", + "Link this machine and optionally allow browser-started sessions." + ], + [ + "shell whoami", + "Show the linked account and machine identity." + ], + [ + "shell logout", + "Unlink this machine and revoke its account token without stopping local shares." + ] + ] + ], + [ + "Understand the session list", + "The app combines account metadata with the relay's current state. Online and Starting mean the relay can still see a live host or startup; Offline and Unavailable never imply that the process is reachable; Finished records a known task exit. Search, access groups, owners, assignees, machines, and uptime help locate a session without opening every row." + ], + [ + "Open and organize sessions", + "Opening a session uses its original share URL and browser password. Session-row controls, assignee selectors, and menus are independent from the row link. Multiple terminal tabs stay mounted while you switch between them, so their sockets and scrollback remain live. Owners can assign several teammates or transfer ownership without changing the public bearer link." + ], + [ + "Start from the browser", + "Remote start is explicit machine consent, not a hidden agent. shell login can start the local daemon after approval; the app then asks that machine to launch a supported agent preset or a command. shell daemon status, stop, and start manage availability, while shell service install is the opt-in path for a machine that should remain reachable across logins.", + [ + [ + "shell login --no-remote-start", + "Link the account without granting browser-start capability." + ], + [ + "shell daemon status", + "Check whether this machine can accept an approved browser start." + ], + [ + "shell service install", + "Install persistent machine availability deliberately; it is never implied by login." + ] + ] + ], + [ + "Personal vault", + "The CLI always prints and locally retains an active session password. After shell login, it also saves an encrypted copy only when that person has enabled their optional vault. The vault is per account, not per organization; teammates do not automatically receive its passwords. Unlock it with its vault password or a supported PRF passkey, and retain the recovery key as the break-glass fallback." + ], + [ + "Organizations and audit", + "An organization groups people, machines, and sessions. Roles govern administrative actions; session ownership and multiple assignees govern responsibility for individual sessions. The audit log records who did what and when. Sensitive recorded input is encrypted to the team's audit key in the browser; ordinary service metadata remains visible to the app." + ], + [ + "Failure and recovery", + "A stale app row cannot keep a dead share online: relay reconciliation moves unreachable sessions out of the live state, and a known task exit marks them Finished. Failed list loads preserve the current view and can be retried. If a password is not in your personal vault, retrieve it from the machine with shell password ; the service has no decryption backdoor." + ], + [ + "Authentication and hosting", + "The hosted app uses Firebase Authentication and PostgreSQL, and the terminal relay remains independent. The app can also run as an optional self-hosted service from app/. Keycloak is reserved for a future self-hosted authentication adapter and is not configured, enabled, or deployed on app.shell.online in this release." + ] + ] + }, "mobile": { "eyebrow": "Terminal fidelity", "title": "A real terminal, fitted to the device in your hand.", @@ -85,6 +161,96 @@ ] ] }, + "refstream": { + "eyebrow": "Refstream alpha", + "title": "An experimental terminal renderer with agent handoffs.", + "intro": "Refstream v0.1.0-alpha.5 is an optional browser renderer. xterm.js remains the default and the compatibility fallback; choosing Refstream changes only the local browser view, never the process, PTY, relay permissions, encryption, or shared terminal grid.", + "seo": { + "description": "Use the optional Refstream alpha renderer in shell.online for terminal search, command markers, themes, output export, backed file references, and scoped agent handoffs.", + "socialTitle": "Refstream alpha in shell.online", + "socialDescription": "What Refstream changes, how to select it, how agent handoffs work, and which alpha limitations still apply." + }, + "cards": [ + [ + "Select it deliberately", + "Open Terminal controls on an anonymous share and choose Refstream (unstable alpha), or use the Renderer selector above terminal tabs in the web app. The choice is saved in that browser. Switching rebuilds the visual terminal from the current authoritative snapshot; it does not restart or resize the local process.", + [ + [ + "xterm.js", + "Default and recommended renderer for broad TUI compatibility." + ], + [ + "Refstream (unstable alpha)", + "Optional renderer for the capabilities described on this page." + ] + ] + ], + [ + "What Refstream owns", + "Refstream owns its VT parser, normal and alternate screen buffers, bounded scrollback, Unicode cells, selection, input protocols, touch handling, and virtualized browser rows. shell.online still owns the authenticated WebSocket, E2EE framing, input lease, read-only enforcement, collaboration, terminal sizing, and process lifecycle." + ], + [ + "Tools enabled by shell.online", + "Refstream mode adds literal search, command inspection when OSC 133 shell markers exist, retained-output download, eight built-in themes, text sizing, and a back-to-live shortcut. Command boundaries and exit status cannot be inferred reliably from arbitrary output, so the Commands view remains empty when the process emits no markers." + ], + [ + "Connect an agent", + "Connect agent creates a private, revocable invitation scoped to this terminal. Read access can inspect the screen, tasks, and changes; control access can additionally submit one complete prompt or command against a recent sequence. The connector returns a session ID that should be reused for requests, status, bounded waits, and collection instead of pairing again for every follow-up.", + [ + [ + "Invitation", + "Single-use, private, and short-lived; never place it in command arguments, URLs, or logs." + ], + [ + "Read terminal", + "Inspect and wait without sending terminal input." + ], + [ + "Read and control", + "Also submit input and supported key signals; still constrained by draft and sequence guards." + ], + [ + "Revoke access", + "Disconnect immediately without sending Ctrl-C or stopping the shared process." + ] + ] + ], + [ + "Tasks and completion", + "Each handoff uses a stable task ID. Retrying the same ID and prompt retrieves the existing task rather than submitting it twice. Completion requires a shell marker, a real host callback, or an explicitly labelled agent observation; output, silence, redraws, and background acknowledgements are not completion. Collecting an answer keeps the connection available for another task." + ], + [ + "Protect local drafts", + "Remote writes require the expected sequence from a fresh read. Local keyboard or IME input invalidates stale ownership, and Enter requires an agent-owned draft. A generic TUI does not expose its editor model through terminal cells, so composer state remains unknown unless the owner verifies it is empty or a trusted host integration reports semantic application state. Colors and cursor placement are display evidence, never permission to type." + ], + [ + "Backed file references", + "Refstream's optional file module can preview or download only paths the host explicitly registered. In shell.online, the host must first opt in with --files or --files-root; merely printing a filename never reads the disk. File authorization remains independent from agent terminal access, and traversal, symlink escape, non-regular files, ambient credentials, and automatic URL fetching stay blocked." + ], + [ + "Persistence and privacy", + "Task records, answers, parser state, and retained terminal output can survive panel changes. shell.online keeps reload recovery only in the same browser tab for up to four hours; closing the tab forgets that snapshot, and recovery still requires the local PTY process to be alive. The relay stores no terminal output or browser snapshot. Treat any exported or host-saved snapshot like the terminal itself." + ], + [ + "Alpha boundaries", + "Refstream is not binary-compatible with the xterm addon ecosystem and does not yet implement every private or uncommon xterm behavior. Physical-device and deployment-specific NAT or TURN behavior need separate testing. If a TUI renders or handles input incorrectly, switch back to xterm.js and report a minimal reproduction without private terminal contents.", + [ + [ + "Refstream alpha.5 release", + "https://github.com/TeoSlayer/refstream.js/releases/tag/v0.1.0-alpha.5" + ], + [ + "Persistent agent protocol", + "https://github.com/TeoSlayer/refstream.js/blob/v0.1.0-alpha.5/docs/agents.md" + ], + [ + "Backed files and WebRTC", + "https://github.com/TeoSlayer/refstream.js/blob/v0.1.0-alpha.5/docs/files.md" + ] + ] + ] + ] + }, "reliability": { "eyebrow": "Process continuity", "title": "The link can disappear. Your process should not.", @@ -631,6 +797,10 @@ "Scale and persistence", "This deployment is deliberately single-node. Session metadata survives restarts in the relay-state volume and persistent clients reconnect with the same URL. Live WebSockets reconnect after a restart. Back up the volume, but do not run multiple relay replicas against one state file." ], + [ + "Optional accounts app", + "The terminal relay works without accounts. A self-hosted app deployment lives in app/ and currently uses Firebase Authentication plus PostgreSQL. Keycloak is reserved for a future self-hosted adapter; it is not implemented in this release and must not be copied into the app.shell.online production configuration." + ], [ "Cloudflare deployment remains available", "wrangler.example.jsonc is still the supported global edge deployment. The standalone server is the portable path for one host; it does not include the optional accounts app or hosted analytics dashboard." diff --git a/docs/self-hosting.md b/docs/self-hosting.md index f725a6b..66bcf81 100644 --- a/docs/self-hosting.md +++ b/docs/self-hosting.md @@ -71,5 +71,11 @@ Objects, Rate Limiting, Analytics Engine, and static assets. ## Accounts app Accounts are optional and do not participate in terminal transport. The app in -`app/` uses Firebase Authentication and PostgreSQL; its local Docker deployment -is documented in [`app/README.md`](../app/README.md). +`app/` uses Firebase Authentication and PostgreSQL in the current release; its +local Docker deployment is documented in [`app/README.md`](../app/README.md). + +Keycloak is reserved for an optional self-hosted authentication adapter. It is +not implemented in this release and is not configured or deployed on +`app.shell.online`. A future contribution must keep its settings in the +self-hosted deployment path rather than changing the hosted production identity +provider. diff --git a/public/llms.txt b/public/llms.txt index 3ba8800..6fd2f2b 100644 --- a/public/llms.txt +++ b/public/llms.txt @@ -86,6 +86,21 @@ inside the current tab for at most four hours. Recovery still requires the local shell process to be running, and the relay stores no terminal output. File access remains a separate host opt-in. +Refstream is currently v0.1.0-alpha.5. It owns only the browser terminal model +and renderer; shell.online continues to own the PTY, E2EE transport, read-only +policy, input lease, shared grid, and process lifecycle. Reuse the connector's +session ID and stable task IDs for follow-ups. A task is complete only with a +shell marker, a trusted host callback, or an explicitly labelled agent +observation; output, silence, and redraws are not completion. Remote writes use +a recent sequence and never override a protected local draft. See +https://shell.online/refstream/ and the pinned upstream protocol at +https://github.com/TeoSlayer/refstream.js/blob/v0.1.0-alpha.5/docs/agents.md. + +The optional account app is documented at https://shell.online/app/. A vault is +personal rather than team-wide, and machine remote-start permission is explicit. +The hosted app uses Firebase Authentication. Keycloak is not enabled in this +release and is reserved for a future self-hosted adapter. + Manage local sessions: shell help diff --git a/public/sitemap.xml b/public/sitemap.xml index 97a84ed..78f2d15 100644 --- a/public/sitemap.xml +++ b/public/sitemap.xml @@ -5,9 +5,11 @@ 2026-09-02 https://shell.online/docs/2026-09-02 + https://shell.online/app/2026-09-14 https://shell.online/cli/2026-09-02 https://shell.online/platforms/2026-09-04 https://shell.online/mobile/2026-08-31 + https://shell.online/refstream/2026-09-14 https://shell.online/reliability/2026-08-31 https://shell.online/security/2026-09-02 https://shell.online/e2ee/2026-09-02 diff --git a/scripts/test-landing-seo.mjs b/scripts/test-landing-seo.mjs index 0d4fccf..ab1d8b5 100644 --- a/scripts/test-landing-seo.mjs +++ b/scripts/test-landing-seo.mjs @@ -130,23 +130,23 @@ for (const example of [ check(sitemap.includes("https://shell.online/"), "Homepage is missing from sitemap"); check(sitemap.includes("2026-09-02"), "Sitemap lastmod is missing"); -check((sitemap.match(//gu) ?? []).length === 10, "Sitemap should list the homepage and knowledge base"); +check((sitemap.match(//gu) ?? []).length === 12, "Sitemap should list the homepage and knowledge base"); check(documentationHtml.includes("__DOC_DESCRIPTION__"), "Documentation description build token is missing"); check(documentationHtml.includes("__DOC_SOCIAL_TITLE__"), "Documentation title build token is missing"); check(documentationHtml.includes("__DOC_SOCIAL_DESCRIPTION__"), "Documentation social-description build token is missing"); check(documentationHtml.includes("__DOC_PATH__"), "Documentation canonical-path build token is missing"); check(documentationHtml.includes('https://shell.online/${path}/`), `${path} is missing from sitemap`); } -for (const guide of ["platforms", "mobile", "reliability", "security", "e2ee", "docker"]) { +for (const guide of ["app", "platforms", "mobile", "refstream", "reliability", "security", "e2ee", "docker"]) { check(readme.includes(`https://shell.online/${guide}/`), `README ${guide} guide link is missing`); } -for (const guarantee of ["Any connected phone selects", "Paste input is split", "authenticated ciphertext", "e2ee_password", "docker compose up --build -d"]) { +for (const guarantee of ["Any connected phone selects", "Paste input is split", "authenticated ciphertext", "e2ee_password", "docker compose up --build -d", "Refstream v0.1.0-alpha.5", "The vault is per account, not per organization"]) { check(docsSource.includes(guarantee), `Versioned documentation guarantee is missing: ${guarantee}`); } check(docsSource.includes("terminal_size control messages"), "E2EE docs must disclose plaintext terminal-size control metadata"); @@ -154,7 +154,7 @@ check(!docsSource.includes("snapshots, resizes, and latency probes are authentic check(readme.includes("end-to-end encrypted by default"), "README default E2EE summary is missing"); check(readme.includes("--no-e2ee"), "README explicit E2EE opt-out is missing"); check(docsContent.version === packageMetadata.version, "Documentation version must match package version"); -for (const page of ["docs", "cli", "platforms", "mobile", "reliability", "security", "e2ee", "docker"]) { +for (const page of ["docs", "app", "cli", "platforms", "mobile", "refstream", "reliability", "security", "e2ee", "docker"]) { check(Array.isArray(docsContent.pages?.[page]?.cards), `Versioned documentation page is missing: ${page}`); } check(documentationRenderer.includes('import documentationSource from "../docs/content.json"'), "Website must render from the repository documentation source"); diff --git a/shared/documentation.ts b/shared/documentation.ts index 1d855df..d62eaed 100644 --- a/shared/documentation.ts +++ b/shared/documentation.ts @@ -1,8 +1,10 @@ export const DOCUMENTATION_KINDS = [ "docs", + "app", "cli", "platforms", "mobile", + "refstream", "reliability", "security", "e2ee", @@ -42,6 +44,7 @@ export const DOCUMENTATION_NAVIGATION: readonly { section: "Get started", entries: [ { kind: "docs", label: "Overview" }, + { kind: "app", label: "Web app" }, { kind: "platforms", label: "Platforms and devices" }, ], }, @@ -49,6 +52,7 @@ export const DOCUMENTATION_NAVIGATION: readonly { section: "Terminal experience", entries: [ { kind: "mobile", label: "Mobile terminals" }, + { kind: "refstream", label: "Refstream (alpha)" }, { kind: "reliability", label: "Reliability" }, ], }, diff --git a/tests/documentation.test.ts b/tests/documentation.test.ts index b9900b5..7c1a1ce 100644 --- a/tests/documentation.test.ts +++ b/tests/documentation.test.ts @@ -22,7 +22,10 @@ const historicalContent: DocumentationContent = { describe("documentation routes", () => { it("resolves current and versioned routes", () => { expect(resolveDocumentationRoute("/cli/", "0.11.2")).toEqual({ kind: "cli", version: "0.11.2" }); + expect(resolveDocumentationRoute("/app/", "0.11.2")).toEqual({ kind: "app", version: "0.11.2" }); + expect(resolveDocumentationRoute("/refstream/", "0.11.2")).toEqual({ kind: "refstream", version: "0.11.2" }); expect(resolveDocumentationRoute("/docs/v0.6.0/mobile/", "0.11.2")).toEqual({ kind: "mobile", version: "0.6.0" }); + expect(resolveDocumentationRoute("/docs/v0.15.1/refstream/", "0.15.1")).toEqual({ kind: "refstream", version: "0.15.1" }); expect(resolveDocumentationRoute("/docs/v0.6.0/", "0.11.2")).toEqual({ kind: "docs", version: "0.6.0" }); expect(resolveDocumentationRoute("/docs/v0.6/mobile/", "0.11.2")).toBeNull(); }); @@ -40,6 +43,7 @@ describe("documentation routes", () => { it("recognizes versioned document paths only", () => { expect(isVersionedDocumentationPath("/docs/v0.6.0/security/")).toBe(true); + expect(isVersionedDocumentationPath("/docs/v0.15.1/app/")).toBe(true); expect(isVersionedDocumentationPath("/docs/v0.6.0/")).toBe(true); expect(isVersionedDocumentationPath("/docs/")).toBe(false); }); diff --git a/web/documentation.ts b/web/documentation.ts index a05e001..1fc808d 100644 --- a/web/documentation.ts +++ b/web/documentation.ts @@ -130,6 +130,10 @@ function documentationLink( function documentationCommand(kind: DocumentationKind): string { if (kind === "docs") return `
$ curl -fsSL https://shell.online/install | sh
 $ shell --read-only python train.py
`; + if (kind === "app") return `
$ shell login
+$ shell daemon status
`; + if (kind === "refstream") return `
Terminal controls → Renderer
+Refstream (unstable alpha)
`; if (kind === "e2ee") return `
$ shell <command>
 $ SHELL_ONLINE_E2EE_PASSWORD='…' shell <command>
`; if (kind === "docker") return `
$ docker compose up --build -d