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

create-donobu-plugin

v1.7.0

Published

Create a new Donobu plugin

Readme

create-donobu-plugin

create-donobu-plugin is the official scaffolding CLI for Donobu Studio plugins. It generates a TypeScript project wired to Donobu's plugin API and pins dependencies to whatever versions your local Donobu installation already uses.

How Donobu loads plugins

Donobu watches a plugins directory inside its working data folder:

| Platform | Path | | -------- | ------------------------------------------------------ | | macOS | ~/Library/Application Support/Donobu Studio/plugins/ | | Windows | %APPDATA%/Donobu Studio/plugins/ | | Linux | ~/.config/Donobu Studio/plugins/ |

Each plugin ships a bundled dist/index.mjs. On startup Donobu imports every plugin and calls the exported functions it finds:

  • loadCustomTools(deps) — returns an array of Tool instances.
  • loadPersistencePlugin(deps) — returns { key, plugin } to register a persistence backend, or null to skip.

A single plugin may export both functions. Scoped package names (e.g., @myorg/my-plugin) are supported.

npm exec install-donobu-plugin builds your project and copies dist/ into the plugins directory. Restarting Donobu is enough to pick up changes.

Prerequisites

  • Node.js 20+ and a package manager (npm 8+, pnpm 10+, or yarn)
  • A local Donobu Studio installation (desktop or backend) so the installer has somewhere to copy the plugin bundle
  • Write access to the Donobu Studio working directory

Quick start

Tool plugin (default)

npx create-donobu-plugin my-support-tools
cd my-support-tools && npm install
# Edit src/index.ts to add your custom tools
npm run build
npm exec install-donobu-plugin
# Restart Donobu Studio

Persistence plugin

npx create-donobu-plugin my-storage --type persistence
cd my-storage && npm install
# Edit src/index.ts to implement your persistence plugin
npm run build
npm exec install-donobu-plugin
# Set PERSISTENCE_PRIORITY to include your plugin key, then restart Donobu

Plugin names may include lowercase letters, numbers, hyphens, and underscores. The CLI prints usage information if the argument is missing or invalid.

Generated project layout

| Item | Description | | -------------------- | ----------------------------------------------------------------------------------------------- | | package.json | Private package with "donobu-plugin": true. donobu is a peer/dev dependency, never bundled. | | tsconfig.json | ES2021 + NodeNext compiler settings that emit declarations into dist/. | | esbuild.config.mjs | Bundles TypeScript source into dist/index.mjs, keeping donobu external. | | src/index.ts | Entry point. Exports loadCustomTools(deps) and/or loadPersistencePlugin(deps). | | README.md | Template instructions for the team that owns the generated plugin. |

The deps pattern

Plugins receive all runtime values through the deps parameter — never use value imports from donobu. Only import type is allowed because type imports are erased at compile time, while value imports would produce an import ... from "donobu" statement in the bundle that Node cannot resolve from the plugins directory.

// CORRECT — type imports are erased, runtime values come from deps
import type { Tool, PluginDependencies } from 'donobu';

export async function loadCustomTools(
  deps: PluginDependencies,
): Promise<Tool<any, any>[]> {
  const logger = deps.donobu.appLogger; // runtime access via deps
  // ...
}
// WRONG — this creates a runtime import that will fail at load time
import { appLogger } from 'donobu';

What is available on deps

  • deps.donobu — the full public SDK: createTool, Tool, ReplayableInteraction, appLogger, MiscUtils, PlaywrightUtils, schemas, exception classes, etc.

Feel free to add your own npm dependencies; esbuild bundles everything except the packages listed in external.

Authoring tool plugins

Every tool plugin exports loadCustomTools(deps) and returns an array of Tool instances. The simplest way to create a tool is deps.donobu.createTool():

import type { Tool, PluginDependencies } from 'donobu';
import { z } from 'zod/v4';

