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

askeroo

v0.0.0-alpha.16

Published

A modern CLI prompt library with flow control, history navigation, and conditional prompts

Readme

Askeroo

A modern CLI prompt library with flow control, back navigation, and conditional fields.

[!WARNING] This library is still in alpha and things might change before release.

Features

  • Works great out of the box, with key prompts, like text, radio, multi
  • Create your own bespoke prompts using a highly flexible framework
  • Runs prompts in a flow which you can configure and customise to suit your needs
  • Stateful navigation that preserves user's inputs and supports back navigation
  • Return structured data your way with imperative-style functions
  • Write dynamic branching with groups and conditionals
  • Run tasks with progress tracking, parallel or sequential execution, and error handling
  • Display notes with support for markdown and chalk syntax
  • Cancel event listeners for cleanup when users exit with Ctrl+C

Installation

npm i askeroo

Quick Start

Askeroo comes with a default runtime and set of prompts that you can use out of the box.

import { ask, group, text, confirm, note } from "askeroo";

const flow = async () => {
    // Display notes
    await note("[Hello world!]{bgBlue}");

    // Call prompts on their own
    const nickname = await text({ label: "Nickname" });

    // Group prompts together
    const profile = await group(async () => {
        const first = await text({ label: "First name" });
        const last = await text({ label: "Last name" });
        return { first, last };
    });

    // Create conditional inputs
    const prefs = await group(
        async () => {
            const role = await text({ label: "Role (user/admin)" });
            if (role === "admin") {
                const code = await text({ label: "Access code" });
                return { role, code };
            }
            const news = await confirm({ label: "Subscribe to newsletter?" });
            return { role, news };
        },
        { label: "Preferences" } // Optional group labels
    );

    // Return structured data your way
    return { nickname, profile, prefs };
};

const result = await ask(flow);
console.log(result);

How does Askeroo work?

Askeroo is built around the concept of a flow, where each prompt is executed in sequence. Some prompts can auto-submit, while others wait for user input, and each prompt can appear in different states—before activation, while active, and after completion. Prompts are also dynamic: they can be updated or changed at any point within your flow function.

When you call ask(flow), Askeroo sets up a runtime that runs your flow function multiple times using a replay mechanism. This allows for a smooth and interactive experience:

  1. First run: The flow executes, prompts are shown one by one, and answers are stored.
  2. User navigates back: The runtime replays the flow, automatically filling in previous answers.
  3. User changes an answer: The runtime clears any answers that came after the changed prompt and continues from there.
  4. Repeat: This process continues until the user completes the flow.

From the user's perspective, this feels like a seamless way to move back and forth between prompts. Behind the scenes, Askeroo is replaying your flow function as needed to determine which prompts should be shown at each step.

// Your flow function runs multiple times
const flow = async () => {
    const role = await radio({
        label: "Select your role",
        options: ["Developer", "Designer"],
    });

    if (role === "Developer") {
        const language = await radio({
            label: "Preferred language",
            options: ["TypeScript", "JavaScript"],
        });
        return { role, language };
    }

    const tool = await radio({
        label: "Design tool",
        options: ["Figma", "Sketch"],
    });
    return { role, tool };
};

Flow:

Select your role: Developer
   ↓
Preferred language: TypeScript
   ↓
User presses back ⬆
   ↓
Select your role: Designer
   ↓
Design tool: Figma
   ↓
Complete

The flow replays automatically when the user navigates back, clearing subsequent answers and showing the appropriate prompts based on the new selection.

Running Flows

  • Run prompt flows

    ask(flow: FlowFunction, options: FlowOpts): Promise<T>

    Executes a prompt flow with replay and navigation support.

    const result = await ask(async () => {
        const name = await text({
            message: "Name",
        });
        return { name };
    });

    Options

    interface FlowOpts {
        allowBack?: boolean;
    }

    Flow API

    The flow function receives an API object with the following properties:

    • onCancel(callback: () => void): Register a callback to be called when the flow is cancelled (e.g., via Ctrl+C)
    const result = await ask(async ({ text, confirm, onCancel }) => {
        // Register cleanup callback
        onCancel(() => {
            console.log("Flow cancelled! Cleaning up...");
            // Close connections, remove temp files, etc.
        });
    
        const name = await text({ label: "Name" });
        const confirmed = await confirm({ label: "Confirm?" });
        return { name, confirmed };
    });

    Cancellation Behavior:

    • First Ctrl+C: Triggers onCancel callbacks for graceful cleanup. A hint message "(Press Ctrl+C again to force quit)" is displayed.
    • Second Ctrl+C: Forces immediate exit with process.exit(1), bypassing any cleanup. Useful if cleanup is stuck or taking too long.

    Multiple cancel callbacks can be registered, and they will all be called when the flow is cancelled:

    await ask(async ({ onCancel }) => {
        // Create temp file
        const tempFile = "temp.json";
        fs.writeFileSync(tempFile, "{}");
    
        // Register multiple cleanup callbacks
        onCancel(() => {
            console.log("Removing temp file...");
            fs.unlinkSync(tempFile);
        });
    
        onCancel(() => {
            console.log("Closing database connection...");
            db.close();
        });
    
        // ... rest of flow
    });
  • Group prompts

    group(flow: () => Promise<T>, options: GroupOpts): Promise<T>

    Group prompts visually and control their behaviour together.

Prompts

  • text(options: TextOpts)

    Show a text input.

  • confirm(options: ConfirmOpts)

    Show a confirmation with choice of yes or no.

  • radio(options: RadioOpts)

    Show a single-choice selection from multiple options.

  • multi(options: MultiOpts)

    Show a multi-choice selection allowing multiple options.

  • note(MarkdownString)

    Show a note using markdown.

  • component(options: ComponentOpts)

    Render a React component using Ink.

  • tasks(taskList: Task[], options?: TasksOpts)

    Execute a list of tasks with progress indication and error handling.

Create Custom Prompts

Askeroo uses automatic plugin registration - when you create a prompt with createPrompt, it registers itself when imported. This means:

  • ✅ No manual registration needed
  • ✅ Only bundle the prompts you actually use
  • ✅ Custom prompts work exactly like built-in ones
import React, { useState } from "react";
import { Text, useInput } from "ink";
import { createPrompt } from "askeroo";

// Define your options interface
export interface CustomOptions {
    label: string;
    placeholder?: string;
}

// Create and export the plugin - it auto-registers when imported!
export const customField = createPrompt<CustomOptions, string>({
    type: "custom-field",
    component: ({ node, options, events }: any) {
        const [value, setValue] = useState("");

        useInput((input, key) => {
            if (key.return) {
                events.onSubmit?.(value);
            } else if (input) {
                setValue((prev) => prev + input);
            }
        });

        // Handle different states
        if (node.state === "completed") {
            return (
                <Text>
                    {options.label}: <Text color="blue">{node.completedValue}</Text>
                </Text>
            );
        }

        if (node.state === "disabled") {
            return <Text dimColor>{options.label}: ...</Text>;
        }

        return (
            <Text>
                {options.label}: {value}
            </Text>
        );
    }
});

Usage

import { ask } from "askeroo";
import { customField } from "./custom-field.js"; // Auto-registers when imported!

const result = await ask(async () => {
    const input = await customField({
        label: "Enter something",
    });
    return { input };
});

Examples

Run the included example:

npm run example

Try npm run build && npm run example test-run

Development

npm install
npm run build
npm run dev          # Watch mode

License

MIT