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

briefing

v0.0.5

Published

Lightweight terminal logger for CLI, build, and release tools.

Readme

NPM version

briefing is a lightweight terminal logger for CLIs, build tools, and release tools. It provides stable, testable log output plus common terminal presentation primitives such as summary, rows, tasks, JSON/YAML, tree, URLs, spinner, and progress.

Installation

pnpm add briefing

API Overview

  • new Logger(options)
  • logger.colors
  • logger.log(...content)
  • logger.info(...content)
  • logger.success(...content)
  • logger.warn(...content)
  • logger.error(...content)
  • logger.debug(content, meta)
  • logger.child(scope)
  • logger.summary(valuesOrBlock)
  • logger.rows(block)
  • logger.commandBlock(block)
  • logger.code(block)
  • logger.diff(block)
  • logger.result(block)
  • logger.note(block)
  • logger.tasks(tasksOrBlock)
  • logger.json(valueOrBlock)
  • logger.yaml(valueOrBlock)
  • logger.yml(valueOrBlock)
  • logger.tree(block)
  • logger.urls(block)
  • logger.group(titleOrBlock, callback)
  • logger.spinner(content)
  • logger.withSpinner(content, callback)
  • logger.progress(content, options)
  • logger.time(content, callback)

Quick Start

import { Logger } from "briefing";

const logger = new Logger({
  scope: "publish",
  prefix: "auk",
  colors: true,
  verbose: true,
});

logger.step("Resolve publish plan");
logger.success(["Published ", logger.package("@demo/ui")]);
logger.warn("workspace dependency should use workspace:*");
logger.error(["Publish failed at ", logger.path("packages/ui")]);

Constructor Options

const logger = new Logger({
  scope: "publish",
  prefix: "auk",
  colors: true,
  silent: false,
  verbose: false,
  sink: undefined,
});

These are the defaults when options are omitted:

  • scope: ""
  • prefix: ""
  • colors: true
  • silent: false
  • verbose: false
  • sink: console-backed stdout/stderr output

Options:

  • scope: the current logger scope, such as publish or build.
  • prefix: an optional short prefix, such as auk.
  • colors: whether to enable colors. Defaults to true.
  • silent: whether to disable all output. Defaults to false.
  • verbose: whether to print debug logs. Defaults to false.
  • sink: a custom output target. Defaults to console.log and console.error, with terminal metadata from process.stdout.

Colors

Each Logger exposes the same color palette it uses internally. Use it when adjacent output needs to match logger colors without installing a separate color dependency.

import { Logger } from "briefing";
import type { ColorPalette } from "briefing";

const logger = new Logger({ colors: true });
const colors: ColorPalette = logger.colors;

logger.step(["Build ", colors.green("ready")]);

Basic Logs

logger.log("plain message");
logger.info("prepare build");
logger.success("build complete");
logger.warn("missing optional config");
logger.error("build failed");
logger.error(new Error("build failed"));
logger.error("Publish failed: ", new Error("Cannot upload artifact"));
logger.info(
  "Publish ",
  logger.package("@demo/ui"),
  " from ",
  logger.path("packages/ui"),
);

warn and error write to stderr. Other log levels write to stdout.

Basic log methods accept multiple content arguments. Passing a single array is still supported for existing token-based composition.

When logging an Error, normal output prints its message. This also works when an Error is mixed into an array or multiple content arguments. With verbose: true, Error output includes the stack trace. Non-primitive values are rendered with util.inspect.

Debug

debug is hidden by default. Enable verbose to print it.

const logger = new Logger({
  verbose: true,
});

logger.debug("Resolved publish plan", {
  packages: ["@demo/theme", "@demo/ui"],
});

Debug metadata is rendered as a bounded preview so large payloads do not flood the terminal. It uses util.inspect with limited depth, array length, and string length. Use logger.json() or logger.yaml() when you intentionally want a full structured data block.

debug keeps its content, meta shape so the second argument is always treated as diagnostic metadata. Use an array for composed debug messages.

Scope and Child Scope

const publish = new Logger({
  scope: "publish",
  prefix: "auk",
});

const preflight = publish.child("preflight");

preflight.step(["Run ", preflight.command("pnpm publish --dry-run")]);

Output shape:

auk publish › preflight Run `pnpm publish --dry-run`

Tokens

Package names, paths, commands, versions, and similar inline entities are not parsed automatically. Use token helpers when you want highlighting.

logger.step([
  "Publish ",
  logger.package("@demo/ui"),
  " with ",
  logger.command("pnpm publish"),
]);

logger.file("+", "dist/index.js");
logger.item(logger.status("ready"));

Available tokens:

  • logger.package(value)
  • logger.path(value)
  • logger.command(value)
  • logger.version(value)
  • logger.value(value)
  • logger.url(value)
  • logger.duration(milliseconds)
  • logger.size(bytes)
  • logger.status(value)

Summary

logger.summary({
  title: "Publish plan",
  values: {
    mode: "single-package",
    version: logger.version("0.1.0-beta.a1b2c3"),
    targets: 3,
    dryRun: false,
  },
});

You can also pass values directly:

logger.summary({
  package: logger.package("@demo/ui"),
  version: logger.version("1.0.0"),
});

Rows

logger.rows({
  title: "Publish targets",
  columns: ["package", "version", "status"],
  rows: [
    [logger.package("@demo/theme"), logger.version("1.0.0"), "ready"],
    [logger.package("@demo/ui"), logger.version("1.0.0"), "skipped"],
  ],
});

Command Block

