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

@loopingai/plugins

v0.2.1

Published

Optional, composable capabilities for Looping agents: ARC-AGI-3, browser rendering, workspace, episodic recall, and pre-turn triage. One subpath per plugin, so a bundle grows only with what it imports.

Downloads

340

Readme

@loopingai/plugins

Optional, composable capabilities for Looping agents.

One subpath per plugin, one factory per subpath, config passed at instantiation. Your bundle grows only with what you import.

npm install @loopingai/plugins

Part of a three-package split: @loopingai/core (the mandatory foundation) · @loopingai/plugins (this) · looping-starter (a working agent that composes them).


The one file you edit

// src/plugins.ts
import { arcAgi } from "@loopingai/plugins/arc-agi";
import { browser } from "@loopingai/plugins/browser";
import { recall } from "@loopingai/plugins/recall";

export interface PluginHost {
  env: Env;
  storage: DurableObjectStorage;
  /** The verified caller this Durable Object belongs to. See below. */
  callerKey: () => string;
}

export const plugins = ({ env, storage, callerKey }: PluginHost) => [
  arcAgi({ apiKey: env.ARC_API_KEY, storage }),
  browser({ binding: env.BROWSER }),
  recall({ ai: env.AI, index: env.VECTORIZE, namespace: callerKey })
];

Delete a line and that module leaves your bundle entirely. Nothing in core imports a plugin, and there is no root barrel@loopingai/plugins on its own does not resolve — so the guarantee is structural rather than a tree-shaker's opinion. npm run verify:exports asserts it on the built graph before every publish.

plugins is a function, not a module-level array: on Workers env does not exist at module scope, and core's registry is built per Durable Object instance in onStart().

export class MyAgent extends Agent<Env> {
  /** Set on the first verified request; constant thereafter. See below. */
  private identity?: string;

  async onStart() {
    this.runtime = createAgentRuntime({
      config,
      plugins: plugins({
        env: this.env,
        storage: this.ctx.storage,
        // A thunk, not a value: `onStart` runs before any request, so the caller
        // is not known yet. The DO is keyed 1:1 by that caller, so it is constant
        // once it is — this just defers reading it until it exists.
        callerKey: () => this.identity ?? ""
      }),
      env: this.env // verify every plugin's declared bindings exist, at startup
    });
  }

  async onTurn(turn: AgentTurn, identity: GatewayIdentity) {
    this.identity ??= identity.key!;
    // …
  }
}

That deferral is the whole reason /recall takes namespace as a function. Anything else needing per-caller state takes it the same way.


The plugins

| Subpath | What it adds | Needs | | ------------------------------ | ------------------------------------------------------------------------------------- | ----------------------------- | | /arc-agi | Play ARC-AGI-3 games — a delegable subtask type, a catalogue tool, a scorecard ledger | ARC_API_KEY | | /browser | Read web pages via Browser Rendering Quick Actions | BROWSER (paid plan) | | /recall | Episodic memory over Vectorize — search history that compaction folded away | VECTORIZE (1024-dim/cosine) | | /triage | A pre-turn gate: is this message even for me? | — | | /workspace | A durable file store for long subagent runs, plus tools over it | @cloudflare/shell |

Each directory has its own README with the config shape and a paste-ready wrangler.jsonc snippet — a plugin cannot add its own binding, which is why it declares what it needs.


Writing one

import { definePlugin, type AgentPlugin } from "@loopingai/core";

export function scraper(config: { apiKey: string }): AgentPlugin {
  return definePlugin({
    key: "scraper",
    mainAgentTools: () => ({ fetchPage: /* … */ }),
    capability: "You can fetch and summarize a page.",
    requires: { secrets: ["SCRAPER_API_KEY"] }
  });
}

Three rules the whole design rests on:

  • Never name a consumer's Env. It is an ambient interface wrangler types generates into their app. Take bindings and secrets as config, which is also the only thing that works on Workers, where env has no module scope.
  • definePlugin sets contractVersion from the core you compiled against. Never write that number as a literal — the point is that it moves, so a version train that leaves one repo behind fails at startup with a sentence instead of a structural-type error.
  • Declare a capability block in exactly one place. If your plugin has a subtaskType, put it there; otherwise on the plugin. Both are rendered, by different call sites.

Testing

Specs run inside real workerd via @cloudflare/vitest-pool-workers, with the harness from @loopingai/core/testing.

npm test          # 231 specs, no credentials and no network
npm run check     # prettier + eslint + tsc + build
npm run verify:exports

test/arc-agi/recorded.spec.ts drives the real ARC API and replays a committed cassette, so it needs no key either. Re-record it against the live API with:

npm run test:record   # real ARC_API_KEY in .env.test (see .env.test.example)

The key reaches the live ARC API and nothing else: the recorder excludes the auth header, so it never lands in the committed cassette. It reaches the spec only as a Miniflare binding — .env.test is loaded into Node's process.env, which a spec running in workerd cannot see, so vitest.config.ts hands it across explicitly.

License

GPL-3.0-only