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

livelines

v0.2.0

Published

CLI spinners for long running async proccesses

Readme

livelines

asciicast

Beautiful CLI spinners for long-running async tasks with nested subtask support. Built with Ink and React.

Features

  • 🎯 Nested Tasks — Create hierarchical task trees with unlimited depth
  • ⏱️ Elapsed Time — Automatic timing for each task with live updates
  • 🎨 Customizable Colors — Style each task with its own color
  • 🔄 60+ Spinners — Choose from all cli-spinners or define your own
  • 📜 Message History — Show recent log lines for tasks with frequent updates
  • Status Indicators — Visual success (✔) and error (✖) states

Installation

# bun
bun add livelines

# npm
npm install livelines

# pnpm
pnpm add livelines

# yarn
yarn add livelines

Quick Start

import LiveLine from 'livelines';

const ll = new LiveLine();

async function main() {
	const task = ll.task('Installing dependencies...');

	await install();

	task.success('143 packages installed');
}

main();

Nested Tasks

Create subtasks by calling .task() on any task handle:

import LiveLine from 'livelines';

const ll = new LiveLine();

async function build() {
	const build = ll.task('Building project...', { color: 'cyan' });

	const compile = build.task('Compiling TypeScript...');
	await compileTS();
	compile.success('42 files compiled');

	const bundle = build.task('Bundling for production...');
	await bundleCode();
	bundle.success('Bundle: 248kb');

	build.success('Build completed');
}

Output:

✔ Building project... [3s]
    ✔ Compiling TypeScript... [1s]
        42 files compiled
    ✔ Bundling for production... [2s]
        Bundle: 248kb
    Build completed

Updating Task Messages

Update a task's message while it's running:

const upload = ll.task('Uploading files...');

for (const file of files) {
	upload.update(`Uploading ${file}...`);
	await uploadFile(file);
}

upload.success(`${files.length} files uploaded`);

Message History

Show recent messages for tasks with frequent updates using logLines:

const deploy = ll.task('Deploying...', { logLines: 3 });

deploy.update('Uploading index.html...');
deploy.update('Uploading app.js...');
deploy.update('Uploading styles.css...');
deploy.update('Uploading assets...');

// Shows the last 3 messages above the current one

API Reference

new LiveLine()

Creates a new LiveLine instance and begins rendering to the terminal.

liveline.task(name, options?)

Creates a new top-level task.

Parameters:

  • name (string) — The task name/label
  • options (TaskOptions) — Optional configuration

Returns: TaskHandle

TaskOptions

| Option | Type | Default | Description | | ------------- | ------------------------------ | --------- | ------------------------------------------------------------------------------------------------ | | spinner | SpinnerName \| SpinnerObject | 'dots' | Spinner style from cli-spinners or custom object | | color | string | 'green' | Task name color (any Ink/Chalk color) | | logLines | number | 0 | Number of previous messages to display | | showElapsed | boolean | true | Show elapsed time |

TaskHandle

The object returned by .task() with the following methods:

.update(message)

Update the task's current message.

task.update('Processing item 5 of 10...');

.success(message)

Mark the task as successfully completed with a final message.

task.success('Completed successfully');

.error(message)

Mark the task as failed with an error message.

task.error('Failed to connect to server');

.task(name, options?)

Create a nested subtask.

const subtask = task.task('Subtask name...', { color: 'blue' });

Custom Spinners

Use any spinner from cli-spinners:

ll.task('Loading...', { spinner: 'aesthetic' });
ll.task('Downloading...', { spinner: 'arrow3' });
ll.task('Processing...', { spinner: 'bouncingBar' });

Or define your own:

ll.task('Custom spinner...', {
	spinner: {
		interval: 100,
		frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'],
	},
});

Colors

Use any color supported by Ink / Chalk:

ll.task('Info', { color: 'blue' });
ll.task('Warning', { color: 'yellow' });
ll.task('Processing', { color: 'cyan' });
ll.task('Deploy', { color: 'magenta' });

Full Example

import LiveLine from 'livelines';

const ll = new LiveLine();

async function main() {
	// Build phase
	const build = ll.task('Building project...', { color: 'cyan' });

	const install = build.task('Installing dependencies...', { color: 'blue' });
	await sleep(1200);
	install.success('143 packages installed');

	const compile = build.task('Compiling source...');
	const typescript = compile.task('TypeScript files...');
	await sleep(800);
	typescript.success('42 files compiled');

	const styles = compile.task('Stylesheets...');
	await sleep(400);
	styles.success('12 files processed');
	compile.success('Compilation complete');

	build.success('Build completed in 3.7s');

	// Deploy phase
	const deploy = ll.task('Deploying to production...', { color: 'magenta' });

	const upload = deploy.task('Uploading files...', { logLines: 3 });
	for (const file of ['index.html', 'app.js', 'app.css']) {
		upload.update(`Uploading ${file}...`);
		await sleep(300);
	}
	upload.success('3 files uploaded');

	deploy.success('Deployed to https://app.example.com');
}

function sleep(ms: number) {
	return new Promise((resolve) => setTimeout(resolve, ms));
}

main();

Contributing

Please see CONTRIBUTING.md for contribution guidelines.

License

MIT