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

@nan0web/ui-cli

v3.1.1

Published

NaN•Web UI CLI. Command line interface for One application logic (algorithm) and many UI.

Readme

@nan0web/ui-cli

A modern, interactive UI input adapter for Node.js projects. Powered by the prompts engine, it provides a premium "Lux-level" terminal experience.

Description

The @nan0web/ui-cli package transforms basic CLI interactions into stunning, interactive experiences using the "One Logic, Many UI" philosophy.

Key Features:

  • Universal Runner — Start your CLI app in 1 line of code with bootstrapApp.
  • Interactive Prompts — Sleek selection lists, masked inputs, and searchable autocomplete.
  • Aesthetic Standards — Pixel-perfect 5-character gutter ({} |) for all components.
  • Schema-Driven Forms — Generate complex CLI forms directly from your data models.
  • Build Optimization — Blazing fast monorepo type-checking with isolated package depth.
  • One Logic, Many UI — Use the same shared logic across Web and Terminal.

Installation

How to install the package?

npm install @nan0web/ui-cli

Universal CLI Runner

The bootstrapApp is the modern way to bootstrap CLI applications. It handles model-to-argv parsing, i18n initialization, and lifecycle management.

Security: The seal() Protocol

To ensure system integrity, bootstrapApp automatically locks the database using db.seal(). This prevents any runtime modifications to the DB structure or mounts after initialization. Requirement: Requires a modern @nan0web/db version supporting the seal protocol.

Model-as-App (Recommended)

The ModelAsApp class provides a unified architecture for both Domain Logic and UI Presentation. It automatically handles CLI help generation, subcommand routing, and i18n variables.

How to bootstrap a CLI application?

import { bootstrapApp, ModelAsApp, show } from '@nan0web/ui-cli'
class StatusApp extends ModelAsApp {
	static UI = { title: 'Status', fine: 'Everything is fine' }
	static debug = { type: 'boolean', help: 'Debug mode', default: false }
	async *run() {
		yield show(StatusApp.UI.fine)
	}
}
class RootApp extends ModelAsApp {
	static command = { positional: true, type: [StatusApp] }
}
await bootstrapApp(RootApp)

Headless Execution & Built-in Apps

You can execute an OLMUI Model programmatically without any interactive UI adapter by calling ModelAsApp.execute(). This is perfect for automation scripts like the ReadmeMd documentation generator.

Additionally, standard tools are natively aliased in nan0cli:

How to run internal apps like ReadmeMd?

/* Programmatic Headless Execution:
import { ReadmeMd } from '@nan0web/ui-cli/domain/ReadmeMd.js'
await ReadmeMd.execute({ data: 'docs' })
*/
/* Or via Terminal CLI Alias:
nan0cli docs --data=docs
*/

Usage (V2 Architecture)

Starting from v2.0, we recommend using the ask() function with Composable Components.

Interactive Prompts

Input & Password

How to use Input and Password components?

import { ask, Input, Password } from '@nan0web/ui-cli'
const user = 'Alice'
console.info(`User: ${user}`)

Select & Multiselect

How to use Select component?

import { ask, Select } from '@nan0web/ui-cli'
const lang = { value: 'en' }
console.info(`Selected: ${lang.value}`)

Multiselect

How to use Multiselect component?

import { ask, Multiselect } from '@nan0web/ui-cli'
const roles = ['admin', 'user']
console.info(`Roles: ${roles.join(', ')}`)

Masked Input

How to use Mask component?

import { ask, Mask } from '@nan0web/ui-cli'
const phone = '123-456'
console.info(`Phone: ${phone}`)

Autocomplete

How to use Autocomplete component?

import { ask, Autocomplete } from '@nan0web/ui-cli'
const model = 'gpt-4'
console.info(`Model: ${model}`)

Slider, Toggle & DateTime

How to use Slider and Toggle?

import { ask, Slider, Toggle } from '@nan0web/ui-cli'
const volume = 50
console.info(`Volume: ${volume}`)
const active = true
console.info(`Active: ${active}`)

Tree Selection

Hierarchical data selection made easy.

How to use Tree component?

import { ask, Tree } from '@nan0web/ui-cli'
const selected = '/src/index.js'
console.info(`Selected file: ${selected}`)

Sortable Lists

Drag and drop items in the terminal.

How to use Sortable component?

import { ask, Sortable } from '@nan0web/ui-cli'
const items = ['First', 'Second', 'Third']
console.info(`Order: ${items.join(' > ')}`)

Advanced Interaction

Models & Forms

You can pass a Model class to ask() to automatically generate and process an interactive form.

How to use ask with Models?

import { ask } from '@nan0web/ui-cli'
class UserProfile {
	static username = { help: 'Enter username', required: true }
	static email = { help: 'Enter email', hint: 'email' }
}
// const profile = await ask(UserProfile)

AI Agents

You can request an AI agent task using the agent intent. In CLI, this shows a status message and can wrap an async action in a spinner.

How to use ask with Agents?

