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

@swirls/aboard

v0.1.0

Published

Manage and observe agentic onboarding flows. Declare the steps an AI agent should take to onboard a user, mount them as endpoints, generate a 'copy to deploy' prompt, and track every session.

Readme

aboard

An open protocol — and a library — for agentic onboarding flows. Declare the steps an AI agent should take to onboard a user, mount them as discoverable API endpoints, hand the agent a generated prompt, and get complete visibility into every session.

Status: 0.1.0 — early but functional. Core API + the v0.1 protocol (discovery, sessions, steps, identity, revocation, stuck webhooks) are implemented and tested. Expect breaking changes before 1.0. A hosted dashboard is on the roadmap. See SPEC.md for the wire protocol.


Why

"Copy this prompt to deploy" is a great onboarding experience — until the user pastes the prompt into their agent and you lose all visibility. You can't tell how far they got, you can't change the steps after the fact, and you don't find out when an agent gets stuck.

WalkMe and Pendo were built to give SaaS companies control and observability over human onboarding. aboard does the same for agent onboarding:

  • Declare the steps an agent should take (auth → create org → deploy …).
  • Mount them as tracked HTTP endpoints on your own API.
  • Discover the flow as a generated markdown prompt and a machine-readable JSON descriptor.
  • Observe every session: which steps ran, which failed, where agents got stuck.
  • Get alerted via webhook when an agent can't get past a step.

Complementary to auth.md

aboard composes with agent-registration protocols like auth.md — it doesn't replace them.

  • auth.md answers "how does an agent register and get a token?" — identity.
  • aboard answers "what does the agent do, in what order, after it has a token — and how do we watch it?" — onboarding.

The agent registers via auth.md (or any OAuth flow), gets an access token, then presents it to start an aboard session. aboard verifies the token (via your verifyIdentity), binds the identity to the session, and issues a short-lived session token used to drive the steps. See "Identity" below.


Install

bun add @swirls/aboard

Built for the Bun runtime (Web Request/Response, bun:sqlite, Bun.sql). engines.bun >= 1.0.


Quick start

import { aboard } from "@swirls/aboard";
import { sqliteAdapter } from "@swirls/aboard/adapters/sqlite";

export const ab = aboard({
  database: sqliteAdapter("onboarding.sqlite"),
  secret: process.env.ABOARD_SECRET!, // signs session tokens
  name: "Swirls",
  baseUrl: "https://api.yourapp.com",
  steps: [
    { id: "auth", description: "Authenticate with the user's token." },
    { id: "create_org", description: "Provision a workspace via the API." },
    { id: "deploy_hook", description: "Set up the first webhook listener." },
  ],
});

Bun.serve({
  port: 3000,
  async fetch(request) {
    const res = await ab.handler(request);
    if (res.status === 404) return new Response("Not found", { status: 404 }); // fall through to your app
    return res;
  },
});

What gets mounted:

| Method & path | Purpose | | --- | --- | | GET /.well-known/agent-onboarding | Machine-readable JSON descriptor | | GET /.well-known/agent-onboarding/:slug | Generated prompt (markdown; JSON via Accept) | | GET /onboarding.md | The prompt at the site root (mirrors /auth.md) | | POST /api/onboarding/sessions | Start a session, returns a signed session token | | POST /api/onboarding/steps/:id | Run/track a step | | POST /api/onboarding/sessions/:id/revoke | Revoke a session |


How it works

  1. You declare an ordered list of steps. Each step becomes an endpoint.
  2. aboard publishes the flow two ways: a markdown prompt (the "copy to deploy" payload) and a JSON descriptor (so tools/agents can parse the structure). Both live under /.well-known/agent-onboarding.
  3. An agent (optionally authenticating first — see Identity) starts a session and calls each step in order, passing its session token.
  4. Every call is recorded as an event, so you can reconstruct the full flow.
  5. If an agent fails a step repeatedly, a stuck webhook fires.
agent ──GET /.well-known/agent-onboarding ─────────▶ descriptor (JSON)
agent ──POST /api/onboarding/sessions ─────────────▶ { sessionId, sessionToken }
agent ──POST /api/onboarding/steps/auth ───────────▶ { status:"completed", next:"create_org", progress:{1/3} }
agent ──POST /api/onboarding/steps/create_org ─────▶ { status:"completed", next:"deploy_hook", progress:{2/3} }
agent ──POST /api/onboarding/steps/deploy_hook ────▶ { status:"completed", next:null, progress:{3/3} }

