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

@hugpy/console

v0.1.1

Published

Portable terminal / file-browser / keeper console for the web — a framework-agnostic mountConsole() module plus a reusable PTY broker. Embeds into any app.

Readme

station-console

A portable terminal / file-browser / LLM-chat console you can drop into any web app. It is the console UI from the LXD station manager, extracted into a mountable JS module plus a reusable backend broker so other applications can embed it without the vm_mgr station chrome.

station-console/
  ui/
    station-console.js   ESM module — export mountConsole(el, opts)
    style.css            all styles scoped under .sc-root (no host collisions)
    vendor/              xterm.js + fit/canvas addons + xterm.css
  server/
    pty_broker.py        aiohttp broker: ws PTY + fs REST + optional LLM proxy
  example/index.html     demo host page that mounts the module
  install.sh             systemd install of the broker

1. The UI module

<link rel="stylesheet" href="ui/vendor/xterm.css">
<link rel="stylesheet" href="ui/style.css">
<script src="ui/vendor/xterm.js"></script>
<script src="ui/vendor/xterm-addon-fit.js"></script>
<script src="ui/vendor/xterm-addon-canvas.js"></script>
<div id="console" style="height:600px"></div>
<script type="module">
  import { mountConsole } from "./ui/station-console.js";
  const api = mountConsole(document.getElementById("console"), {
    backend: "https://my-broker:8801",  // '' = same origin
    vm:      "local",                   // target the broker exposes
    token:   "…",                       // optional API token
    namespace: "app1",                  // localStorage prefix (isolate multiple mounts)
    features: { files: true, llm: true, keeper: true, composer: true },
  });
</script>

xterm.js and the fit addon must be loaded before the module (the canvas addon is optional — it falls back to the DOM renderer). The module owns only the DOM inside the element you pass; the host app keeps its own chrome.

API returned by mountConsole

| method | description | |---|---| | openPane(spec) | open a pane. spec: {type:'shell'} or {type:'keeper', backend, model, skipPerms}. Returns the pane id. | | closePane(id) | close a pane (kills its server session). | | setTarget(name) | switch the whole console to another target (tears down panes, reboots from that target's saved layout). | | getTarget() | current target name. | | setView('term'\|'files'\|'llm') | switch the active view. | | on(event, fn) | subscribe; returns an unsubscribe fn. Events: paneOpen, paneClose, paneExit, targetChange. | | destroy() | close sockets, detach listeners, clear the element. |

Retained features: keeper terminal with model selection, optional --dangerously-skip-permissions, split panes, copy-on-select, the read/write file browser, the streaming LLM chat, and the multiline composer. The winsize-before-spawn fix is built in — a full-screen TUI keeper paints its first frame at the real pane geometry, not a stale 24×80.

2. The broker

Speaks exactly the protocol the module expects. The target resolver is the portable seam — it decides how {name} maps to a PTY/filesystem:

  • --resolver local — a plain shell + the host filesystem. Works anywhere.
  • --resolver lxdlxc exec <name> as an unprivileged dev user (LXD VMs).
# standalone, any host, open in dev:
python3 server/pty_broker.py --resolver local --token "$TOK" --cors-origin '*'

# LXD parity with the original station console:
python3 server/pty_broker.py --resolver lxd --port 8801

| env / flag | meaning | |---|---| | --token / STATION_BROKER_TOKEN | API token (X-Console-Token header or ?token=). | | --set-password | print a pbkdf2 hash; set STATION_BROKER_PASSWORD_HASH to require a login. | | --cors-origin / STATION_BROKER_CORS_ORIGIN | * or comma-separated origins — required for cross-origin embeds. | | --llm-base / STATION_BROKER_LLM_BASE | OpenAI-compatible gateway base URL; enables /api/llm/models + /chat. Omit → chat shows "no models". | | --cert / --key | enable TLS. | | STATION_BROKER_DIRECTIVE | global keeper directive file (default /opt/llm-station/directive.md). | | STATION_BROKER_KEEPER_SOCK | tmux socket for persistent keepers (default console). Use a distinct socket to isolate keepers from another console on the same host. | | STATION_BROKER_LOCAL_NAME | station name the local resolver advertises (default local); set to label the host, e.g. hugpy. |

Keeper persistence. A Claude keeper (+🤖) runs inside a tmux session, so it survives a broker restart — the PTY we spawn is just a tmux client; the keeper keeps running (in the VM for lxd, on the host for local) and reconnecting reattaches. Its system prompt layers the global directive above, then a per-target file — /srv/share/projects/<name>/.keeper-directive.md (lxd) or <home>/.keeper-directive.md (local) — each applied only if present.

To add your own target type (ssh, docker, k8s pod…), implement a resolver class (list_targets, safe_path, shell_argv, run, pull, keeper_argv) and register it in main().

3. One-command install

sudo ./install.sh --resolver local --port 8801 --cors-origin 'https://my-app.example'

Generates an API token (printed once), a self-signed TLS cert, and a systemd unit (station-console-broker.service). The broker also serves ui/ at /ui, so a host page can import the module from the same origin.

Relation to vm_mgr

The original console at vm_mgr/console/ is unchanged and still runs the full station manager (VM tabs, lifecycle, provisioning). This package is the reusable extraction of its console core; vm_mgr can later be re-pointed at the module, but nothing here modifies it.