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

@ts-utilities/notify

v0.1.12

Published

Sonner like notifier for cli

Readme

@ts-utilities/notify

Sonner-like CLI notifier — create, update, and dismiss terminal notifications with a clean id-based API.

√ Build succeeded
× Tests failed
▲ Warning: deprecated API
i Info: server starting
● Running
⠋ Loading...

Install

npm install @ts-utilities/notify

Quick Start

import { notify } from '@ts-utilities/notify';

notify('Hello');
notify.success('Done');
notify.error('Failed');
notify.warning('Caution');
notify.info('Note');
notify.loading('Working…');

const id = notify('First');
notify('Replaced', { id });    // same id → replaces in place

notify.dismiss(id);            // remove one
notify.clear();                // remove all

Inline Styles

Style specific parts of a message without affecting the whole notification.

import { notify } from '@ts-utilities/notify';

notify([
  'Build ',
  { text: 'succeeded', style: { color: 'green', bold: true } },
  ' in 2.4s',
]);

notify.error([
  { text: '✗ ', style: { color: 'red' } },
  'Connection refused on ',
  { text: 'localhost:3000', style: { color: 'cyan', underline: true } },
]);

Works everywhere — notify(), update(), promise(), progress():

// With type helpers
notify.success([
  { text: '✓ ', style: { color: 'green' } },
  'Done in ',
  { text: '2.4s', style: { color: 'cyan' } },
]);

// With update
const h = notify.loading('Working…');
h.update({ message: [{ text: '✓', style: { color: 'green' } }, ' Done'] });

// With promise
await notify.promise(fetch('/api'), {
  loading: ['Fetching ', { text: '/api', style: { color: 'cyan' } }, '…'],
  success: (data) => [`Got `, { text: `${data.status}`, style: { color: 'green' } }],
  error: [`Request `, { text: 'failed', style: { color: 'red' } }],
});

Plain strings still work — no migration needed.

notify('Hello');
notify.success('Done');

Icons

Override the default type-based icon with any character or emoji.

notify('Rocket launch', { icon: '🚀' });
notify.success('Party time', { icon: '🎉' });
notify.loading('Working', { icon: '🌀' });

Custom icons work with update to change the icon dynamically:

const { id } = notify.loading('Processing', { id: 'task' });
notify.update(id, {
  type: 'success',
  message: 'Done!',
  options: { icon: '✅' },
});

When icon is set, it completely replaces the default character (including loading/progress spinners).

Style

Every notification accepts a style option to control color, background, modifier flags, and mode.

notify('Blue bold text', {
  style: { color: 'blue', bold: true },
});

notify('Custom hex color', {
  style: { color: '#ff4500' },
});

notify('RGB color + underline', {
  style: { color: 'rgb(255, 165, 0)', underline: true },
});

notify('White on blue', {
  style: { color: 'white', backgroundColor: 'blue', bold: true },
});

notify('Multiple modifiers', {
  style: { bold: true, italic: true, underline: true, color: 'cyan' },
});

Color

| Type | Example | |------|---------| | Named chalk | 'red', 'green', 'blue', 'cyan', 'magenta', 'yellow', 'white', 'gray', 'black' | | Hex | '#ff0000', '#0f0' | | RGB | 'rgb(255, 0, 0)', 'rgb(100, 200, 50)' | | ChalkInstance | chalk.hex('#f0f'), chalk.rgb(255, 0, 255) |

backgroundColor accepts the same formats — named colors like 'blue' are automatically prefixed to bgBlue. You can also pass chalk.bgRed, chalk.bgHex(...), etc.

Mode

Controls which parts of the notification receive styling:

notify('Only icon is colored', { style: { mode: 'icon-only', color: 'green' } });
notify('Only text is colored', { style: { mode: 'text-only', color: 'magenta', bold: true } });
notify('No ANSI at all', { style: { mode: 'none' } });

Dynamic style via update

const { id } = notify.loading('Working…');
notify.update(id, {
  options: { style: { color: 'blue', bold: true } },
});

Style on progress notifications

const bar = notify.progress({ total: 5 }, { loading: 'Styled progress' });
notify.update(bar.id, {
  options: { style: { color: 'cyan', bold: true } },
});

Style on promise notifications

await notify.promise(fetch('/api/data'), {
  loading: 'Fetching…',
  success: 'Got it!',
  error: 'Failed',
}, {
  style: { color: 'blue', bold: true },
}).catch(() => {});

Style on toast

import { toast } from '@ts-utilities/notify';

toast.success('Styled toast', { style: { color: 'green', bold: true } });
toast('Auto-dismiss with style', { duration: 2000, style: { color: 'magenta' } });

Progress

const bar = notify.progress({ total: 15 });
for (let i = 0; i < 15; i++) {
  await sleep(100);
  bar.advance();
}

Display Toggles

All display options are enabled by default. Disable any of them:

notify.progress({
  total: 100,
  display: { spinner: false, percentage: false, count: false },
}, { loading: 'Downloading' });

Bar Variants

| Variant | Progress | |---------|----------| | bar (default) | ██████████░░░░░░░░░░ 50% | | block | ██████████ 50% | | smooth | █████ 50% | | shade | ▓▓▓▓▓▒░░░░ 50% | | slim | ━━━━━❯╌╌╌╌ 50% | | pill | ▰▰▰▰▰▱▱▱▱▱ 50% | | braille | ⣿⣿⣿⣿⣷⣀⣀⣀⣀⣀ 50% | | line | ━━━━━━━━━━━━━━━━━━━━ 100% | | dot | ●●●●●○○○○○ 50% | | circle | ⬤⬤⬤⬤⬤◉◯◯◯◯ 50% | | diamond | ◆◆◆◆◆◆◆◆◆◇ 90% | | pacman | ──────────ᗧ········· 50% | | none | 80% (400/500) |

notify.progress({ total: 5, variant: 'pacman' }, { loading: 'Chomping' });

circle and pacman animate — the head character alternates every ~0.5s.

Custom Variant

Pass an inline ProgressBarSet with optional head/headAlt for an animated pointer:

// Custom characters
notify.progress({ total: 10, variant: { full: 'x', empty: 'o' } });

// Animated head edge
notify.progress({
  total: 8,
  variant: { full: '─', empty: ' ', head: '◐', headAlt: '◑' },
});

Variant is set-once — notify.update cannot change it.

Unknown Total

const bar = notify.progress({}, { loading: 'Processing' });

bar.advance();        // → 1
bar.advance();        // → 2
bar.set(42);          // → 42

Update

Partial update — only specified fields are merged:

const bar = notify.progress({ total: 100 }, { loading: 'Uploading' });

notify.update(bar.id, {
  progress: { current: 75 },
  message: 'Still uploading…',
});

notify.update(bar.id, {
  type: 'success',
  message: 'Upload completed',
});

// or
// bar.done('Upload completed')

// or 
// const bar = notify.progress({ total: 100 }, { loading: 'Uploading', success: 'Upload Completed' });

Promise

notify.promise() returns a thenable handle that resolves to T directly. Rejects on error — wrap in try/catch.

Await the handle directly

const handle = notify.promise(fetch('/api/data'), {
  loading: 'Fetching…',
  success: 'Fetched!',
  error: 'Request failed',
});

try {
  const data = await handle;
  notify.success(`Got ${data}`);
} catch (err) {
  notify.error('Request failed');
}

Await .result (identical to awaiting the handle)

await handle.result is equivalent to await handle — both resolve to T or reject.

try {
  const data = await handle.result;
  notify.success(`Got ${data}`);
} catch (err) {
  notify.error('Request failed');
}

Callbacks

const handle = notify.promise(Promise.resolve(42), {
  success: (data) => `Got ${data}`,
  error: (err) => `Error: ${err.message}`,
  finally: () => cleanup(),
});

try {
  const data = await handle;
  notify.success(`Got ${data}`);
} catch (err) {
  notify.error('Request failed');
}

Function Labels

Pass an async function directly — the label is derived from the function name.

async function fetchUser() { return 'Alice'; }
try {
  const data = await notify.promise(fetchUser);
} catch {}

PromiseHandle<T> type

interface PromiseHandle<T> extends NotifyHandle, PromiseLike<T> {
  result: Promise<T>;
}

Toast (auto-dismiss)

import { toast } from '@ts-utilities/notify';

