Skip to content

feat: add a browser GUI (sndcli --gui) - #16

Open
lavisor wants to merge 2 commits into
baairon:mainfrom
lavisor:feature/gui
Open

lavisor wants to merge 2 commits into
baairon:mainfrom
lavisor:feature/gui

Conversation

@lavisor

@lavisor lavisor commented Oct 11, 2026 •

Copy link
Copy Markdown

What

Adds an optional browser front end to soundcli. sndcli --gui starts a small local server and opens the same library, player and download queue in a browser tab. The terminal app is unchanged.

sndcli --gui                 # build-free when installed from npm
sndcli --gui --port 4000     # pick a port
sndcli --gui --no-open       # print the link instead of opening it
npm run gui                  # from a clone: builds the page, then starts it

Why

Some people would rather click through a library than learn the key bindings. The GUI offers a second way in without changing how the TUI works.

How

  • Shared core. The app state and actions that lived in src/ui/App.tsx moved into src/core/createApp.ts. The Ink TUI and the GUI server both use it, so the two front ends can't drift apart. App.tsx shrinks by about 200 lines as a result.
  • Server. src/gui/server.ts serves the built page and a WebSocket. It sends a snapshot of the state on connect, then sends playback, queue and library updates as they happen.
  • Protocol. src/gui/protocol.ts defines the messages and contains types only, so the web bundle never imports Node code.
  • Web page. web/ is a small Vite + React app with Songs, Download and Settings sections and a Now Playing bar.
  • CLI. --gui, --port <n> and --no-open are parsed in src/cli/args.ts. The port is validated as an integer from 1 to 65535, and the help text is updated.
  • Build. npm run build now also builds the web page. typecheck covers web/ too. The new dependencies are ws, vite, @vitejs/plugin-react and react-dom, plus their type packages.

Security

The GUI drives a process that can play audio, download and delete files, so it is locked down:

  • It binds to 127.0.0.1 only.
  • Each launch generates a random token, which the page and the WebSocket upgrade both require. The check is constant-time.
  • The server rejects WebSocket connections whose Origin isn't its own loopback origin, so other sites can't reach it.

Scope and known limits

  • Changing the library folder and converting formats are still terminal-only.
  • Run either the TUI or the GUI at a time, since both write to the same library. The README says so.
  • package-lock.json has a large diff because of the new dependencies.

Testing

  • npm run typecheck
  • npm test (adds test/gui-server.test.ts and extends test/cli-args.test.ts)
  • npm run gui and play, queue a download, and rename or delete a track in the browser

Docs

README has a new "Prefer a window?" section.

@lavisor
lavisor marked this pull request as ready for review October 11, 2026 07:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant