briefing
v0.0.5
Published
Lightweight terminal logger for CLI, build, and release tools.
Maintainers
Readme
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 briefingAPI Overview
new Logger(options)logger.colorslogger.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:truesilent:falseverbose:falsesink: console-backed stdout/stderr output
Options:
scope: the current logger scope, such aspublishorbuild.prefix: an optional short prefix, such asauk.colors: whether to enable colors. Defaults totrue.silent: whether to disable all output. Defaults tofalse.verbose: whether to print debug logs. Defaults tofalse.sink: a custom output target. Defaults toconsole.logandconsole.error, with terminal metadata fromprocess.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:
pendingrunningsuccesswarnerrorskipped
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
},
},
},
});