CLI spinners for long running async proccesses
npm install livelines
Beautiful CLI spinners for long-running async tasks with nested subtask support. Built with Ink and React.
- šÆ 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
``bashbun
bun add livelines
Quick Start
`typescript
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:`typescript
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:
`typescript
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:`typescript
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
$3
Creates a new LiveLine instance and begins rendering to the terminal.
$3
Creates a new top-level task.
Parameters:
-
name (string) ā The task name/label
- options (TaskOptions) ā Optional configurationReturns:
TaskHandle$3
| 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 |$3
The object returned by
.task() with the following methods:####
.update(message)Update the task's current message.
`typescript
task.update('Processing item 5 of 10...');
`####
.success(message)Mark the task as successfully completed with a final message.
`typescript
task.success('Completed successfully');
`####
.error(message)Mark the task as failed with an error message.
`typescript
task.error('Failed to connect to server');
`####
.task(name, options?)Create a nested subtask.
`typescript
const subtask = task.task('Subtask name...', { color: 'blue' });
`Custom Spinners
Use any spinner from cli-spinners:
`typescript
ll.task('Loading...', { spinner: 'aesthetic' });
ll.task('Downloading...', { spinner: 'arrow3' });
ll.task('Processing...', { spinner: 'bouncingBar' });
`Or define your own:
`typescript
ll.task('Custom spinner...', {
spinner: {
interval: 100,
frames: ['ā ', 'ā ', 'ā ¹', 'ā ø', 'ā ¼', 'ā “', 'ā ¦', 'ā §', 'ā ', 'ā '],
},
});
`Colors
`typescript
ll.task('Info', { color: 'blue' });
ll.task('Warning', { color: 'yellow' });
ll.task('Processing', { color: 'cyan' });
ll.task('Deploy', { color: 'magenta' });
`Full Example
`typescript
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();
``Please see CONTRIBUTING.md for contribution guidelines.
MIT