import { ask } from '@nan0web/ui-cli'
import { agent } from '@nan0web/ui'
// 1. Simple task
// await ask({ type: 'agent', task: 'Review the code' })
// 2. Task with action (shows spinner)
// await ask({ type: 'agent', task: 'Analyzing...', action: async () => 'Success' })

Static Views

Alerts

How to render Alerts?

import { ask, Alert } from '@nan0web/ui-cli'
const out = await ask(Alert({ variant: 'success', children: 'Operation completed' }))

Dynamic Tables

How to render Tables?

import { ask, Table } from '@nan0web/ui-cli'
const data = [{ id: 1, name: 'Alice' }]
const out = await ask(Table({ data, interactive: false }))

Feedback & Progress (OLMUI)

Following the "One Logic, Many UI" (OLMUI) philosophy, business logic should never directly import CLI-specific components. Instead, use the platform-agnostic progress helper yielded from your generator models.

To enforce the Strict Model-First i18n architecture, the progress message parameter MUST be a reference to a static UI dictionary property of your model, rather than a hardcoded literal string.

The progress(message, value, optionsOrTotalOrId, id) helper supports multiple syntax variations:

1. Short Positional Syntax (Determinate Progress)

Pass the message (referencing model UI), value, total steps, and optional tracking id as direct positional parameters:

How to use Spinner?

import { progress } from '@nan0web/ui'

// class SyncApp extends ModelAsApp {
//     static UI = {
//         syncing: 'Syncing files...',
//         done: 'Synchronization complete!'
//     }
//     async *run() {
//         // Start & Update (pos: message reference, value, total, id)
//         yield progress(SyncApp.UI.syncing, 50, 100, 'sync-loader')

//         // Complete (stop with success status)
//         yield progress(SyncApp.UI.done, 100, { id: 'sync-loader', stop: 'success' })
//     }
// }
const action = Promise.resolve('Done')
const result = await ask(Spinner({ UI: 'Loading...', action }))

2. Short Indeterminate Spinner

Omit the total steps or pass 0 to render an indeterminate pulsing spinner (e.g. for unknown API delays), using the model's static UI reference:

class FetchApp extends ModelAsApp {
    static UI = {
        loading: 'Connecting to API...',
        connected: 'Connected!'
    }
    async *run() {
        // Start spinner (pos: message, value, id)
        yield progress(FetchApp.UI.loading, 0, 'api-spinner')

        // Stop spinner
        yield progress(FetchApp.UI.connected, 100, { id: 'api-spinner', stop: 'success' })
    }
}

3. Options Object Syntax (Determinate / Customized)

Pass an options object as the third argument to unlock fine-grained control:

How to use ProgressBar?

import { progress } from '@nan0web/ui'

// class ExportApp extends ModelAsApp {
//     static UI = {
//         exporting: 'Exporting database...',
//         failed: 'Export failed!'
//     }
//     async *run() {
//         // Start & Customize (total, id, width, fps, format, forceOneLine)
//         yield progress(ExportApp.UI.exporting, 25, {
//             total: 100,
//             id: 'export-bar',
//             width: 20,            // character width of visual bar
//             fps: 15,             // limit updates rate for smooth terminal rendering
//             format: '{time} {bar} {percent} {title}'
//         })

//         // Stop with error status
//         yield progress(ExportApp.UI.failed, 25, { id: 'export-bar', stop: 'error' })
//     }
// }
const p = await ask(ProgressBar({ UI: 'Downloading...', total: 100 }))
p.update(100)
p.success('Done')

One Logic, Many UI (Generators)

OLMUI Applications are built using Async Generators. This allows the business logic to remain UI-agnostic by yield-ing Intents.

Rendering Components via yield

You can render any UI component (Alert, Table, etc.) from within your generator by yielding a render intent.

How to render components in a generator?

import { render } from '@nan0web/ui'
async function* myGenerator() {
	// Option 1: Standard Intent
	yield render('Alert', { children: 'Hello from Intent' })
	// Option 2: Using render() helper (Standard in @nan0web/ui)
	yield render('Alert', { children: 'Hello from Helper' })
	// Option 3: Table with data
	yield render('Table', { data: [{ id: 1, name: 'Alice' }], interactive: false })
}
const gen = myGenerator()
const alert1 = await gen.next()

Sub-path Exports (OLMUI)

The package uses "One Logic, Many UI" (OLMUI) architecture, exposing only strict architectural boundaries.

  • import { ModelAsApp } from '@nan0web/ui-cli/domain' — Domain Base classes.
  • import { App } from '@nan0web/ui-cli/app' — Main Application Model & Router.
  • import { playground } from '@nan0web/ui-cli/test' — Testing & Snapshot utilities.

How to use isolated domain models and UI adapters?

Legacy API

CLiInputAdapter

How to request form input via CLiInputAdapter?

import { CLiInputAdapter } from '@nan0web/ui-cli'

Playground

How to run the playground?

npm run play

License

How to check the license? - ISC LICENSE file.