toast('Auto-dismisses after 3s');
toast('Custom duration', { duration: 500 });
toast.success('Quick success');

Toast inherits all notify.* methods:

toast.loading('…');
toast.promise(Promise.resolve(42), { loading: '…', success: 'Done' });
toast.dismiss(id);
toast.clear();

Timer

timer is a standalone utility for measuring elapsed time. Works independently of notifications — use it anywhere in your code.

import { timer } from '@ts-utilities/notify';

timer.start('fetch');
const data = await fetch('/api');
console.log(timer.get('fetch'));       // 1.234 (seconds)
console.log(timer.stop('fetch', 'ms')); // 1234, timer removed

timer.start(id)

Starts tracking a named timer. Silently overwrites if the id already exists.

timer.get(id, unit?)

Returns elapsed time since start(). Timer keeps running. Throws if the id doesn't exist.

| unit | granularity | |------|-------------| | 'auto' (default) | smart — 500ms, 2.5s, 2m 30s, 1h 15m 30s | | 'ms' | milliseconds | | 's' | seconds | | 'm' | minutes |

timer.stop(id, unit?)

Returns elapsed time and removes the timer. Same unit options as get().

Combining with notifications

timer.start('api');
const h = notify.loading('api request...', { display: { timer: true } });
await sleep(1500);
h.update({
  type: 'success',
  message: `api done in ${timer.stop('api').toFixed(2)}s`,
});

The notification shows a live ticking timer. timer.stop() gives the exact precision value for the final message.

timer.measure(fn, messages?)

Wraps a function, runs it, and auto-displays the elapsed time when done. Supports sync and async functions.

// Auto-message (uses function name as label)
timer.measure(() => doWork());
// → doWork Completed in 2.0s

// With loading + custom success callback
timer.measure(
  () => heavyComputation(),
  {
    loading: 'Working...',
    success: (elapsed) => `done in ${elapsed.toFixed(2)}s`,
    error: (err) => `error: ${(err as Error).message}`,
  },
);

// Async function
const data = await timer.measure(async () => {
  const res = await fetch('/api');
  return res.json();
});

| message | Default example | Callback signature | |---------|-----------------|--------------------| | loading | (no loading shown) | — | | success | "fnName Completed in 2.5s" | (elapsed: number) => string — elapsed in seconds | | error | "fnName Failed in 1.2s" | (error: unknown) => string |

If loading is omitted, the function runs without a loading notification — only the result is displayed.

Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | id | string | auto | Custom notification id (replaces existing) | | icon | string | — | Custom icon character or emoji | | toast | boolean \| { duration: number } | — | Makes entry auto-dismiss | | keepAlive | boolean | false | Keeps process alive until dismissed | | style | NotifyStyleOptions | — | Color, background, modifier flags |

Types

import type {
  Message,
  InlineSegment,
  NotifyEntry,
  NotifyOptions,
  NotifyStyleOptions,
  ColorMode,
  NotifyColor,
  NotifyType,
  NotifyHandle,
  ProgressOptions,
  ProgressHandle,
  ProgressVariant,
  ProgressVariantLike,
  ProgressBarSet,
  ProgressConfig,
  PromiseMessages,
  PromiseHandle,
  TimerUnit,
} from '@ts-utilities/notify';

Message:

type Message = string | InlineSegment[];

type InlineSegment = string | { text: string; style?: NotifyStyleOptions };

ProgressBarSet:

{
  full: string;
  empty: string;
  head?: string;
  headAlt?: string;
}

API

notify(message, options?)NotifyHandle

Creates a default notification. Returns a handle with id, dismiss(), and update().

notify.{success|error|warning|info|loading}(message, options?)NotifyHandle

notify.progress(config, messages?)ProgressHandle

Creates a persistent animated progress notification. Returns a handle with advance(), set(), done(), fail(), label(), dismiss(), and update().

notify.update(id, update)

Partially updates a notification.

notify.promise(promiseOrFn, messages?, options?)PromiseHandle<T>

Returns a thenable handle — await handle and await handle.result both resolve to T. Rejects on error — wrap in try/catch.

notify.dismiss(id)

notify.clear()

timer.start(id)

timer.get(id, unit?)

timer.stop(id, unit?)

timer.measure(fn, messages?)