Concepts

Steps

{
  id: "create_org",
  description: "Provision a workspace via the API.",

  // Optional server-side logic. Return value is stored as the step output.
  // Throw to mark the step failed (and eventually fire the stuck webhook).
  run: async ({ body, principal, setMetadata }) => {
    const org = await provisionOrg(body, principal?.subject);
    setMetadata({ orgId: org.id }); // persisted onto the session
    return { orgId: org.id };
  },

  dependsOn: ["auth"],          // defaults to the previous step (linear flow)
  artifact: { name: "starter-kit", url: "https://…/kit.zip" },
  onStuck: { afterAttempts: 3, webhook: "https://hooks.swirls.dev/stuck" },
}

A step with no run simply marks itself completed and records the event — useful when the agent does the work elsewhere (e.g. running a CLI) and reports progress.

Identity (auth.md / OAuth interop)

By default sessions are anonymous. To require a verified identity, provide verifyIdentity — it receives the incoming bearer token (e.g. an auth.md/OAuth access token) and returns the principal, or null to reject:

aboard({
  // …
  auth: { discovery: "https://api.yourapp.com/auth.md" }, // tells agents where to get a token
  verifyIdentity: async (token) => {
    const claims = await verifyAccessToken(token); // your check
    if (!claims) return null;
    return { subject: claims.sub, agentProvider: claims.agent_provider, scopes: claims.scope?.split(" ") };
  },
});

When set, POST /sessions requires Authorization: Bearer <accessToken>. The verified principal is bound to the session, exposed to step run handlers as ctx.principal, and attached to events — so observability knows which user and which agent platform (OpenAI/Anthropic/Cursor/…) is onboarding.

Sessions & tokens

Two distinct tokens, deliberately separated:

  • Identity token (optional, external) — an auth.md/OAuth access token that says who the agent/user is. Verified by verifyIdentity at session creation.
  • Session token (issued by aboard) — a progress credential: an HMAC-SHA256 over a { sid, exp } payload. Short-lived (default 24h, set sessionTokenTtl), tamper-evident, sent as Authorization: Bearer on step calls. Expired or tampered tokens are rejected.

Revocation

ab.revokeSession(id) (or POST /sessions/:id/revoke) marks a session abandoned; its session token immediately stops working (403 session_revoked). The endpoint accepts the session's own token or your adminToken.

Events & observability

Every meaningful action records an immutable event:

session.created · step.started · step.completed · step.failed · agent.stuck · session.completed · session.revoked

aboard({ /* … */, onEvent: (event) => track(event) }); // real-time
const events = await ab.getEvents(sessionId);                // per session
const all = await ab.listSessions();

Stuck detection

When a step's run throws, the attempt is recorded. Once attempts on that step reach afterAttempts (default 3), an agent.stuck event fires and the configured webhook is POSTed:

{
  "event": "agent.stuck",
  "sessionId": "…",
  "stepId": "deploy_hook",
  "attempts": 3,
  "error": "Connection refused",
  "principal": { "subject": "user_123", "agentProvider": "anthropic" },
  "metadata": { /* accumulated session metadata */ }
}

The webhook is best-effort — a failure to notify never breaks the agent's call.


Discovery

