npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

keystream-cli

v0.0.1

Published

Real-time terminal chat with Vim-style navigation training.

Readme

Keystream

Keystream is a real-time terminal chat app built with Bun. It is designed to do two jobs at once:

  • let multiple users chat live from the CLI
  • turn normal chat usage into Vim-style keyboard practice

The app has a standalone Bun WebSocket server, a full-screen terminal client, persistent SQLite history, room-based chat, and a modal UI with NORMAL, INSERT, COMMAND, and SEARCH modes.

Key Features

  • Real-time multi-user chat over WebSockets
  • Persistent message history stored in SQLite
  • Room-based messaging with live room switching
  • Full-screen terminal UI built for keyboard-first use
  • Vim-style modal interaction
  • Motion counts like 5j
  • Feed jumps with gg and G
  • Search mode with /
  • Command mode with :
  • Strict Vim mode that blocks arrow keys and tracks violations
  • Lightweight training stats for mode time, streaks, and arrow-key usage
  • Auto-reconnect behavior in the CLI client
  • Local multi-client practice launcher with tmux

Current Scope

This version is intentionally narrow:

  • authentication is username-only
  • chat is room-first
  • direct messages are not implemented
  • file upload, reactions, and message editing are not implemented
  • this is best suited for trusted users or local/LAN usage unless you add TLS and stronger access control around it

Requirements

  • Bun 1.3+
  • macOS, Linux, or another environment where Bun and terminal WebSocket clients work
  • tmux only if you want to use bun run dev:stack

Install

bun install

Quick Start

Option 1: Run the full local practice stack

This is the fastest way to try Keystream as a Vim practice tool.

bun run dev:stack

What it does:

  • starts one server
  • opens three CLI clients in a tiled tmux session
  • connects them to the same general room
  • enables --strict-vim in each client

If tmux is not installed, use the manual setup below.

Option 2: Run server and clients manually

Start the server:

bun --cwd apps/server src/index.ts --host 0.0.0.0 --port 3088 --db ./data/keystream.sqlite

Start a client:

bun --cwd apps/cli src/index.ts --url ws://127.0.0.1:3088 --username alice --room general --strict-vim

Start another client in a second terminal:

bun --cwd apps/cli src/index.ts --url ws://127.0.0.1:3088 --username bob --room general --strict-vim

You can omit --strict-vim if you want arrow keys to translate to motions instead of being counted as violations.

Public Install And Distribution

Install the public npm CLI

Once published, the CLI can be installed globally with npm:

npm install -g keystream-cli
keystream --help

The installed command is keystream, while the package name is keystream-cli.

Run the published CLI

keystream --url ws://127.0.0.1:3088 --username alice --room general --strict-vim

Pull the backend Docker image

Once a tagged GitHub release has published the backend image:

docker pull ghcr.io/lihaowang/keystream-server:v0.0.1

Run the backend Docker image with persistent storage

mkdir -p ./keystream-data

docker run --rm \
  -p 3088:3088 \
  -e KEYSTREAM_HOST=0.0.0.0 \
  -e KEYSTREAM_PORT=3088 \
  -e KEYSTREAM_DB=/data/keystream.sqlite \
  -v "$(pwd)/keystream-data:/data" \
  ghcr.io/lihaowang/keystream-server:v0.0.1

This mounted /data path is the persistent SQLite store. If you do not mount it, history will not survive container replacement.

CLI Flags And Env Vars

Server

Command:

bun --cwd apps/server src/index.ts --host 0.0.0.0 --port 3088 --db ./data/keystream.sqlite

Flags:

  • --host bind address, default 0.0.0.0
  • --port listening port, default 3088
  • --db SQLite database path, default ./data/keystream.sqlite

Environment variables:

  • KEYSTREAM_HOST
  • KEYSTREAM_PORT
  • KEYSTREAM_DB

Client

Command:

bun --cwd apps/cli src/index.ts --url ws://127.0.0.1:3088 --username alice --room general --strict-vim

