<div align="center">
<h2>briefing</h2>

[![NPM version](https://img.shields.io/npm/v/briefing.svg?style=flat-square)](https://www.npmjs.com/package/briefing)

</div>

<h1></h1>

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

```bash
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

```ts
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

```ts
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.

```ts
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

```ts
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.

```ts
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

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

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

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

Output shape:

```text
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.

```ts
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

```ts
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:

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

## Rows

```ts
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

```ts
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

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

## Tasks

```ts
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

```ts
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

```ts
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

```ts
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

```ts
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

```ts
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

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

## Group

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

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

## Spinner

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

You can also control a spinner manually:

```ts
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

```ts
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

```ts
const timer = logger.timer();

await build();

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

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

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

## warnOnce

```ts
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

```ts
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

```ts
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`:

```ts
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
      },
    },
  },
});
```