aboard publishes the same flow in two representations (like auth.md's human-and-machine-readable duality):

# Machine-readable descriptor (JSON)
curl https://api.yourapp.com/.well-known/agent-onboarding
curl https://api.yourapp.com/.well-known/agent-onboarding/default.json
curl -H 'accept: application/json' https://api.yourapp.com/.well-known/agent-onboarding/default

# Human/agent prompt (markdown)
curl https://api.yourapp.com/.well-known/agent-onboarding/default
curl https://api.yourapp.com/onboarding.md

The descriptor (ab.getDescriptor()) is the structured source of truth — steps, dependencies, endpoints, artifacts, and the auth requirement. The full schema is in SPEC.md.


For agents

The generated prompt says this, tailored to your steps:

  1. (If required) Get an access token — register via the auth.discovery URL (e.g. an auth.md), and present it as Authorization: Bearer <accessToken> when starting the session.
  2. Start a sessionPOST {baseUrl}{basePath}/sessions. Keep the sessionToken.
  3. Authenticate every step with Authorization: Bearer <sessionToken>.
  4. Call steps in order, starting from the next value. Each response gives the new next and your progress.
  5. Send required inputs as a JSON body; download artifacts when a step response includes one.
  6. Handle failures409 means call the missing steps first; 422 means read error, fix it, and retry the same endpoint.
  7. Done when "next": null and progress.completed === progress.total.

Programmatic API

const ab = aboard(config);

ab.handler(request): Promise<Response>          // mount this
ab.getPrompt(): string                          // markdown prompt
ab.getDescriptor(): OnboardingDescriptor        // machine-readable descriptor
ab.createSession({ metadata?, identity? }): Promise<{ sessionId, sessionToken, session }>
ab.getSession(id): Promise<SessionRecord | null>
ab.listSessions(): Promise<SessionRecord[]>
ab.getEvents(sessionId): Promise<OnboardingEvent[]>
ab.revokeSession(sessionId): Promise<boolean>
ab.basePath / ab.slug / ab.wellKnownPath

Configuration

| Option | Type | Default | Description | | --- | --- | --- | --- | | database | Adapter | — | Storage backend (required) | | steps | Step[] | — | Ordered steps (required, ≥1) | | secret | string | — | Signs session tokens (required) | | basePath | string | /api/onboarding | Where the handler is mounted | | baseUrl | string | — | Public API URL, for absolute URLs in prompt/descriptor | | slug | string | default | Well-known slug | | name | string | Onboarding | Product name in the prompt | | sessionTokenTtl | number | 86400 | Session token lifetime (seconds) | | verifyIdentity | (token, request) => Identity \| null | — | Establish identity from an access token; requires it when set | | auth | { required?, discovery?, description? } | — | Where/how agents get a token | | adminToken | string | — | Enables + protects the read endpoints | | onEvent | (event) => void | — | Called for every event | | onStuck | { afterAttempts?, webhook? } | afterAttempts: 3 | Global stuck behaviour |


Adapters

import { memoryAdapter } from "@swirls/aboard";                 // tests / prototyping
import { sqliteAdapter } from "@swirls/aboard/adapters/sqlite"; // bun:sqlite
import { pgAdapter } from "@swirls/aboard/adapters/pg";         // Bun.sql (Postgres)

memoryAdapter();
sqliteAdapter("onboarding.sqlite");
pgAdapter({ connectionString: process.env.DATABASE_URL! });

The sqlite/pg adapters create their tables automatically. Implement the Adapter interface to back storage with anything else.


Mounting in other frameworks

handler is a standard Web fetch handler:

// Hono
app.all("/api/onboarding/*", (c) => ab.handler(c.req.raw));
app.get("/.well-known/agent-onboarding/*", (c) => ab.handler(c.req.raw));
app.get("/onboarding.md", (c) => ab.handler(c.req.raw));

// Next.js (app router) — app/api/onboarding/[[...rest]]/route.ts
export const POST = (req: Request) => ab.handler(req);
export const GET = (req: Request) => ab.handler(req);

Security

  • Keep secret private and stable — rotating it invalidates existing session tokens.
  • verifyIdentity is where you trust an external token; validate signature, audience, and expiry there. Setting auth.required without a verifyIdentity throws — aboard fails closed rather than accept unverifiable tokens.
  • adminToken (separate from secret) gates the built-in read endpoints and is compared in constant time.
  • POST /sessions is public unless verifyIdentity is set; add rate limiting if needed.

Built in: session tokens are HMAC-signed with an expiry; incoming metadata is stripped of prototype-pollution keys (__proto__/constructor/prototype); JSON bodies are capped at 256 KB; stuck webhooks use a 5s timeout and reject redirects. SQL adapters use parameterized queries throughout.


Development

bun install
bun test          # run the test suite
bun run typecheck # tsc --noEmit
bun run build     # emit dist/ (JS + .d.ts)
bun run example   # start the example Swirls onboarding server on :3000

Roadmap

  • Hosted dashboard to visualise sessions and pinpoint where agents succeed/fail.
  • First-class auth.md verifier helpers.
  • Richer artifact handling (checksums, auth'd downloads) and more adapters.

License

MIT © ByteSlice LLC, DBA Swirls.ai