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

horology

v0.2.1

Published

opencode plugin: schedule prompts and slash-commands into a live opencode session using system-native timers (systemd user timers), so recurring jobs and one-shot reminders survive restarts and land in the right session.

Readme

horology

An opencode plugin that schedules prompts and slash-commands into a live opencode session using system-native timers.

opencode has no built-in scheduler. horology splits the problem in two:

  • Timing is delegated to systemd user timers. They survive an opencode restart and a logout (with lingering enabled), so a recurring job or a one-shot reminder is not lost when you close the TUI.
  • Firing is session-aware. When a timer fires it does not spawn a fresh opencode run; it makes a tiny loopback HTTP call back into the already running opencode server. The plugin — living inside that server — injects the job's prompt (via the async prompt endpoint, so it queues natively) or slash-command into the session it has been tracking. Output lands where you are actually looking, not in some detached headless run.

How it works

systemd user timer  ──fires──▶  curl 127.0.0.1:<port>/fire {name}  ──▶  horology (in opencode)
                                                                          │
                                    tracks the live session id ◀──────────┘
                                                                          │
                              session.promptAsync / session.command  ◀────┘   (into that session)
  • On load (active by default), horology starts a loopback trigger server on 127.0.0.1, then arms one transient systemd-run --user timer per configured job (no unit files to install or clean up).
  • It tracks the active session id from opencode's chat.message and event stream.
  • A fired timer POSTs to /fire with a per-run shared token; horology injects the job into the tracked session (or the newest session in the project directory, or a fresh one).
  • On dispose (opencode shutdown) and on SIGINT/SIGTERM, horology stops the trigger server and disarms every timer it armed — so nothing fires into a dead server and the next start rebuilds cleanly.

Requirements

  • Linux with a systemd user instance (systemctl --user). horology shells out to systemd-run --user and systemctl --user.
  • To keep timers running after you log out / close the terminal, enable lingering once:
    loginctl enable-linger "$USER"
  • curl on PATH (used by the timer to call the loopback endpoint).

Install

Add the package to the plugin array of your opencode config (opencode.json in your project, or ~/.config/opencode/opencode.json globally):

{
  "$schema": "https://opencode.ai/config.json",
  "plugin": ["horology"]
}

opencode auto-installs listed npm plugins at startup (cached under ~/.cache/opencode/). No manual npm install and no .opencode/plugins/ wrapper is needed for the published package.

Configuration

All configuration is via environment variables. horology is active by default whenever the plugin is loaded — the /schedule command and tools are available in every session, and any HOROLOGY_JOBS baseline is armed. Set HOROLOGY_DISABLE=1 to load it inert.

| Variable | Default | Description | | --- | --- | --- | | HOROLOGY_DISABLE | unset | Set to 1 to load the plugin inert (no timers, no /schedule). HOROLOGY=0/false/off/no also disables. | | HOROLOGY_JOBS | unset | JSON array of job objects (see below), or a path to a .json file containing that array. Optional — /schedule works without it. | | HOROLOGY_PORT | ephemeral | Loopback port for the trigger server. Leave unset for an OS-chosen port. | | HOROLOGY_UNIT_PREFIX | horology | systemd unit namespace. Units are named <prefix>-<job>.timer/.service. | | HOROLOGY_TOKEN | random per run | Shared secret the timer must present on /fire. Auto-generated if unset. | | HOROLOGY_DIRECTORY | plugin project dir | Directory used to scope session.list when resolving a target session. | | HOROLOGY_SCHEDULE_AGENT | unset | Optional agent to run the /schedule translation turn under. | | HOROLOGY_DEBUG | unset | 1 for verbose logging (to the opencode app log, service horology). |

Job shape

[
  {
    "name": "inbox-sweep",                        // unique, kebab-case (becomes the unit suffix)
    "on": "Mon..Fri *-*-* 07..18:00,30:00",       // systemd OnCalendar expression
    "prompt": "Run the inbox-sweep skill …",       // enqueue this text into the live session
    "agent": "build"                               // optional agent override
  },
  {
    "name": "standup-reminder",
    "on": "Mon..Fri *-*-* 09:25",
    "command": "remind",                           // OR run a slash-command instead of a prompt
    "arguments": "stand-up in 5 minutes",
    "oneShot": false
  }
]
  • Provide either prompt (enqueued via the async prompt endpoint) or command (+ optional arguments, run via the session command endpoint).
  • on is any valid systemd OnCalendar expression. Test one with systemd-analyze calendar "<expr>".
  • oneShot: true disarms the timer after it fires once (useful for reminders).

Scheduling from the TUI (/schedule)

Besides the HOROLOGY_JOBS baseline (read once at load), you can schedule jobs live from the opencode TUI in plain language. horology contributes a /schedule command and a set of tools the model calls to arm/list/cancel timers.

/schedule remind me to check my email in 10 minutes
/schedule every weekday at 9am, summarize what changed in the repo overnight

How it works: the /schedule command hands your request to the model, which translates the timing and calls the horology_schedule tool. For relative reminders ("in N minutes/hours") the model passes inMinutes and horology computes the absolute fire time itself (so it never has to guess the wall-clock time); these are always one-shot. For specific or recurring times the model passes a systemd OnCalendar expression.

The model can also call:

  • horology_list — show the currently armed jobs and their next fire times.
  • horology_cancel <name> — disarm and forget a job.

Ephemeral: jobs added via /schedule live only for the current opencode process. They are not persisted — to make one permanent, add it to HOROLOGY_JOBS. On restart, horology re-arms exactly what's in HOROLOGY_JOBS.

Set HOROLOGY_SCHEDULE_AGENT to run the /schedule translation turn under a specific agent.

Inspecting / debugging

systemctl --user list-timers 'horology-*'      # what's armed and when it next fires
journalctl --user -u 'horology-*' -f           # watch fires
systemctl --user list-units 'horology-*'       # timers + services

Local development (from this repo)

The published package needs no wrapper, but to exercise the plugin in-repo before publishing there is a thin dev wrapper at .opencode/plugins/horology.ts that re-exports from this package's built output:

cd opencode-plugin
npm install
npm run build          # emits dist/ that the wrapper imports

Then start opencode from the repo root (horology is active by default; set HOROLOGY_JOBS for a baseline, or use /schedule). Rebuild (npm run build) after editing anything under src/. npm run typecheck runs a no-emit strict type check.

Publishing

cd opencode-plugin
npm run typecheck                 # strict type check
npm publish --dry-run             # inspect the tarball (should contain only dist/, package.json, README, LICENSE)
npm login                         # first time only
npm publish --access public

prepublishOnly runs the build automatically, so dist/ is always fresh in the published tarball. Only dist/ is shipped (see files in package.json); src/ and node_modules/ are excluded.

Security notes

  • The trigger server binds 127.0.0.1 only and requires the per-run HOROLOGY_TOKEN on every /fire call. It is not reachable off-host.
  • horology executes systemd-run/systemctl as your user and injects the prompts/commands you configured. Only run job definitions you trust.

License

MIT (c) Quintin De Kok