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

@nulfrog/sandglass

v0.1.3

Published

Real-time terminal monitor for the Sandcastle issue-processing runner.

Readme

⏳ Sandglass

A real-time terminal dashboard for the Sandcastle issue-processing runner (the .sandcastle/ plan → implement → review → merge loop).

Sandglass watches your .sandcastle/logs/ directory live and shows you which issue is being worked on, which phase is active, recent activity, and a tail of the current agent's log — without you having to tail -f anything by hand.

┌ ⏳ Sandglass · brycebooth-at-m42/level-agentic-lesson-lab     ● LIVE · iteration 3/10 · 4s ago ┐
  Plan → Implement → Review → Merge
┌ Current work · 3 in parallel ─────────────────────────────────────────────────────────────────┐
  #14  Add lesson editor autosave
  phase: Implement · branch: sandcastle/issue-14
┌ Issues (6) ──────────────────┐┌ Live log · ...issue-14-implementer.log ────────────────────────┐
  ✓ #19 Merge   Convex backend   Expanding shell expressions done (0.3s)
  ✓ #10 Merge   Repo scaffolding ...
  • #14 Implement Add lesson edi  Writing apps/teacher/src/editor/autosave.ts
[s] Start runner  [x] Stop runner  [o] Open issue  [q] Quit

Install / run

Sandglass is a published CLI — no clone required:

# one-off, from inside the repo you want to monitor
npx @nulfrog/sandglass

# or install globally
npm i -g @nulfrog/sandglass
sandglass

By default it auto-detects the repo root (the nearest ancestor of your current working directory containing .sandcastle/) and monitors <repo>/.sandcastle/logs. Point it elsewhere with flags or config:

npx @nulfrog/sandglass --repo /path/to/another/repo
npx @nulfrog/sandglass --combined /tmp/sandcastle-real.log   # also parse a stdout log
npx @nulfrog/sandglass --github owner/name                   # issue links
npx @nulfrog/sandglass --help

On Windows, use a UTF-8-capable terminal (e.g. Windows Terminal) so the emoji/box-drawing render correctly. Requires Node 22+.

Why a TUI (and not Electron / a web app)?

  • Cross-platform out of the box — pure Node, runs the same on Windows, macOS and Linux.
  • Lives where Sandcastle lives — the terminal. No packaging, no browser.
  • Built to extend — UI is Ink (React for the terminal) and every key-driven capability is an entry in a small action registry, so adding new actions is a few lines.

Config

Resolution precedence: CLI flags > env (SANDGLASS_REPO, SANDGLASS_LOGS, SANDGLASS_COMBINED_LOG) > config file > defaults. Config files are read from your current working directory (and the install dir): create sandglass.config.json (or sandglass.local.json, gitignored):

| Key | Meaning | | ----------------- | -------------------------------------------------------- | | repoPath | Repo containing .sandcastle/ | | logsDir | Logs dir (defaults to <repoPath>/.sandcastle/logs) | | runnerCommand | Command the Start runner action launches | | combinedLog | Optional combined stdout log to also parse | | githubRepo | owner/name for issue links/header (auto-detected from the repo's git remote when unset) | | liveThresholdMs | A log counts as "live" if touched within this window | | tailLines | Lines of the active log to display |

Keys / actions

| Key | Action | | --- | --------------------------------------------------- | | s | Start the runner (runnerCommand) as a child | | x | Stop a runner Sandglass launched | | o | Open the active issue on GitHub | | q | Quit |

How it works

  • src/monitor/logsScanner.ts decodes log filenames (<branch>-<phase>[--<issue>].log) into structured records.
  • src/monitor/store.ts (Monitor) watches the logs dir with chokidar, treats the most-recently-modified log as the live phase, and reduces everything into a single MonitorState. It can also attach to a combined stdout log or to a runner it spawns itself, parsing the runner's high-level lines (=== Iteration … ===, the planner's <id>: <title> → <branch> list, [phase] Started on branch …, Branches merged., All done.). The runner (.sandcastle/main.mts) works all unblocked issues in parallel each iteration, so the Issues panel holds the concurrent set while "Current work" highlights the most-recently-active one.
  • src/ui/App.tsx renders that state with Ink and re-renders on every update.

Develop

npm install
npm run build        # tsup → dist/cli.js (the published bin)
node dist/cli.js --help
npm run typecheck

Extending with new actions

Add to src/actions/builtins.ts:

.register({
  key: "p",
  label: "Push main",
  run: ({ monitor }) => {
    // e.g. spawn a git push, then monitor.pushEvent(...)
  },
})

Each action gets { monitor, exit }. Monitor exposes startRunner, stopRunner, isRunnerSpawned, and the current state. Add new state by extending MonitorState in src/types.ts and the reducers in store.ts.

Project layout

src/
  cli.tsx              entry point (arg parsing, render)
  config.ts            config + flag/env resolution
  types.ts             shared data model
  monitor/
    parser.ts          filename + stdout-line parsing
    logsScanner.ts     scan .sandcastle/logs
    tail.ts            read file tails / appended bytes
    store.ts           Monitor: watch + reduce + emit "update"
  actions/
    registry.ts        Action type + registry
    builtins.ts        default action set
  ui/
    App.tsx            Ink dashboard

License

MIT