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

@dylanpiercey/work

v0.2.52

Published

Web GUI for agentic dev workspaces

Readme

work

A self-hosted web GUI for agentic dev workspaces — on desktop and as a mobile PWA. Spin up multi-repo worktree workspaces, run coding agents (Claude Code, Codex, Copilot, Cursor, Grok) or plain shells in real terminals, review diffs and send feedback to agents, and watch PR status — from any device.

  • Workspaces — named directories of git worktrees (one per GitHub repo, cloned from a shared bare-repo cache), all on a generated branch. Create, add/remove repos, archive/restore, delete.
  • Sessions — real CLIs in tmux, mirrored to the browser over WebSocket (xterm.js). Sessions survive server restarts and phone locks; agent hooks surface working / waiting / idle state at a glance.
  • Review — a fast diff view across every repo in the workspace, with line comments you can send straight into an agent session.
  • Mobile-first PWA — installable, with a touch-tuned terminal (sticky keyboard toggle, d-pad for arrows/tab/ctrl-c, momentum scrolling).

Install (bare machine)

One command on a fresh Linux or macOS box — installs the system tools (git, tmux, a compiler for node-pty), mise + node, the published package, and the background service (systemd/launchd):

curl -fsSL https://cdn.jsdelivr.net/npm/@dylanpiercey/work/install.sh | bash

Already have node (and git/tmux)? npm i -g @dylanpiercey/work && work setup does the same minus the system tools. Update later with work update (or re-run the installer).

Develop

Requires Linux or macOS with git, tmux, gh (authenticated), and Node 24+. The bootstrap script installs everything via mise, offers the agent CLIs, and runs pnpm install:

./scripts/bootstrap.sh

Run

pnpm dev                  # http://localhost:7171

# While hacking on work itself, point the background service at a checkout
# (and back at the npm install when done):
cp scripts/work-source.sh ~/.local/bin/work-source && chmod +x ~/.local/bin/work-source
work-source local          # this tree (or: work-source local /path/to/work)
work-source rebuild        # rebuild + restart without rewriting the unit
work-source prod           # global @dylanpiercey/work again
work-source                # status

From the command line (and agents)

Everything the app's pages do has a work command too, so an agent — or a script — can use and set up work from a terminal: work help lists them. Inside a workspace, work branches, work session, work topic, work review, work notify and work open; agents start and message each other with work session new|send|wait — any vendor's, each in a tab of its own the user watches, its last message as a turn ends coming back to the one that asked as a prompt — and run groups of them with work session fanout, which the session list shows as a lead's team; to change things, work workspace create, work repo add, work config get|set (checked against what each setting takes; work config schema lists them), work agent|skill|lsp|host and work gh login; work status sums the machine up, and work procs prints each workspace port's link as the user's browser would open it. Every command takes --json (JSON alone on stdout) and never prompts under it or --non-interactive.

Access

work has no login of its own — it runs agents and terminals as you, so it answers this machine only by default (it listens on 127.0.0.1). To reach it from other devices, put it behind something that terminates TLS and authenticates — a reverse proxy with a login, a Cloudflare Tunnel with Access, tailscale serve — and tell work about it:

  • WORK_ALLOWED_HOSTS=work.example.com — the name(s) the proxy serves (their workspace-port subdomains are included). work refuses any other name, which is what stops a web page elsewhere from pointing its own name at your machine (DNS rebinding).
  • If the proxy runs on another machine, work has to listen on the network for it: WORK_LISTEN=0.0.0.0 and WORK_TRUSTED_PROXIES=<proxy ip> (IPs or CIDRs, comma-separated). work then answers that proxy and this machine, and refuses everyone else on the network, who would otherwise walk around the proxy's login. WORK_TRUSTED_PROXIES=* answers any machine — only on a network you trust as it is.

Set them when installing (WORK_ALLOWED_HOSTS=… WORK_TRUSTED_PROXIES=… WORK_LISTEN=0.0.0.0 work setup); work setup keeps them in the service from then on. A refused request gets a page saying which setting it needs. Everything (pages, API, and the /term websockets) serves from the single app port, so route the whole hostname to it.

Tailscale is the zero-config way. On a machine logged in to Tailscale (1.52 or newer), work setup offers to run tailscale serve --bg <port>, which puts work at https://<machine>.<tailnet>.ts.net/ for your devices on the tailnet alone (never Funnel, which would publish it to the internet), and adds that name to WORK_ALLOWED_HOSTS: open it on a phone signed in to the same tailnet. work setup --tailscale does it without asking and --no-tailscale skips the question; --yes alone leaves Tailscale's config as it is. A serve config already pointing at work only has its name allowed, and work doctor says which of these holds.

Direct connections. Behind a proxy, a tunnel or a relay, terminals move to a direct WebRTC connection to this machine when it is measurably faster than the path through the proxy (Settings → Terminal → Direct connection: Auto by default, Always, or Off, one setting for every device). Pages, the API and sign-in still go through your proxy; the direct path is set up over the app's authenticated events socket, carries terminal traffic alone, encrypted (DTLS), to UDP port WORK_RTC_PORT (by default the app's port number), and ends when that login does. That port is held by a helper process of work's own, only while direct connections are in use: it starts at the first one and exits ten minutes after the last. Proxy-side inspection or logging does not see terminal traffic that goes direct: turn the feature off (per device in Settings, or WORK_RTC=off for the machine) where it must. On the LAN the direct path needs nothing more; from another network (a phone on cellular) the UDP port has to be reachable through your router. Settings → Terminal can allow work to open it on the router when needed (UPnP, off by default): only after a device failed to connect without it, leased, and given back once unused. A host firewall can keep a device out too, though usually it does not (ICE gets through a stateful one from most networks), so work says nothing of it until a device has failed to connect directly while nothing has come in through the port. Then work doctor says it drops the port, or may (where only root can read its rules, it judges from the defaults and gives the command that shows them), with the command that admits it, and work setup offers to add that rule, named work so it is removed as one (--firewall adds it without asking, --no-firewall skips the question). macOS's firewall admits programs rather than ports, so there it is the node the service runs that work doctor checks and work setup lets in. WORK_RTC_STUN names the STUN servers that find this machine's public address (Cloudflare's by default; empty for none).

A faster git status. Listing a large repo's untracked files is most of what git status costs, and work asks it of every checkout and topic branch's worktree. So the service turns on git's untracked cache (core.untrackedCache) in the repos work keeps (<work root>/.cache, whose worktrees are every workspace's checkouts), after which git reads again only the directories that changed; your agents' own git status gets faster too. It relies on directory modification times, so it is only turned on where the work root is a local filesystem with fine timestamps (not NFS, SMB, FUSE mounts, HFS+ or FAT) and git's own check passes there. A repo set core.untrackedCache=false is left alone, and git config --global core.untrackedCache false keeps it off everywhere; work doctor says which it is.

Opening workspace ports remotely. The port chips in the sidebar link to http://<host>:<port>, which only works where the machine is reachable directly — under a tailnet name too, since tailscale serve has no wildcard names: there a chip reaches a dev server that listens beyond loopback (--host), and one on loopback says so. Through a tunnel, route a wildcard hostname to the app port as well and work proxies it — HTTP and websockets, so HMR works — to localhost:<port>. WORK_PORT_HOSTS picks the hostname shape the chips link to; sub is always accepted, dash only when picked, and only for a name of three labels or more:

  • sub (default): https://3000.work.example.com. Needs a wildcard *.work.example.com route and a certificate that covers it (your own reverse proxy with a DNS-01 wildcard; Cloudflare only with Advanced Certificate Manager, since Universal SSL covers one label).
  • dash: https://3000-work.example.com. One label under the zone, so a Cloudflare Tunnel works on the free plan. DNS wildcards match a whole label, so the tunnel's public hostname is *.example.com pointing at the app port, with an Access application on the same wildcard so the ports sit behind the same login as the app. Work ignores hostnames that do not start with a port, so other names under the zone are unaffected.

Only loopback ports on the work machine are reachable, behind the same auth as the app. API writes and the terminal websockets refuse a foreign Origin, so a page a workspace serves cannot drive work with the shared login cookie.

Backup, restore, join

A work machine holds almost nothing that lives only there: branches are on origin, tools come from mise's registry, skills from their source repos. So a backup is a small manifest, not a filesystem copy — Settings → Backup → Export (or work export [file]) writes one with the workspaces (ids, names, branches, repos), settings, GitHub hosts, skill sources and mise pins — a few KB, with every default left out (owner/name@base only when the base is not the remote's default, tool@version only when pinned). A workspace is only as recoverable as what its branches have on origin, so the export pre-flight lists anything the file cannot carry — unpushed commits, uncommitted or untracked files, ignored files that are not build junk — and waits for a decision.

Restore (Settings → Backup, or work restore <file> --yes) installs what the file names that the machine lacks — a tool outside work's catalog or a new GitHub host only when ticked (or named with --allow), since it installs and runs whatever the file says — and re-creates the workspaces as rows; each one's repos then clone in the background, one workspace at a time, on the branch the manifest recorded (from origin, or fresh from the base when it was never pushed). Archived workspaces clone when they are un-archived.

Share / Join: the clipboard button on Manage (or work share) copies a workspace's invite — its backup-manifest entry (name, branch, repos) as JSON. Someone else pastes it into New workspace (the join button; or work join <file>, or pipes it in) and gets the same repos, checked out on their own lane (<their-branch>-<them>) of the workspace branch, with PRs and the review targeting that branch.

Configuration

| Variable | Default | Purpose | | ------------------------- | --------------------- | ---------------------------------------------------- | | WORK_ROOT | ~/work | Workspaces root (+ .cache bare repos) | | WORK_DATA_DIR | ~/.local/share/work | sqlite db and server state | | WORK_APP_PORT | 7171 | HTTP server (app, assets, and /term websockets) | | WORK_ALLOWED_HOSTS | — | Hostnames a proxy serves it under (see Access) | | WORK_LISTEN | 127.0.0.1 | Listen address (0.0.0.0 for every interface) | | WORK_TRUSTED_PROXIES | — | Proxy IPs/CIDRs allowed in from the network (*) | | WORK_PORT_HOSTS | sub | Port-chip hostname shape: sub / dash (see above) | | WORK_USER / WORK_HOST | current user/host | Branch-name prefix parts | | WORK_BRANCH_PREFIX | <user>-<host> | Whole branch-name prefix, overriding the parts | | WORK_RTC | on | off turns direct connections off (see Access) | | WORK_RTC_PORT | the app port | UDP port of direct connections | | WORK_RTC_STUN | Cloudflare's STUN | Their STUN servers, comma-separated (empty: none) |

The usage panel (and work usage) reads plan/quota windows straight from each agent CLI's own credentials on disk — Claude, Codex, Cursor, Grok, and Copilot (via gh's token). Providers you are not signed into simply say so. A script that waits for room in a window reads work usage --json, whose shape (and what each field means when a reading is stale) work help usage spells out.

Running more than one work (a laptop and a desk box, say)? Settings → Appearance gives each a name and an accent colour: the tab title, home-screen icon, notifications, and the app's chrome all take them, so two windows or two installed apps stop looking identical.

PR chips refresh by polling GitHub (faster for a few minutes after a local push via repo-pushed). No inbound GitHub webhooks are required.

Layout on disk

  • ~/work/<slug>-<id>/ — a workspace: one worktree per repo + generated AGENTS.md (and CLAUDE.md symlink) encoding the conventions
  • ~/work/.cache/github.com/<owner>/<repo>.git — shared bare-repo cache
  • ~/.local/share/work/work.db — sqlite database (authoritative state)

Terminals live in a dedicated tmux server (tmux -L work), kept alive by a transient systemd unit so app restarts never kill your sessions.