logger.commandBlock({
  title: "Publish packages",
  description: "Build and publish selected packages.",
  command: "auk publish --filter @demo/* --version beta",
  options: [
    {
      name: "--filter <name>",
      description: "Select workspace packages by exact name or scope/*.",
    },
    {
      name: "--dry-run",
      description: "Run build and pnpm publish --dry-run only.",
    },
  ],
  examples: [
    {
      command: "auk publish --dry-run",
      description: "Preview the current package publish flow.",
    },
  ],
});

Note

logger.note({
  title: "Next steps",
  body: [
    ["Run ", logger.command("pnpm install"), " if dependencies changed."],
    ["Retry with ", logger.command("auk publish --dry-run"), "."],
  ],
});

Tasks

logger.tasks({
  title: "Task report",
  tasks: [
    { title: "Resolve publish plan", status: "success" },
    { title: "Build packages", status: "running" },
    { title: "Verify metadata", status: "pending" },
    { title: "Publish @demo/ui", status: "warn" },
    { title: "Publish @demo/widgets", status: "error" },
    { title: "Notify subscribers", status: "skipped" },
  ],
});

Supported statuses:

  • pending
  • running
  • success
  • warn
  • error
  • skipped

JSON and YAML

logger.json({
  title: "Package JSON",
  value: {
    name: "@demo/ui",
    version: "1.0.0",
  },
});

logger.yaml({
  title: "Publish YAML",
  value: {
    packages: ["@demo/theme", "@demo/ui"],
    dryRun: false,
  },
});

logger.yml() is an alias for logger.yaml().

JSON and YAML use the same framed presentation as logger.code(), with syntax highlighting when colors are enabled.

Code

logger.code({
  title: "Install script",
  language: "bash",
  code: ["pnpm install", "pnpm build"],
});

code accepts a string or an array of lines. The block renders with a straight corner frame, the language label in the top-left corner, and spacing between the label and code content.

Default highlighted languages are bash, c, cpp, css, go, html, javascript, json, less, rust, shellscript, typescript, and yaml, with common aliases such as sh, js, ts, golang, c++, rs, and yml.

Diff

logger.diff({
  title: "Package diff",
  diff: [
    "diff --git a/package.json b/package.json",
    "--- a/package.json",
    "+++ b/package.json",
    "@@ -1,5 +1,5 @@",
    " {",
    '   "name": "@demo/ui",',
    '-  "version": "0.9.0",',
    '+  "version": "1.0.0",',
    " }",
  ],
});

Result

logger.result({
  title: ["Published ", logger.package("@demo/ui")],
  status: "success",
  body: ["Registry updated"],
  details: {
    version: logger.version("1.0.0"),
    duration: logger.duration(1320),
  },
});

Tree

logger.tree({
  title: "Output files",
  tree: [
    "dist/index.js",
    "dist/index.d.ts",
    {
      name: "packages",
      children: [
        {
          name: "ui",
          children: ["src/index.ts", "src/Button.tsx"],
        },
      ],
    },
  ],
});

URLs

logger.urls({
  title: "Release links",
  urls: [
    "https://example.com/releases/1.0.0",
    {
      label: "Registry",
      url: "https://registry.npmjs.org/@demo/ui",
    },
  ],
});

Group

await logger.group("Build phase", async () => {
  const build = logger.child("build");

  build.step("Build packages");
  build.success("Build complete");
});

Spinner

await logger.withSpinner("Run publish preflight", async (spinner) => {
  spinner.text(["Checking ", logger.package("@demo/theme")]);
  await runPreflight();
});

You can also control a spinner manually:

const spinner = logger.spinner("Run build");

spinner.start();
spinner.text("Bundling files");
spinner.succeed("Build complete");

Spinner output uses a live terminal line only when the sink provides liveLine. Otherwise, it falls back to stable logs.

Progress

const progress = logger.progress("Publishing packages", {
  total: 2,
});

progress.update(1, ["Published ", logger.package("@demo/theme")]);
progress.update(2, ["Published ", logger.package("@demo/ui")]);
progress.succeed("Published 2 packages");

Progress output uses a live terminal line only when the sink provides liveLine. Otherwise, it falls back to stable logs.

Timer

const timer = logger.timer();

await build();

logger.success(["Build complete in ", logger.duration(timer.elapsed())]);

You can also wrap an async task with time():

await logger.time("Build packages", async () => {
  await build();
});

warnOnce

logger.warnOnce("workspace dependency should use workspace:*");
logger.warnOnce("workspace dependency should use workspace:*");
logger.warnOnce("package ", logger.package("@demo/ui"));
logger.warnOnce(["package ", logger.package("@demo/ui")]);

The same rendered content is printed only once. Tokens and composed content use their rendered stable text as the dedupe key, so rest arguments and the single-array composition form dedupe together.

Raw Output

logger.raw("raw stdout line");
logger.raw("raw stderr line", "stderr");
logger.newline();
logger.newline(2);

raw() does not add scope, prefix, or colors.

Custom Sink

const logs: Array<string> = [];

const logger = new Logger({
  colors: false,
  sink: {
    stdout: (message) => logs.push(message),
    stderr: (message) => logs.push(message),
    isTTY: false,
    columns: 80,
  },
});

A custom sink is useful for tests, log collection, or integration with another output system.

stdout and stderr are required. isTTY, columns, and liveLine are optional. columns is used to size live progress bars.

Custom sinks can opt into live output by providing liveLine:

const logger = new Logger({
  sink: {
    stdout: console.log,
    stderr: console.error,
    isTTY: true,
    liveLine: {
      update: (message) => {
        // update the active live line
      },
      clear: () => {
        // clear the active live line
      },
      done: () => {
        // finish the live line
      },
    },
  },
});