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

captain

v0.4.1

Published

A small, JSON-serializable workflow protocol and transport-neutral runtime.

Readme

captain

Captain v1 is a small, JSON-serializable workflow protocol. It ships four targets:

  • captain — workflow authoring with flow, phase, ask, ui, and ai.
  • captain/protocol — protocol version 1, Draft 2020-12 JSON Schema, generated TypeScript declarations, and conformance fixtures.
  • captain/runtime — transport-neutral runtime glue for a host server.
  • captain/cli — a Bun-powered full-screen terminal host built with OpenTUI.

Clients do not need a Captain SDK. Web, CLI, and iOS clients can render the frame JSON directly from the schema. Every yield is automatically one full-page frame and one submission scope; there are no page, form, or section UI types.

Terminal CLI

Installing Captain exposes a captain executable. The core authoring and runtime modules support Node, but the full-screen CLI currently requires Bun 1.3 or newer.

bun add captain
bunx captain start ./my-workflow.js --name Marshall

Every named flag is added to the workflow's durable context. Workflow params are intentionally not supported by the CLI yet. captain start loads ./captain.js when it exists; pass --config <file> to use a different runtime config:

// captain.js
export default {
  services: {
    ai: {
      async generate(input, { signal, update }) {
        update({ message: 'Generating…' })
        return { response: await generateWithMyProvider(input.prompt, { signal }) }
      },
    },
  },
}

The config may instead default-export a sync or async function receiving { cwd, env }. Without a config file, the CLI provides an OpenAI Responses API service configured by OPENAI_API_KEY, with optional OPENAI_MODEL, CAPTAIN_AI_MODEL, OPENAI_EFFORT, and OPENAI_BASE_URL values.

The TUI owns stdin and renders its alternate screen on stderr. Its completed result goes to stdout, so redirection and pipes remain clean. Strings and objects with a custom toString() are emitted directly; ordinary objects and arrays become pretty JSON.

bunx captain start ./write-readme.js > README.generated.md
bunx captain start ./scaffold.js | jq -r .files[0].content > index.js

All asks yielded in one frame appear together as one scrollable form. Tab and Shift+Tab move between fields without losing answers. Enter advances from single-line and choice fields; on the final field it validates and submits the whole form. Multiline fields use Alt+Enter to advance while plain Enter adds a line. Enter also continues display-only pages. The persistent navigation controls use Ctrl+P for back and Ctrl+N for forward; Ctrl+O opens the workflow drawer, Ctrl+R retries failed work, and Ctrl+C exits.

Applications can embed the same behavior from captain/cli, supplying workflows and runtime options entirely in memory. Module calls return the raw workflow value, leaving output handling to the application:

import { createTui } from 'captain/cli'
import { scaffold, release } from './workflows.js'

const tui = createTui({
  workflows: { scaffold, release },
  runtimeOptions: {
    services: { ai: { generate: myAiService } },
  },
})

const result = await tui.run('scaffold', {
  context: { name: 'Marshall' },
})

await handleResult(result)

createTui() also accepts a caller-created Captain runtime, custom input and output streams, or a createUI() factory. formatResult() is exported separately for embedded CLIs that want Captain's built-in stdout policy. See examples/cli for realistic scaffolding, README, release-note, UI, and embedded-module workflows.

Workflow authoring

npm install captain
import { ai, ask, flow, phase, ui } from 'captain'

export const welcome = flow(function* () {
  phase('Your profile')

  const { name, plan } = yield [
    ui.md`# Welcome`,
    {
      name: ask.text('Name'),
      plan: ask.multiline('What are you building?'),
    },
  ]

  const summary = yield ai`Summarize ${name}'s plan: ${plan}`

  yield ui.notice(String(summary.response), { tone: 'success' })
  return { name, plan, summary }
})

Arrays determine presentation order. Object properties name value-producing results, including nested results. A directly yielded ask or service returns its value directly. ui(...) is only a composition convenience.

Workflows receive at most one parameter object. Hosts may also seed durable context before the first generator step:

export const welcome = flow(function* (params) {
  this.name ??= yield ask.text('What is your name?', { key: 'name' })
  yield ui.md`# Hello, ${this.name}! Welcome to ${params.product}.`
})

