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

@macfja/ansi

v1.0.0

Published

A lib to handle operation on ANSI text

Readme

@macfja/ansi

A lib to handle operation on ANSI text

Installation

npm install @macfja/ansi
# or
pnpm add --save @macfja/ansi
# or
yarn add --save @macfja/ansi

API

Main package

parse function

Parse a string and return a list of text or ansi sequence.

/**
 * Parse a string into a series of Text and Ansi sequence
 * @param text
 */
declare function parse(text: string): ParsedText;

stringify function

Transform a list of sequence to a ANSI text (reverse of parse)

/**
 * Stringify a series of Text and Ansi sequence into a string
 * @param input
 */
declare function stringify(input: ParsedText): string;

insertAt function

Insert a string inside an ansi text at a visual position.
The inserted string is isolated from the ansi text (ANSI instruction are stop before the inserted text, and restarted after it).

/**
 * Insert a text into an Ansi text at a visible (printable) position
 * @param text The text to insert into
 * @param visiblePosition The visible (printable) position where to insert the text
 * @param value The text to insert
 */
declare function insertAt(text: string | ParsedText, visiblePosition: number, value: string): string;

wrap function

Wrap an ANSI text at a defined size.
Can be a hard wrap or a soft wrap (if it can, it won't break word)

/**
 * Wrap an Ansi text within a defined length
 * @param text The text to wrap
 * @param cols The maximum width of the text
 * @param options
 */
declare function wrap(text: string, cols: number, options?: WrapOptions): string;

type WrapOptions = {
    /**
     * Indicate what to do with white space.
     * - "trim", will remove any white space at start and end of each line
     * - "fill", will remove any white space at the start of line and add space to match the col size
     * - "preserve", will leave line as-is
     */
    whiteSpace: "trim" | "fill" | "preserve";
    /**
     * Indicate how to wrap line.
     * - "word", if possible it won't break any word (soft wrap)
     * - "char", break line at the desired col without considering word (hard wrap)
     */
    break: "word" | "char";
};

stripAnsi function

Remove all ANSI instruction from a text

/**
 * Remove all Ansi Escape Code from a text
 * @param text
 */
declare function stripAnsi(text: string | ParsedText): string;

ansiPosition function

Return the ANSI position from a visual position

/**
 * Return the ANSI position from a visual position
 * @param text The ANSI text to search in
 * @param visiblePosition The visible (printable) position
 */
declare function ansiPosition(text: string | ParsedText, visiblePosition: number): number;

truncate function

Truncate (no wrapping) an ANSI text at a defined size.
Text can be truncate at the start, the end or in the middle

/**
 * Limit the length of an Ansi text by truncating it if needed
 * @param text The text to truncate
 * @param cols The maximum width of the text
 * @param position Where to truncate [default: "end"]
 */
declare function truncate(text: string, cols: number, position?: "start" | "middle" | "end"): string;

Extensions package

You can extend the behavior of this lib with extensions:

Finding an ANSI code

The library come with many standard ANSI escape code:

| Code | Name | |------------|-------------------------------------------| | CUU | Cursor Up | | CUD | Cursor Down | | CUF | Cursor Forward | | CUB | Cursor Back | | CNL | Cursor Next Line | | CPL | Cursor Previous Line | | CHA | Cursor Horizontal Absolute | | ED | Erase in Display | | EL | Erase in Line | | SU | Scroll Up | | SD | Scroll Down | | CUP | Cursor Position | | HVP | Horizontal Vertical Position | | | AUX On | | | AUX Off | | DSR | Device Status Report | | SGR | Select Graphic Rendition | | OSC (link) | Operating System Command (Hypertext Link) |

But there are many more code (standard and non-standard).

To add the capacity to correctly parse them, you can add new ANSI code matcher:

import { registerAnsiMatcher, AnsiMatcher, FE_ESCAPE } from "@macfja/ansi/extension"
import regexpEscape from "regexp.escape";

const noReportFocus = new AnsiMatcher(
    // The Ansi Escape code category 
    FE_ESCAPE.CSI,
    // The regular expression that match the escape code (the match '0' will be used)
    new RegExp(regexpEscape(`${FE_ESCAPE.CSI}?1004l`)),
    // The non dynamic part of the escape code (use to quickly search in text)
    `${FE_ESCAPE.CSI}?1004l`
)
registerAnsiMatcher(noReportFocus)

Optimizing the parser result

Sometimes an ANSI text can be optimized, for example with SGR multiple instruction can be grouped together (like foreground color, background color, etc.). To do those optimisation, a postprecessor can be applied after the parsing of the text:

import { type postprocess, registerPostprocess, recalculatePosition, type ParsedText, AnsiSequence, FE_ESCAPE } from "@macfja/ansi/extension"

const colorPostprocessor: postprocess = (input: ParsedText) => {
    // VGA Color
    // https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit
    const colorMap = {
        0: [0,0,0],
        1: [170,0,0],
        2: [0,170,0],
        3: [170,85,0],
        4: [0,0,170],
        5: [170,0,170],
        6: [0,170,170],
        7: [170,170,170],
    }
    return recalculatePosition(input.map(item => {
        if (
            !(item instanceof AnsiSequence)
            || item.kind !== FE_ESCAPE.CSI
            || !item.sequence.endsWith('m')
            || !item.sequence.startsWith(`${FE_ESCAPE.CSI}38;2;`)
            || !item.sequence.startsWith(`${FE_ESCAPE.CSI}48;2;`)
        ) {
            return item;
        }
        const color = item.sequence.match(/(?<type>[34]8;2;(?<r>\d+);(?<g>\d+);(?<b>\d+)m$/)
        if (color.groups?.type === undefined || color.groups?.r === undefined || color.groups?.g === undefined || color.groups?.b === undefined) {
            return item;
        }
        const colorCode = colorMap.findIndex(codes => codes.join('-') === `${color.groups.r}-${color.groups.g}-${color.groups.b}`)
        if (colorCode === undefined) {
            return item
        }
        return new AnsiSequence(FE_ESCAPE.CSI, `${FE_ESCAPE.CSI}${color.groups.type}${colorCode}m`, item.start)
    }))
}

registerPostprocess(colorPostprocessor)

Sequence cutting helper

When doing some operation of ANSI text (inserting a char, wrapping lines, etc.), we need to close any ANSI code that is still affect the rendering, and reopen everything after. To do so, there are 2 functions (one for closing code, one for reopening them)

declare function registerToClose(fn: toClose): void;
declare function registerToReopen(fn: toReopen): void;
type toClose = (input: ParsedText, offset?: number) => Array<AnsiSequence>;
type toReopen = (input: ParsedText, offset?: number) => Array<AnsiSequence>;

Decorate package

The decorate package come with a function to decorate a text:

declare function encapsulate(input: string, options: EncapsulateOption): string;

The function second parameter is an object describing how to decorate the text:

type EncapsulateOption = {
    /**
     * Set to true if the decorator add char before the first line of the input text
     */
    beforeLine?: boolean;
    /**
     * Set to true if the decorator need add char(s) before each line
     */
    beforeCol?: boolean;
    /**
     * Set to true if the decorator add char after the last line of the input text
     */
    afterLine?: boolean;
    /**
     * Set to true if the decorator need add char(s) after each line
     */
    afterCol?: boolean;
    /**
     * The decorator function will be call multiple times:
     * - With `line` === -1, and `col` from 0 (or -1) to `cols` - 1 (or `cols`) if `beforeLine` is `true`
     * - With `line` === `lines`, and `col` from 0 (or -1) to `cols` - 1 (or `cols`) if `afterLine` is `true`
     * - With `col` === -1, and `line` from 0 (or -1) to `lines` - 1 (or `lines`) if `beforeCol` is `true`
     * - With `col` === `cols`, and `line` from 0 (or -1) to `lines` - 1 (or `lines`) if `afterCol` is `true`
     * @param line
     * @param col
     * @param lines
     * @param cols
     */
    decorator: (line: number, col: number, lines: number, cols: number) => string;
};

Several pre-made EncapsulateOption are available:

  • ASCIIBox: Create a box with +, - and |
  • roundedBox: Create a box with a continuous line with rounded border
  • doubleSquareBox: : Create a box with a continuous double line
  • squareBox: : Create a box with a continuous line
  • curlyBracket: Prefix the text with a big curly bracket
  • padding(): A function to create a padding/margin space around the text
  • boxChar(): A function to create a box

roundedBox

import { encapsulate, squareBox } from "@macfja/ansi/decorate"

console.log(encapsulate(' Lorem ipsum dolor sit amet,  \n consectetur adipiscing elit. ', roundedBox))
╭──────────────────────────────╮
│ Lorem ipsum dolor sit amet,  │
│ consectetur adipiscing elit. │
╰──────────────────────────────╯

curlyBracket

import { encapsulate, curlyBracket } from "@macfja/ansi/decorate"

console.log(encapsulate('Lorem ipsum\ndolor sit\namet,\nconsectetur\nadipiscing elit.', curlyBracket))
⎧ Lorem ipsum
⎪ dolor sit
⎨ amet,
⎪ consectetur
⎩ adipiscing elit.