livelines
v0.2.0
Published
CLI spinners for long running async proccesses
Readme
livelines
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 livelinesQuick 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 completedUpdating 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 oneAPI 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/labeloptions(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
