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

@openpen/module-api

v1.3.0

Published

OpenPen Module API — public contract for built-in modules and third-party plugins

Readme

@openpen/module-api

The public contract surface for OpenPen modules — both first-party (src/core/modules/*) and third-party plugins (~/.openpen/plugins/*).

This is the only import path that built-in modules and plugins are allowed to use (apart from relative imports, node:*, and third-party npm packages). The boundary is enforced by tests/unit/boundaries/* in the OpenPen main repo.

Install

npm install @openpen/module-api zod

Import z from @openpen/module-api, not from 'zod' directly. The build externalises 'zod' — a direct import { z } from 'zod' in a plugin file will fail at build time with an unresolved-import error. Use import { z } from '@openpen/module-api' everywhere in your plugin.

Quick start

import { defineModule, z } from '@openpen/module-api'

export default defineModule({
  id: '@scope/my-module',
  version: '0.1.0',
  metadata: {
    name: { en: 'My Module', 'zh-Hant': '我的模組' },
  },
  settingsSchema: z.object({
    enabled: z.boolean().default(true),
  }),
  contributes: {
    tools: [
      {
        id: 'my-tool',
        // ...
      },
    ],
  },
})

setup(ctx) — Module initialisation

defineModule supports an optional setup(ctx: ModuleSetupContext) hook, called once when the module loads. ctx provides:

| Method | Description | |--------|-------------| | ctx.getSettings<T>() | Read this module's parsed settings (requires settingsSchema to be declared) | | ctx.updateSettings(patch) | Persist a partial update to this module's settings | | ctx.onSettingsChange<T>(cb) | Subscribe to settings changes; returns an unsubscribe function | | ctx.callMain(action, payload?) | Call this module's main-side handler | | ctx.onDispose(fn) | Register a cleanup callback (called in reverse order when the module is unloaded) | | ctx.notify(payload) | Show a short-lived toast in the overlay window; returns NotifyHandle for early dismiss() | | ctx.t(key, params?) | Resolve an i18n key against this module's contributes.locales for the current host locale | | ctx.locale | The host's current locale (e.g. 'en', 'zh-Hant') |

import { defineModule } from '@openpen/module-api'
import type { NotifyPayload } from '@openpen/module-api'

export default defineModule({
  id: 'my-module',
  name: { en: 'My Module' },
  contributes: { /* ... */ },

  setup(ctx) {
    // Show a toast when the module finishes loading.
    ctx.notify({
      message: { en: 'My Module ready' },
      variant: 'success',
      duration: 1500,
    })

    // Release resources (timers, listeners, etc.) on unload.
    const timer = setInterval(() => { /* ... */ }, 5000)
    ctx.onDispose(() => clearInterval(timer))
  },
})

See docs/reference/notify-api.md.

See the OpenPen docs for guides, slot catalogue, and UIKit reference.

Long-term stability of the UIKit

The UIKit (@openpen/module-api/uikit) wraps a headless behaviour library internally. That library is an implementation detail — it is not part of the public API surface.

If the underlying library is ever replaced, the host will follow a documented fallback order: Headless UI Vue → Ark UI → self-written primitives. The wrapper props, events, and slots documented here will remain stable across any such swap. Plugin authors who import only from @openpen/module-api/uikit will be unaffected. Authors who drop to the primitive layer (named primitive exports) will see a major-version bump and a small targeted port. Authors who bypass the UIKit entirely and import a headless library directly in their plugin's package.json are responsible for porting that surface themselves.

This guarantee is why the wrapper layer is the recommended starting point for all plugin UI.

License

MIT