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

@qiankun01/baton-plugin

v0.1.14

Published

Public protocol and authoring types for Baton plugins.

Readme

Public, host-independent authoring contract for Baton plugins.

import {
  type ConditionedStatus,
  type Resource as ExampleResource,
  type PluginActivationContext,
  type PluginPackage,
} from "@qiankun01/baton-plugin";

const EXAMPLE_RESOURCE = {
  apiVersion: "example.baton.dev/v1alpha1",
  kind: "Example",
} as const;

// Creation may attach Plugin-defined string metadata:
// context.resources.create(EXAMPLE_RESOURCE, {
//   name: "example-1",
//   labels: { "example.com/team": "platform" },
//   annotations: { "example.com/display-name": "First example" },
//   spec: { title: "Hello" },
// });

interface ExampleStatus extends ConditionedStatus {
  readonly phase?: "active" | "done";
}

type Example = ExampleResource<{ title: string }, ExampleStatus>;

const plugin: PluginPackage = {
  pluginId: "example/plugin",
  version: "0.1.0",
  activate(context: PluginActivationContext) {
    context.logger.write({
      level: "info",
      component: "activation",
      message: "Example plugin activated",
    });
    context.toast.show({ text: "Example plugin ready", tone: "success" });
    context.registerCommand({
      commandId: "examples",
      name: "examples",
      description: "List examples",
      execute() {
        return {
          kind: "picker",
          title: "Examples",
          search: {
            mode: "remote",
            query: "",
            placeholder: "Search examples",
          },
          options: [{ name: "Hello", value: "hello" }],
        };
      },
    });
    context.registerContextProvider({
      kind: "example",
      search(query) {
        return context.resources
          .list<{ title: string }, ExampleStatus>(
            EXAMPLE_RESOURCE,
          )
          .filter((resource) =>
            resource.spec.title.toLowerCase().includes(query.toLowerCase())
          )
          .map((resource) => ({
            id: resource.metadata.name,
            label: resource.spec.title,
            detail: resource.status.phase,
          }));
      },
      provide(id, { maxChars }) {
        const resource = context.resources.get<
          { title: string },
          ExampleStatus
        >(EXAMPLE_RESOURCE, id);
        return `Example: ${resource.spec.title}`.slice(0, maxChars);
      },
    });
    context.registerController({
      resourceType: EXAMPLE_RESOURCE,
      sources: [{
        type: "cron",
        sourceId: "periodic-refresh",
        cron: "*/5 * * * *",
        timeZone: "UTC",
      }],
      async reconcile(_baton, resource: Example) {
        // Observe current facts and patch status through context.resources.
      },
      present(resource) {
        return {
          title: resource.spec.title,
          status: resource.status.phase,
        };
      },
    });
  },
};

export default plugin;

This package contains protocol types only. Baton runtime implementations such as Manager, Binding, Controller, Store, Marketplace, persistence, and Harness routing are intentionally excluded.

Plugins may opt into Kubernetes-style current-state conditions by extending ConditionedStatus. conditions remains optional and lives inside the Plugin-owned status schema; Baton stores it but does not interpret condition types, reasons, transitions, or lifecycle policy. Keep at most one current condition per type, and update lastTransitionTime only when that condition's status changes.

context.toast is session-scoped and non-durable. Use it for one-off feedback caused by an operation or state transition. Ongoing state belongs in Resource status and an optional Board presentation; do not emit a toast on every reconcile.

context.logger writes best-effort diagnostics to the owning BatonSession. Baton adds the Plugin identity and owns the log path and persistence. Use structured details for troubleshooting context, never include secrets, and never use diagnostics as Resource state. Polling code should deduplicate repeated failures instead of writing the same message every reconcile.

registerContextProvider exposes searchable, read-only context that a user can explicitly add to one Harness turn with @. kind is local to the Package; Baton qualifies it as <pluginName>@<kind>, groups picker candidates by that identity, and removes the registration with the Plugin Binding. Keep search local and side-effect free; provide runs only after the user selects a reference and submits the turn.

Controller Sources have two narrow roles:

  • A Source performs initial discovery, installs live subscriptions, and calls emit(resource) when it observes a Resource owned by that Controller. Baton materializes a missing Resource, treats an identical repeated value as a keyed wakeup, and rejects an implicit spec change. start() resolves only after the initial scan and subscription are ready; the owning Binding aborts its signal on close.
  • A CronSource periodically enqueues every current Resource owned by that Controller.

Both paths use the same keyed reconcile queue as Resource changes and requeueAfterMs. Sources never update status or produce Plugin Output; those remain exclusively owned by reconcile.

Controllers may also declare watches to map changes from secondary Resources to primary ReconcileRequests:

import { enqueueRequestsFromMapFunc } from "@qiankun01/baton-plugin";

const repositories = enqueueRequestsFromMapFunc<
  unknown,
  { readonly repositories?: readonly string[] }
>((workspace) =>
  (workspace.status.repositories ?? []).map((name) => ({ name })),
);

registerController({
  resourceType: "Repository",
  watches: [{ resourceType: "Workspace", handler: repositories }],
  async reconcile(_baton, repository) {
    // Reconcile repository.metadata.name from the latest stored state.
  },
});

For an update, enqueueRequestsFromMapFunc maps both the old and new Resource and deduplicates the resulting requests. A Watch routes Resources already stored by Baton; a Source discovers external state and materializes the primary Resource before it is reconciled.

A Controller can return kind: "interaction" when its Resource needs a durable user decision:

return {
  output: {
    kind: "interaction",
    decisionKey: "associate-pr",
    title: "Associate pull request",
    prompt: "Which requirement should own this pull request?",
    options: [
      { optionId: "req_1", label: "REQ-1" },
      { optionId: "reject", label: "Do not associate", role: "reject" },
    ],
  },
};

A command can return search.mode: "local" to let chat-tui filter its current options, or "remote" to receive later query text in PluginCommandInput.searchQuery. Baton debounces remote queries and ignores responses superseded by a newer query. A remote result may contain no options; return the same remote-search picker shape so the field stays open.

On the next reconcile, read the result from baton.pluginInteractions by decisionKey. Baton persists the answer before re-enqueuing the same Resource, so plugins do not register option callbacks or hold an in-memory promise while waiting. Omit options for free text.