Help us shape the future of Go! Take the 2026 Go Developer Survey

web

package
v0.2.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 9, 2026 License: Apache-2.0 Imports: 35 Imported by: 0

Documentation

Overview

Package web provides an embedded web server that mirrors the rysh TUI in a browser. It connects to the same NATS bus as the TUI, polls workspace snapshots, and pushes them to WebSocket clients. Commands from the browser are published back to NATS so the actor hierarchy processes them identically.

Parity API surface (web_electron_roadmap Phase 3, tasks W7–W10).

The Electron desktop app gets a handful of conveniences from its main process (`window.electronAPI`): env/platform info, workspace reads, voice transcription and shell tab-completion. In web mode there is no Electron main process — but the daemon this server runs inside owns the same data and the same Go engines, so each capability is served here instead:

W9  GET  /api/env             → is_web flag + platform + session + capability map
W8  GET  /api/workspaces      → current workspace + on-disk session registry
W10 GET  /api/voice/config    → public voice config (never the API key)
W10 POST /api/voice/transcribe→ audio body → transcript (internal/voice)
W7  ws   completion_get       → completion_result (internal/tui engine),
                                replied ONLY to the requesting client

All /api/* routes sit behind the same access-token middleware as the rest of the server (auth.go); nothing here widens the surface. Everything is read-only except transcription, which only calls the configured provider.

Control-dashboard surface (WS5, design 005 — tasks DB1–DB4).

This file adds the browser control plane on top of the read-only viewer in server.go:

  • DB1 channel operations → MsgHumanoidChannelStart/Stop on humanoid.{name}.inbox (same messages ##humanoid channel start/stop send)
  • DB2 pairing operations → MsgChannelPairApprove / MsgChannelAllow / MsgChannelPairList on humanoid.{name}.pairing (same subject the terminal ##humanoid pair commands ride, WS3 / design 003 §4.4), plus a live relay of MsgChannelPairRequest/QR/Status/ListReply into the WS hub
  • DB3 humanoid operations → MsgHumanoidActivate/Deactivate on the registry inbox and MsgHumanoidSetGovernance / MsgHumanoidSetReplyMode on humanoid.{name}.inbox
  • DB4 control gating → every MUTATING endpoint/action requires control mode; read/list endpoints stay available. Control mode is enabled by the RYSH_WEB_CONTROL environment variable (1/true/yes/on) read in NewServer — chosen as the least-invasive wiring so the two existing WorkspaceActor NewServer call sites need no changes — or programmatically via SetControl (tests, future `rysh web --control`). When control is enabled the HTTP listener is forced onto 127.0.0.1.

Terminal and web drive the SAME typed messages on the SAME subjects, so the two fronts cannot diverge (design 003 §4.4 / 005 G5).

Server-side embedded web panes for browser mode (web_electron_roadmap W12).

In the Electron app a web pane is a native WebContentsView overlaid on the pane. A plain browser tab cannot embed arbitrary third-party pages (X-Frame-Options / CSP) nor attach a debugger — so in web mode the server drives a HEADLESS Chromium (internal/cdp, the same engine behind rysh's browser automation) and streams JPEG frames + url/title to the pane over the existing /ws protocol:

client → server: webpane_open {pane_id,url,profile} · webpane_navigate
                 {pane_id,url} · webpane_back/forward/reload/close {pane_id}
server → client: webpane_frame {pane_id,url,title,screenshot}   (~1 fps)
                 webpane_error {pane_id,error}                  (fail visible)

Availability is advertised in /api/env capabilities.web_pane (needs a workspace root for the profile dir and a Chromium binary). When it is false, the renderer keeps its explicit "requires the desktop app" note — the capability degrades visibly, never silently.

Profiles: each rysh web profile maps to its own persistent user-data-dir under <workspace>/.rysh/browser-instances/webpane/<profile> — deliberately NOT the interactive-login profile dir (Chromium locks user-data-dirs, and a headed login browser may hold that lock).

Index

Constants

View Source
const AccessTokenTTL = 30 * 24 * time.Hour

AccessTokenTTL is how long a successful login stays valid: one month.

View Source
const DefaultHost = "127.0.0.1"

DefaultHost is the bind address used when none is configured: loopback, so a rysh web session is reachable only from the machine running the daemon unless the operator explicitly opts into a wider bind (`--bind 0.0.0.0`, web host, RYSH_WEB_HOST). The login is a guard, not a perimeter — binding the default surface to a LAN-visible interface is a decision the operator must make deliberately.

Variables

This section is empty.

Functions

func ClearCredentials added in v0.2.3

func ClearCredentials(ryshDir string) (bool, error)

ClearCredentials removes the credentials file, disabling username/password auth. Reports whether a file was actually there to remove.

func CredentialsPath added in v0.2.3

func CredentialsPath(ryshDir string) string

CredentialsPath returns the credentials file for a rysh dir.

Types

type Credentials added in v0.2.3

type Credentials struct {
	Username     string `json:"username"`
	PasswordHash string `json:"password_hash"`
	// Secret is the base64url HS256 signing key for issued access tokens.
	Secret    string `json:"secret"`
	UpdatedAt string `json:"updated_at"`
}

Credentials is the on-disk shape of web-auth.json.

func LoadCredentials added in v0.2.3

func LoadCredentials(ryshDir string) (*Credentials, error)

LoadCredentials reads the credentials for a rysh dir. A missing file is not an error — it means username/password auth is simply not configured, and (nil, nil) is returned. A file that exists but is unusable IS an error: a corrupt credentials file must not silently downgrade to "no auth".

func SaveCredentials added in v0.2.3

func SaveCredentials(ryshDir, username, password string) (*Credentials, error)

SaveCredentials hashes password, mints a fresh signing key, and writes the credentials file (0600 in a 0700 dir). Returns what was written.

func (*Credentials) SigningKey added in v0.2.3

func (c *Credentials) SigningKey() []byte

SigningKey decodes the HS256 key used to sign and verify access tokens. Returns nil when the stored key is unusable — parseJWT rejects an empty key, so a broken file fails every token rather than accepting every token.

func (*Credentials) Verify added in v0.2.3

func (c *Credentials) Verify(username, password string) bool

Verify reports whether a login attempt matches. bcrypt runs even when the username is wrong so a bad username and a bad password cost the same time, and neither answer is distinguishable from the outside.

type Hub

type Hub struct {
	// contains filtered or unexported fields
}

Hub maintains the set of active WebSocket clients and broadcasts messages to all of them. Modelled after the Gorilla WebSocket chat example.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server is the web UI server. It serves static assets over HTTP and maintains WebSocket connections to push real-time workspace snapshots to browsers.

func NewServer

func NewServer(port int, sessionName string, pub *msg.NATSPublisher, nc *nats.Conn, codecs *msg.CodecRegistry) *Server

NewServer creates a new web UI server.

func (*Server) ControlEnabled

func (s *Server) ControlEnabled() bool

ControlEnabled reports whether control mode is enabled.

func (*Server) Host

func (s *Server) Host() string

Host returns the configured bind IP ("" = default).

func (*Server) IsRunning

func (s *Server) IsRunning() bool

IsRunning reports whether the server is currently running.

func (*Server) LoginEnabled added in v0.2.3

func (s *Server) LoginEnabled() bool

LoginEnabled reports whether a username/password login is configured.

func (*Server) LoginUsername added in v0.2.3

func (s *Server) LoginUsername() string

LoginUsername returns the configured username, or "" when login is off.

func (*Server) Port

func (s *Server) Port() int

Port returns the configured port number.

func (*Server) SetBashCompletion added in v0.2.3

func (s *Server) SetBashCompletion(enabled bool)

SetBashCompletion enables delegation to bash's programmable completion specs for argument-position tab-completion (config [ui] shell_bash_completion). Built-in command/path completion is always on.

func (*Server) SetClientCountListener

func (s *Server) SetClientCountListener(fn func(n int))

SetClientCountListener registers fn to receive the live WebSocket-client count on every transition. Must be called before Start.

func (*Server) SetControl

func (s *Server) SetControl(enabled bool)

SetControl enables or disables control mode. Must be called before Start: the listen address is computed at Start time and mutating endpoints consult the flag per-request under the server mutex.

func (*Server) SetCredentials added in v0.2.3

func (s *Server) SetCredentials(c *Credentials)

SetCredentials installs (or, with nil, removes) the username/password login. Safe to call while the server is running: `##rysh web auth` applies new credentials to a live server, and because saving credentials mints a fresh signing key, every token issued under the old password stops verifying.

func (*Server) SetFSBrowser

func (s *Server) SetFSBrowser(fn func(op, paneID, path string, offset, length int64) (any, string, string))

SetFSBrowser injects the file-browse implementation. Call before Start.

func (*Server) SetHost

func (s *Server) SetHost(host string)

SetHost pins the HTTP listener to a specific bind IP — 0.0.0.0 to expose the UI on every interface, a dedicated loopback alias like 127.0.100.1 fronted by a reverse proxy, and so on. Empty selects DefaultHost (loopback). Must be called before Start.

func (*Server) SetShared added in v0.2.3

func (s *Server) SetShared(host string, port int)

SetShared configures the shared door to be opened at Start. Host empty means DefaultHost. Use StartShared to open one on a server already running.

func (*Server) SetVoice added in v0.2.3

func (s *Server) SetVoice(v VoiceSettings)

SetVoice injects the daemon's voice configuration. Call before Start.

func (*Server) SetWorkspaceInfo added in v0.2.3

func (s *Server) SetWorkspaceInfo(path, name string)

SetWorkspaceInfo records the workspace this session runs in (working directory + display name), served by /api/env and /api/workspaces and used as the root for server-side web-pane browser profiles (W12).

func (*Server) SetWorkspaceLister added in v0.2.3

func (s *Server) SetWorkspaceLister(fn func() []WorkspaceEntry)

SetWorkspaceLister injects the recent-workspace source (the on-disk session registry, read by the actors package — this package cannot import it). nil ⇒ /api/workspaces returns only the current workspace.

func (*Server) SharedAddr added in v0.2.3

func (s *Server) SharedAddr() (string, int)

SharedAddr returns the shared door's host and port, or ("", 0) when closed.

func (*Server) Start

func (s *Server) Start() error

Start launches the web server in background goroutines.

func (*Server) StartShared added in v0.2.3

func (s *Server) StartShared(host string, port int) error

StartShared opens the shared door on host:port. The server must be running, and credentials must be configured: the whole point of this listener is that it demands them, so opening one that could not check a password would serve the session to anyone who found the port.

Opening a second shared door closes the first — one shared address at a time, so there is never an old port still serving after a rebind.

func (*Server) Stop

func (s *Server) Stop() error

Stop gracefully shuts down the web server.

func (*Server) StopShared added in v0.2.3

func (s *Server) StopShared() error

StopShared closes the shared door, leaving the private one (and every client on it) untouched. A no-op when none is open.

func (*Server) TrackCredentialsFile added in v0.2.4

func (s *Server) TrackCredentialsFile(ryshDir string)

TrackCredentialsFile tells the server which workspace rysh dir holds its web-auth.json, making that FILE the source of truth rather than whatever snapshot was installed at start.

This exists because credentials are per-workspace state shared by every daemon rooted there, while `creds` is per-process. A second daemon running `##rysh web auth` rewrites the file and mints a fresh signing key — correctly, that is how changing the password logs old browsers out — and this process, still verifying against the superseded key, then rejects every client with a 401 that no user can diagnose or fix (F-9). Restarting the listener "fixed" it precisely because a restart is the only thing that re-read the file.

Not a watcher: a stat per auth decision costs a syscall, needs no goroutine and no lifecycle, and cannot leak a watch when the server stops.

type VoiceSettings added in v0.2.3

type VoiceSettings struct {
	Enabled  bool
	Provider string // "deepgram" (default) | "whisper"
	APIKey   string
	Hotkey   string // e.g. "ctrl+r"
	Language string // optional BCP-47 hint
}

VoiceSettings is the web server's view of the daemon's voice configuration (voice + [voice_control] in rysh.config.yaml). Injected by the workspace actor via SetVoice before Start. The APIKey never leaves the server.

type WorkspaceEntry added in v0.2.3

type WorkspaceEntry struct {
	Path        string `json:"path"`
	Name        string `json:"name"`
	LastOpened  string `json:"last_opened,omitempty"`
	LastSession string `json:"last_session,omitempty"`
}

WorkspaceEntry is one workspace visible to the web UI: the current one, or a recent one recorded in the on-disk session registry.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL