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

ework-web

v0.10.56

Published

ework-web — standalone multi-project issue tracker. Local SQLite-backed, no external API dependency. Bun + TypeScript + SSR HTML.

Readme

ework-web

Standalone multi-project issue tracker. Local SQLite-backed, no external API dependency. Bun + TypeScript + SSR HTML + vanilla JS.

Issues, comments, labels, reactions, and attachments live in a local SQLite database, served under /<owner>/<repo>/issues/<n> URLs. The same process also hosts an OpenCode session viewer, a file viewer, inline translate, and TTS sidecars.

Features

  • Multi-project issues/<owner>/<repo>/issues/<n> URLs, one DB holds all projects
  • Comments with markdown (marked + highlight.js + DOMPurify), code highlighting, linkify
  • Attachments — image/pdf/etc upload, stored on local filesystem, streamed via Bun.file
  • Labels & reactions — per-project labels, 9-emoji reactions on comments
  • OpenCode session viewer — read-only /sessions, hot bars, translate, TTS
  • File viewer/file?path=… with security gate (path validation + denylist + realpath)
  • Inline translation + TTS — OpenAI-compatible /v1/chat/completions + /v1/audio/speech
  • Token-cookie auth — single shared token + HMAC-signed cookie (30d)
  • Rate limiting — token bucket on /api/*, /login, translate, TTS, settings

Installation

Prerequisites

  • Bun runtime (v1.x) — required for all install paths
  • Gitea instance — optional; only needed if you want an upstream mirror per project. Standalone mode (local SQLite) needs no external service.
  • LLM endpoint (OpenAI-compatible /v1/chat/completions, optionally /v1/audio/speech) — optional; only needed for inline translation and TTS.

Option 1 — AIO Docker (recommended)

The all-in-one image bundles ework-web, the daemon, and opencode. Requires Docker.

cp docker/.env.docker.example docker/.env.docker   # fill WORK_TOKEN + WORK_COOKIE_SECRET
./docker/build.sh                                   # builds ework-aio:latest
HOST_PORT_WEB=13002 HOST_PORT_DAEMON=13101 ./docker/run.sh ework-aio:latest

Default published ports are 3002 (web) and 3101 (daemon). Override HOST_PORT_WEB / HOST_PORT_DAEMON if those are taken on the host. See docker/run.sh and docker/README.md (if present) for full options (HOST_BIND_ADDR, volume names, env passthrough).

Option 2 — From source (development)

git clone <this-repo> ework-web && cd ework-web
cp .env.example .env       # fill WORK_TOKEN (≥8 chars) + WORK_COOKIE_SECRET (≥8 chars)
bun install
bun run check              # tsc --noEmit — must pass
bun run dev                # :3002 watch (or bun run start for one-shot)

Option 3 — Systemd (production)

For a persistent single-host deploy, use the deploy script which rsyncs to ~/.local/share/ework/ and installs a systemd unit:

./scripts/deploy.sh --restart   # type-check → rsync src/ → bun install → install unit → restart

The unit runs under User=ework and reads /var/lib/ework/.env. Inspect or customize scripts/ework.service before first deploy; subsequent deploys leave the installed unit alone. See the Deploy section below for the full flow.

First-run setup

  1. Visit http://127.0.0.1:3002/login (or whichever WORK_PORT you set).
  2. Log in with the WORK_TOKEN you configured.
  3. Land on /projects — create a project inline, or just POST an issue to /<owner>/<repo>/issues and the project will be auto-created.

Configuration

All env vars prefixed WORK_*. Zod-validated in src/config.ts.

| Var | Default | Purpose | |---|---|---| | WORK_PORT | 3002 | Listen port | | WORK_HOST | 127.0.0.1 | Bind host (use :: for IPv6 remote) | | WORK_TOKEN | — | Required access token (≥8 chars) | | WORK_COOKIE_SECRET | — | HMAC secret for auth cookie (≥8 chars) | | WORK_OPERATOR_LOGIN | op | Author attribution for writes | | WORK_WRITES_ENABLED | true | Gate all write ops | | WORK_DB_PATH | $XDG_DATA_HOME/ework/ework.db | SQLite DB location | | WORK_ATTACHMENT_ROOT | $XDG_DATA_HOME/ework/attachments | Attachment files root |

Plus OpenCode / translate / TTS / file-viewer vars — see .env.example and src/config.ts.

Defaults worth knowing

  • Port 3002 — chosen to avoid the 119x range used by VPN/tunnels.
  • Writes default true — ework-web is intended for direct human use; flip WORK_WRITES_ENABLED=false for a read-only mirror.
  • Auth scope — single operator (WORK_OPERATOR_LOGIN) writing as one user. The schema reserves a users table for multi-user; not yet surfaced in the UI.

Project layout

src/
├── index.ts             # Bun.serve :3002 + route dispatch + security headers
├── config.ts            # Zod env validation (no gitea block)
├── auth.ts              # token + HMAC cookie
├── store.ts             # SQLite data layer (projects/issues/comments/labels/reactions/attachments)
├── schema.sql           # DDL applied on boot
├── db.ts                # rawDB handle + config table CRUD
├── attachments.ts       # filesystem blob storage + Bun.file streaming
├── ratelimit.ts         # token bucket
├── opencode.ts          # OpenCode CLI client (read-only)
├── translate.ts         # OpenAI-compatible translation
├── fileview.ts          # file/dir viewer + security gate
├── reactions.ts         # hydrate reactions onto CommentView[]
├── render/
│   ├── layout.ts        # SSR layout + THEME_CSS + utils
│   ├── markdown.ts      # marked + DOMPurify + linkify (no Gitea rewrite)
│   └── components.ts    # CommentView + renderCommentCard
├── views/
│   ├── issueThread.ts   # SSR issue thread (SQLite-backed)
│   ├── issueList.ts     # per-project list (with search + state tabs)
│   ├── issueNew.ts      # create-issue form
│   ├── issues.ts        # global feed (cross-project)
│   ├── home.ts          # projects list + new-project form
│   ├── sessionLog.ts    # OpenCode session viewer
│   └── settings.ts      # /settings form
└── static/
    ├── app.js           # issue-thread client (virtual scroll, poll, composer, reactions UI)
    ├── session.js       # session-viewer client
    ├── file.js          # file tail -f
    ├── tts.js           # TTS playback
    └── favicon.svg

scripts/deploy.sh        # tsc → rsync → bun install → systemctl restart ework

URL / API surface

| Method | Path | Notes | |---|---|---| | GET | / | redirect → /projects | | GET/POST | /projects | projects list / create new | | GET | /issues | global feed (cross-project), ?state=&q= | | GET | /<o>/<r>/issues | per-project list, ?state=&q= | | GET | /<o>/<r>/issues/new | create form | | POST | /<o>/<r>/issues | create (auto-creates project if missing) | | GET | /<o>/<r>/issues/<n> | issue thread SSR | | GET | /api/<o>/<r>/issues/<n>/page?page=K | fetch older comments page | | GET | /api/<o>/<r>/issues/<n>/since?since=ISO | poll for new comments | | POST | /api/<o>/<r>/issues/<n>/comment | {body, close?, reopen?} | | POST | /api/<o>/<r>/issues/<n>/upload | multipart attachment upload | | GET | /attachments/<uuid> | stream attachment (image inline, else download) | | GET | /sessions /sessions/:id | OpenCode session browser | | GET | /file /file/raw /file/dl | file viewer (path-gated) | | GET | /api/file/since | tail -f polling | | POST | /api/translate /api/translate/stream | inline translation | | POST | /api/tts GET /api/tts/stream/:id | TTS staging + streaming | | GET/POST | /settings | runtime overrides | | POST | /login | token → cookie |

Deploy

git add -A && git commit -m "..."    # commit first (deploy.sh rsyncs working tree)
./scripts/deploy.sh

deploy.sh type-checks → rsyncs src/bun install → installs the systemd unit from scripts/ework.service on first run → restarts ework.service. To customize the unit (different user, port, env path), edit scripts/ework.service before first deploy; subsequent deploys leave the installed unit alone.

Logs: sudo journalctl -u ework -f.

Scope

  • ❌ No issue edit/delete.
  • ❌ No milestones / assignees / PR / multi-user login (schema-compatible; not implemented).
  • ❌ No migration tool from Gitea (fresh start; can be added later).
  • Never writes to opencode.db (read-only SELECT).

License

MIT — see LICENSE.