export async function loadCustomTools(
  deps: PluginDependencies,
): Promise<Tool<any, any>[]> {
  return [
    deps.donobu.createTool(deps, {
      name: 'judgeWebpageTitle',
      description: 'Judge the quality of a title to a webpage.',
      schema: z.object({ webpageTitle: z.string() }),
      requiresGpt: true,
      call: async (context, parameters) => {
        const resp = await context.gptClient!.getMessage([
          {
            type: 'user',
            items: [
              {
                type: 'text',
                text: `Judge the quality of this webpage title on a scale of 1 (worst) to 10 (best) and explain why.
                       Title: ${parameters.webpageTitle}`,
              },
            ],
          },
        ]);
        return {
          isSuccessful: true,
          forLlm: resp.text,
          metadata: null,
        };
      },
    }),
  ];
}

For tools that need to extend ReplayableInteraction or another base class, use a factory function so the base class comes from deps.donobu rather than a value import:

import type { Tool, PluginDependencies } from 'donobu';

function createMyTool(deps: PluginDependencies): Tool<any, any> {
  const { ReplayableInteraction } = deps.donobu;

  class MyTool extends ReplayableInteraction</* ... */> {
    // ...
  }

  return new MyTool();
}

export async function loadCustomTools(deps: PluginDependencies) {
  return [createMyTool(deps)];
}

Authoring persistence plugins

Persistence plugins export loadPersistencePlugin(deps). The returned key must appear in the PERSISTENCE_PRIORITY environment variable for Donobu to use it.

import type {
  EnvPersistence,
  FlowsPersistence,
  PersistencePlugin,
  PluginDependencies,
} from 'donobu';

export async function loadPersistencePlugin(
  deps: PluginDependencies,
): Promise<{ key: string; plugin: PersistencePlugin } | null> {
  const bucket = process.env.MY_STORAGE_BUCKET;
  if (!bucket) return null; // skip registration if config is missing

  return {
    key: 'MY_CUSTOM',
    plugin: {
      async createFlowsPersistence(): Promise<FlowsPersistence | null> {
        // Return your FlowsPersistence implementation, or null.
        return null;
      },
      async createEnvPersistence(): Promise<EnvPersistence | null> {
        // Return your EnvPersistence implementation, or null.
        return null;
      },
    },
  };
}

If your persistence classes need SDK utilities like appLogger or FlowNotFoundException, accept deps.donobu as a constructor parameter rather than importing them directly.

Build and install

npm run build
npm exec install-donobu-plugin

npm run build cleans dist/, runs TypeScript (for declarations), and bundles the source with esbuild. npm exec install-donobu-plugin infers the plugin name from package.json and copies dist/ into the Donobu plugins directory. Restart Donobu after installing so the new bundle is loaded.

esbuild considerations

The generated esbuild.config.mjs bundles your TypeScript source and all third-party dependencies into a single dist/index.mjs, while keeping donobu external (it is injected at runtime by the plugin loader). The mainFields: ['module', 'main'] option ensures esbuild prefers ESM entry points from third-party packages for cleaner output.

If you add dependencies that include CJS modules using require() for Node builtins (common in Google Cloud, AWS, or gRPC libraries), the bundle may fail at runtime with "Dynamic require of X is not supported". Fix this by adding a banner that provides a real require():

await build({
  // ... your existing options ...
  banner: {
    js: 'import { createRequire } from "module"; const require = createRequire(import.meta.url);',
  },
});

This injects a real require() via Node's createRequire, allowing bundled CJS code to resolve Node builtins normally in the ESM context.

Recommended development loop

  1. Make changes in src/
  2. npm run build && npm exec install-donobu-plugin
  3. Restart Donobu Studio or the backend process
  4. Trigger your tool from the UI or API to verify behavior

Troubleshooting

  • Plugin not appearing: Ensure npm exec install-donobu-plugin ran successfully and that dist/index.mjs exists. Restart Donobu and watch the logs for plugin loading messages.
  • "Cannot find package 'donobu'": Your bundle contains a runtime import ... from "donobu". Ensure all donobu imports use import type and access runtime values through deps.donobu.
  • "Dynamic require of X is not supported": A bundled CJS dependency calls require() for a Node builtin. Add the createRequire banner to your esbuild config (see above).
  • Schema errors: Zod throws runtime errors when inputs don't match your schema. Log the error message to quickly see which field failed.