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

@escalated-dev/plugin-sdk

v0.1.0

Published

SDK for building Escalated plugins — framework-agnostic, TypeScript-first

Readme

@escalated-dev/plugin-sdk

Tests Node.js TypeScript License: MIT

SDK for building Escalated plugins. Write plugins once in TypeScript, run them across all Escalated backend frameworks (Laravel, Django, AdonisJS, Rails).

Quick Start

import { definePlugin } from '@escalated-dev/plugin-sdk'

export default definePlugin({
  name: 'my-plugin',
  version: '1.0.0',

  actions: {
    'ticket.created': async (event, ctx) => {
      ctx.log.info('New ticket!', event)
    },
  },
})

Installation

npm install @escalated-dev/plugin-sdk

Core API

definePlugin(definition)

The single entry point for defining a plugin. Returns a ResolvedPlugin with normalized filters and endpoints and a toManifest() method used by the Escalated host to register your plugin.

import { definePlugin, type PluginContext } from '@escalated-dev/plugin-sdk'

export default definePlugin({
  name: 'my-plugin',
  version: '1.0.0',
  description: 'Does something useful',

  // Config fields shown in the admin settings UI
  config: [
    { name: 'api_key', label: 'API Key', type: 'password', required: true },
    { name: 'notify_channel', label: 'Notify Channel', type: 'text' },
  ],

  // Action hooks — fire-and-forget
  actions: {
    'ticket.created': async (event, ctx) => { /* ... */ },
    'ticket.resolved': async (event, ctx) => { /* ... */ },
  },

  // Filter hooks — must return the (possibly modified) value
  filters: {
    'ticket.reply.compose': async (data, ctx) => {
      return { ...data, extra_field: 'value' }
    },
    // Shorthand with explicit priority
    'ticket.statuses': {
      priority: 5,
      handler: async (statuses, ctx) => [...statuses, { key: 'custom', name: 'Custom' }],
    },
  },

  // REST endpoints exposed by the plugin
  endpoints: {
    'GET /my-plugin/status': async (ctx, req) => ({ ok: true }),
    'POST /my-plugin/action': {
      capability: 'manage_settings',
      handler: async (ctx, req) => { /* ... */ },
    },
  },

  // Incoming webhooks
  webhooks: {
    'POST /webhooks/my-service': async (ctx, req) => { /* ... */ },
  },

  // Scheduled tasks (cron expression as key)
  cron: {
    '0 9 * * 1': async (ctx) => {
      ctx.log.info('Running weekly report')
    },
  },

  // Lifecycle
  onActivate: async (ctx) => { await ctx.store.set('data', 'init', { activated: true }) },
  onDeactivate: async (ctx) => { /* cleanup */ },
})

Plugin Context (ctx)

Every handler receives a PluginContext with the following services:

| Property | Type | Description | |---|---|---| | ctx.config | ConfigStore | Read/write plugin configuration values | | ctx.store | DataStore | Persistent key-value and query store scoped to the plugin | | ctx.http | HttpClient | Make outbound HTTP requests | | ctx.broadcast | BroadcastClient | Push real-time events to channels, users, or tickets | | ctx.log | Logger | Structured logging (info, warn, error, debug) | | ctx.tickets | TicketRepository | Find, query, create, and update tickets | | ctx.replies | ReplyRepository | Find, query, and create replies | | ctx.contacts | ContactRepository | Find contacts by ID or email, create contacts | | ctx.tags | TagRepository | List and create tags | | ctx.departments | DepartmentRepository | List and find departments | | ctx.agents | AgentRepository | List and find agents | | ctx.emit | function | Emit a custom hook for other plugins to listen to | | ctx.currentUser | function | Returns the currently authenticated user, or null |

UI Extensions

definePlugin({
  // ...

  // Full-page routes
  pages: [
    {
      route: '/admin/my-plugin',
      component: 'MySettingsPage',
      layout: 'admin',
      capability: 'manage_settings',
      menu: { label: 'My Plugin', section: 'admin', position: 50 },
    },
  ],

  // Inject components into existing pages
  components: [
    {
      page: 'ticket.show',
      slot: 'sidebar',
      component: 'MyTicketPanel',
      order: 20,
    },
  ],

  // Dashboard widgets
  widgets: [
    {
      component: 'MyWidget',
      label: 'My Stats',
      size: 'half',
      badge: async (ctx) => {
        const count = await ctx.store.query('alerts', { unread: true })
        return count.length || null
      },
    },
  ],
})

Broadcastable Events

Plugins can listen to real-time broadcasting events in addition to standard action hooks. When the host framework has broadcasting enabled (Pusher, Reverb, ActionCable, Django Channels, Mercure, Phoenix PubSub, SSE, etc.), the following events are forwarded to plugins:

| Event | Description | |---|---| | broadcast.ticket.updated | Ticket data changed and was broadcast to connected clients | | broadcast.ticket.replied | A new reply was broadcast on a ticket channel | | broadcast.ticket.assigned | Agent assignment was broadcast | | broadcast.ticket.status_changed | Status transition was broadcast | | broadcast.presence.updated | Ticket presence (who is viewing) was broadcast | | broadcast.notification.sent | A real-time notification was pushed to a user |

Subscribe to these in your plugin's actions block just like any other hook:

actions: {
  'broadcast.ticket.replied': async (event, ctx) => {
    // React to a reply that was just broadcast in real time
    ctx.log.info('Broadcast reply on ticket', event.ticketId)
  },
},

Documentation

See the Escalated Docs for the full plugin development guide.

Related Packages

Requirements

  • Node.js >= 18
  • TypeScript >= 5.4

License

MIT