await runtime.start('welcome', {
  params: { product: 'Captain' },
  context: { name: 'Ada' },
})

Here the initial question is skipped. params is immutable invocation input by convention; context becomes durable workflow state through this, is saved with the session, and is never included in public snapshots. Both values must be JSON-compatible objects. Positional parameter arrays are not supported.

The exact v1 vocabulary is listed in Primitives.

AI authoring

ai is multiline prompting sugar for service.ai.generate. It uses the same automatic replay-stable service keys and produces the same canonical service effect:

const result = yield ai`
  Create a character based on:

  ${params}
`.as({
  type: 'object',
  required: ['name', 'lore'],
  additionalProperties: false,
  properties: {
    name: { type: 'string', minLength: 5, maxLength: 10 },
    lore: { type: 'string' },
  },
})

const character = result.response

String interpolations remain text; other values are formatted as JSON. .as(schema) supplies responseSchema to the host AI service, and Captain validates output.response before completing the effect. Invalid structured output becomes a normal retryable service error. Model, reasoning effort, credentials, and provider-specific configuration belong to the host AI service. An explicit { key } remains available when intentional logical reuse is needed, but is not required.

Host services

Every runtime supplies an AI generation handler. It receives immutable input and returns output; progress is a separate replace-on-update JSON value.

import { createRuntime } from 'captain/runtime'

const runtime = createRuntime({
  services: {
    ai: {
      async generate(input, { signal, update }) {
        update({ message: 'Generating…' })
        return { response: await generateText(input, { signal }) }
      },
    },
  },
})

Services retain stable-key reuse, retries, cancellation, concurrent execution, restoration, and optional lifecycle renderers. Service renderers receive { status, input, progress, output, error } and return ordinary UI effects. Without a renderer, clients receive core.ui.service.

Minimal Bun API

The host owns routing, authentication, persistence, polling, and SSE. A minimal API can map directly to start, get, and act:

import { welcome } from './workflows/welcome.js'

runtime.register('welcome', welcome)

Bun.serve({
  async fetch(request) {
    const url = new URL(request.url)
    const parts = url.pathname.split('/').filter(Boolean)

    if (request.method === 'POST' && url.pathname === '/workflows/welcome') {
      return Response.json(await runtime.start('welcome'))
    }

    if (request.method === 'GET' && parts[0] === 'workflows' && parts[2] === 'sessions') {
      return Response.json(runtime.get(parts[1], parts[3]))
    }

    if (request.method === 'POST' && parts[0] === 'workflows' && parts[2] === 'sessions') {
      return Response.json(await runtime.act(parts[1], parts[3], await request.json()))
    }

    return new Response('Not found', { status: 404 })
  },
})

A waiting ask frame is submitted with answers keyed by ask key:

{
  "action": "next",
  "frame": "current-frame-id",
  "revision": 3,
  "answers": { "name-effect-key": "Ada" }
}

start, get, act, restoreSession, and interrupt return only the public SessionSnapshot. exportSession returns opaque host persistence data using captain-session version 3.

Protocol development

bun test
bun run check:protocol
bun run typecheck

There is also an opt-in integration test that runs the character workflow against the real OpenAI Responses API:

OPENAI_API_KEY=your-key bun run test:openai

It defaults to gpt-5.6; set OPENAI_MODEL to override that choice. Live tests run only through test:openai; normal bun test and bun run check skip them even when OPENAI_API_KEY is exported. The OpenAI SDK is a development dependency, and the package's files allowlist excludes the entire test/ directory from published artifacts.

Run bun run generate:protocol after changing src/protocol/schema.json. Scheduling, until, uploads, resources, notifications, capabilities, custom protocol extensions, and client SDKs remain post-v1.

Primitives

Ask

  • ask.text
  • ask.multiline
  • ask.email
  • ask.secret
  • ask.url
  • ask.tel
  • ask.number
  • ask.date
  • ask.time
  • ask.checkbox
  • ask.confirm
  • ask.choice

UI

  • ui.markdown
  • ui.heading
  • ui.notice
  • ui.progress
  • ui.image
  • ui.video
  • ui.service (runtime-generated fallback)

Service

  • service.ai.generate