Flags:

  • --url WebSocket server URL, default ws://127.0.0.1:3088
  • --username username to claim on connect
  • --room initial room name, default general
  • --strict-vim block arrow keys and record violations

Environment variables:

  • KEYSTREAM_URL
  • KEYSTREAM_USERNAME
  • KEYSTREAM_ROOM
  • KEYSTREAM_STRICT_VIM=1

Flag precedence is higher than environment variables.

Published npm usage:

keystream --url ws://127.0.0.1:3088 --username alice --room general --strict-vim

How To Use Keystream

Screen layout

The client screen has four regions:

  • left pane: room list
  • main pane: message feed for the current room
  • bottom input pane: mode-specific input area
  • status line: mode, username, room, training stats, and status messages

Modes

Keystream is modal. Most interaction starts in NORMAL mode.

NORMAL

Use this mode to navigate rooms and messages.

  • i, a, o: enter INSERT mode
  • :: enter COMMAND mode
  • /: enter SEARCH mode
  • h and l: move between rooms
  • j and k: move through messages in the current feed
  • 5j, 10k, etc.: counted motions
  • gg: jump to the top of the current feed
  • G: jump to the bottom of the current feed
  • Ctrl-u: move up 10 messages
  • Ctrl-d: move down 10 messages
  • Ctrl-c: quit immediately

INSERT

Use this mode to compose and send a message.

  • type to edit the current message buffer
  • Backspace: delete one character
  • Enter: send the message to the active room
  • Esc: return to NORMAL

COMMAND

Use this mode for room and app commands.

  • starts with :
  • type a command, then press Enter
  • Esc cancels command mode

SEARCH

Use this mode to filter the current room feed.

  • starts with /
  • type a search term, then press Enter
  • search is case-insensitive
  • only the current room feed is filtered
  • empty search clears the filter
  • Esc cancels search mode

Built-in commands

  • :join <room>: join or create a room and switch to it
  • :leave: leave the current room on the server
  • :rooms: show known rooms in the status line
  • :users: show active occupants in the current room
  • :history [limit]: load older messages before the earliest loaded message
  • :stats: show mode timing, streak, and arrow-key stats
  • :help: print the command list
  • :commands: alias for :help
  • :quit: exit the client
  • :q: alias for :quit

Vim training behavior

Keystream is not just a terminal chat client with some Vim keys added. The app is intentionally structured to train habits through repetition.

  • Navigation is concentrated in NORMAL mode.
  • Writing happens in INSERT mode.
  • Commands happen in COMMAND mode.
  • Search happens in SEARCH mode.
  • The status line tracks clean-motion streaks and arrow-key violations.
  • In strict mode, arrow keys are blocked and replaced with a hint to use h, j, k, l.

Typical workflow

  1. Start the server.
  2. Open one or more clients.
  3. Land in NORMAL mode.
  4. Press i to enter INSERT mode and type a message.
  5. Press Enter to send it.
  6. Press Esc to go back to NORMAL.
  7. Use j, k, gg, G, and search to move through the feed.
  8. Use :join dev to switch into another room.
  9. Use :stats to check your mode time and arrow-key violations.

Development Guide

Repository layout

apps/
  cli/      Terminal client
  server/   Bun WebSocket server + SQLite persistence
packages/
  shared/   Shared protocol, types, and validation
scripts/
  dev-stack.ts   Local tmux-based practice launcher
test/
  integration/   End-to-end server integration tests

Main modules

  • packages/shared/src/protocol.ts: typed wire protocol and runtime validation
  • apps/server/src/chat-server.ts: WebSocket lifecycle and room/message coordination
  • apps/server/src/store.ts: SQLite schema and persistence logic
  • apps/cli/src/app.ts: modal terminal UI and client-side state machine
  • apps/cli/src/client.ts: reconnecting WebSocket transport
  • apps/cli/src/modal-state.ts: Vim training state and motion logic
  • apps/cli/src/commands.ts: command parsing

Install dependencies

bun install

Run tests

Run all tests:

bun test

Run the TypeScript checks:

bunx tsc -p packages/shared/tsconfig.json --noEmit
bunx tsc -p apps/server/tsconfig.json --noEmit
bunx tsc -p apps/cli/tsconfig.json --noEmit

Build release artifacts locally

Build the Node-compatible npm CLI bundle:

bun run build:cli

Dry-run the npm package tarball:

bun run pack:cli --dry-run

Build the backend Docker image:

bun run docker:build:server

Run the backend Docker smoke test:

bun run docker:smoke:server

Recommended development loop

  1. Start the server in one terminal.
  2. Start one or more clients in separate terminals.
  3. Use bun test after behavior changes.
  4. Re-run the TypeScript checks before finishing a change.
  5. Use bun run dev:stack when you want rapid multi-user keyboard practice.

Manual smoke test

Use this after touching the server-client interaction path:

  1. Start the server on a local port.
  2. Open two clients with different usernames.
  3. Confirm both receive welcome and room_snapshot behavior.
  4. Send messages from one client and confirm the other updates live.
  5. Join a new room with :join.
  6. Use / search and :history.
  7. Run :stats and confirm Vim training counters update.

Persistence notes

  • The server uses SQLite and creates the database directory automatically.
  • The default database path is ./data/keystream.sqlite.
  • The local dev stack uses ./data/dev-stack.sqlite.
  • Message history survives server restarts when you reuse the same database path.

Networking notes

  • The server speaks WebSocket only.
  • The client reconnects automatically if the connection drops.
  • Duplicate active usernames are rejected.
  • Room message ordering is assigned by the server per room using monotonically increasing sequence numbers.

Scripts

  • bun run build: workspace build/typecheck script entrypoint
  • bun run check: alias for bun test
  • bun run build:cli: build the public Node-compatible CLI package into apps/cli/dist
  • bun run pack:cli --dry-run: generate and inspect the npm tarball from apps/cli/dist
  • bun run docker:build:server: build the local backend container image
  • bun run docker:smoke:server: build, run, restart, and verify the Dockerized backend with persisted SQLite data
  • bun run release:check: run the release validation path used before tagging
  • bun run dev:server: run the server package script
  • bun run dev:cli: run the CLI package script
  • bun run dev:stack: launch the tmux multi-client practice environment

Deployment Guide

Backend container

The Docker image targets the backend only. It does not package the CLI.

  • Registry: ghcr.io/lihaowang/keystream-server
  • Default internal port: 3088
  • Default container DB path: /data/keystream.sqlite
  • Supported runtime configuration: KEYSTREAM_HOST, KEYSTREAM_PORT, KEYSTREAM_DB

Recommended deployment pattern:

  1. Mount a persistent volume to /data
  2. Expose port 3088 or reverse-proxy it
  3. Front the service with TLS if it is reachable outside a trusted LAN
  4. Reuse the same mounted database volume across restarts

GitHub Container Registry release path

The backend image is published by GitHub Actions on version tags:

  • workflow: .github/workflows/release-server-image.yml
  • trigger: v* tags
  • image tags:
    • ghcr.io/lihaowang/keystream-server:<tag>
    • ghcr.io/lihaowang/keystream-server:latest

npm CLI release path

The CLI release path is manual for final publish, but validated in CI:

  1. Build the CLI package with bun run build:cli
  2. Validate the package with bun run pack:cli --dry-run
  3. Log in to npm with npm login
  4. Publish from apps/cli/dist with:
npm publish ./apps/cli/dist --access public

See docs/releasing.md for the maintainer flow.

Known Limitations

  • :leave acknowledges on the server but the UI does not implement a separate “roomless” screen state.
  • Presence updates are modeled in the protocol but not yet rendered as live status changes in the UI.
  • The README examples use direct Bun entrypoints because they are the most explicit and reliable during development.
  • Public internet deployment should be fronted by TLS and stronger access control if you plan to expose it beyond a trusted environment.

Future Improvements

  • Direct messages
  • Better presence rendering
  • Message editing and reactions
  • Room privacy and invitations
  • Richer training metrics and drills
  • PTY-level end-to-end tests for the full terminal UI