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

@pi-harness/plugin-api

v0.1.160

Published

Public contract for authoring Pi Harness plugins: Cordis context services, plugin configuration helpers, and bounded workspace file access

Readme

@pi-harness/plugin-api

The contract a Pi Harness plugin is written against. A plugin is an ordinary Cordis plugin; this package supplies the service types the harness puts on the Cordis Context, the helpers for declaring plugin configuration, and the bounded file access the harness expects a plugin to use when it touches the workspace.

It carries no launcher, no HTTP server and no bundled runtime, so installing it does not pull the harness itself.

Install

npm install @pi-harness/plugin-api @deepseek-ai/cordis @deepseek-ai/schemastery @earendil-works/pi-ai @earendil-works/pi-coding-agent

The four runtimes are peerDependencies. They appear in this package's published type surface, so the plugin and the harness have to resolve the same copy of each: a second copy of @deepseek-ai/cordis detaches the declare module augmentation and the plugin compiles against a Context that carries none of the harness services. Match the versions declared here and npm reports any conflict at install time.

Use

import type { Context } from "@deepseek-ai/cordis";
import { Type } from "@earendil-works/pi-ai";
import { defineTool } from "@earendil-works/pi-coding-agent";
import type {} from "@pi-harness/plugin-api";

export const helloTool = defineTool({
  name: "hello",
  label: "Hello",
  description: "Greet a person by name.",
  parameters: Type.Object({ name: Type.String({ description: "The name to greet." }) }),
  execute(_toolCallId, params) {
    return Promise.resolve({ content: [{ type: "text", text: `Hello, ${params.name}!` }], details: undefined });
  },
});

export default {
  name: "pi-hello",
  inject: ["piTools"],
  apply(context: Context) {
    context.effect(() => context.piTools.register(helloTool));
  },
};

The bare import type {} from "@pi-harness/plugin-api" is what applies the Context augmentation; without it context.piTools does not exist as far as TypeScript is concerned.

Plugin panel visible and read callbacks may accept an optional AbortSignal. The registry evaluates different panels concurrently, limits awaited panel work to two seconds and reuses one terminal result across overlapping browser polls. A timeout becomes an isolated panel error, so one broken extension cannot hide healthy panels or hold the control room on its connection screen. Callbacks should return control to the event loop promptly, observe the signal and release their own resources; JavaScript cannot preempt a callback that blocks the server thread synchronously.

examples/plugin-hello in the repository is this plugin with its tests.

Test a plugin against a real runtime

@pi-harness/core/test-harness boots the harness services a plugin injects - resources, session, tools and, with createTestRuntimeContext, the runtime - against a stub model provider, so a test can activate the plugin and call its tools without a network or an API key. Install @pi-harness/core as a devDependency to use it; the plugin itself still depends only on this package.

import { createTestRuntimeServices } from "@pi-harness/core/test-harness";

const { context } = await createTestRuntimeServices([]);
await context.plugin(helloPlugin, {});
expect(context.piTools.snapshot().customTools.map((tool) => tool.name)).toContain("hello");

The argument is the list of scripted model responses; pass [] for a plugin that never prompts the model, and use createTestRuntimeContext instead when the test needs the runtime to answer one. Every plugin under packages/plugins in the repository is tested this way.

Exports

  • Services — the service interfaces the harness puts on the Cordis Context (PiToolsSnapshot, PiSessionService, PiModelsService, PiRuntimeService, PiResourcesService, PiMcpService, PiTelemetryService, PiHarnessLaunch), the declare module augmentation that attaches them, and the PiToolRegistry / PiPluginUiRegistry a test can construct directly.
  • ConfigEmptyConfig for a plugin that takes no options, and assertKnownConfigKeys for rejecting unknown keys with a message that lists the supported ones. Both are built on Schemastery.
  • Workspace pathsresolveExistingWorkspacePath, resolveWorkspaceFilePath, prepareWorkspaceFile and isPathInside, which keep a plugin from escaping the workspace root through symlinks or ...
  • Bounded file accessreadBoundedFile and readBoundedTextFile with their BoundedFileSizeError / BoundedFileTypeError, which refuse oversized files, directories, FIFOs and symlinks rather than stranding the process on them. Both readers accept an optional AbortSignal and stop between filesystem reads when the caller cancels.
  • Atomic writesatomicWriteFile, which serialises concurrent writes to the same path, preserves an existing target's permission bits and fsyncs the containing directory.
  • Bounded commandsrunBoundedCommand(argv, cwd, timeoutMs, maxOutputBytes, signal?, options?), a no-shell, stdin-EOF runner with per-stream byte limits. Optional env applies only to the child; encoding: "buffer" preserves raw stdout/stderr on success and failure, while the default remains UTF-8 strings. Timeout must be an integer from 1 to 2147483647 ms; output limits must be non-negative safe integers. Failures retain bounded stdout/stderr and exec-style code/killed metadata. POSIX commands own a process group; failure signals the group and escalates after one second even if its leader exits. Escalation closes pipes and settles failure independently of escaped descendants. Windows uses taskkill tree termination with direct-process fallback; native Windows acceptance is outstanding. This is local lifecycle cleanup, not a sandbox or proof that deliberately escaped descendants or remote jobs stopped.

The command runner accepts an optional sixth argument { env } for an explicit child environment. Omit it to inherit the host environment. Supplying it does not mutate process.env; include inherited values explicitly when they are needed. Notification integrations use this to carry text as data rather than executable source.

License

MIT