CerebroGuidesAdvanced Features

Advanced Features

Learn about advanced Cerebro features for building complex CLIs.

Last updated:

Learn about advanced Cerebro features for building complex CLIs.

Command Composition

Call commands from within other commands using runtime.runCommand():

cli.addCommand({
    name: "deploy",
    execute: async ({ runtime, logger }) => {
        logger.info("Building...");
        await runtime.runCommand("build", {
            argv: ["--production"],
        });

        logger.info("Testing...");
        await runtime.runCommand("test", {
            argv: ["--coverage"],
        });

        logger.info("Deploying...");
    },
});

Passing Arguments

Pass arguments to called commands:

await runtime.runCommand("copy", {
    argv: ["file1.txt", "file2.txt"],
});

Passing Options

Pass options to called commands:

await runtime.runCommand("build", {
    argv: ["--production", "--output", "dist"],
});

Merging Options

Merge additional options:

await runtime.runCommand("build", {
    argv: ["--production"],
    customOption: "value",
});

Getting Return Values

Commands can return values:

cli.addCommand({
    name: "calculate",
    execute: () => {
        return 42;
    },
});

cli.addCommand({
    name: "use-calculation",
    execute: async ({ runtime }) => {
        const result = await runtime.runCommand("calculate");
        console.log(`Result: ${result}`); // 42
    },
});

Custom CLI Configuration

Configure your CLI instance:

const cli = new Cerebro("my-app", {
    packageName: "my-app",
    packageVersion: "1.0.0",
    cwd: "/custom/path",
    logger: customLogger,
    argv: process.argv.slice(2), // Custom argv
});

Custom Logger

Provide a custom logger:

const customLogger = {
    info: (...args) => console.log("[INFO]", ...args),
    error: (...args) => console.error("[ERROR]", ...args),
    warn: (...args) => console.warn("[WARN]", ...args),
    debug: (...args) => console.debug("[DEBUG]", ...args),
};

const cli = new Cerebro("my-app", {
    logger: customLogger,
});

Command Sections

Customize help output:

cli.setCommandSection({
    header: "My Awesome CLI v1.0.0",
    footer: "For more info, visit https://example.com",
});

Default Command

Set a default command when none is provided:

cli.setDefaultCommand("help"); // Shows help by default
cli.setDefaultCommand("start"); // Runs start command by default

Running Without Exiting

For testing or programmatic use:

await cli.run({
    shouldExitProcess: false, // Don't exit process
    autoDispose: false, // Don't cleanup automatically
});

Error Handling

Custom Error Handling

Use plugins for custom error handling:

cli.addPlugin({
    name: "error-handler",
    onError: async (error, toolbox) => {
        // Custom error formatting
        console.error(`❌ Error in ${toolbox.commandName}:`);
        console.error(error.message);

        // Log to file
        await logErrorToFile(error, toolbox);

        // Send to monitoring
        await sendToMonitoring(error);
    },
});

Error Types

Cerebro provides specific error types:

import { CommandNotFoundError, CommandValidationError } from "@visulima/cerebro";

// Handle specific errors
try {
    await cli.run();
} catch (error) {
    if (error instanceof CommandNotFoundError) {
        console.error(`Command not found: ${error.command}`);
        console.error(`Did you mean: ${error.alternatives.join(", ")}?`);
    } else if (error instanceof CommandValidationError) {
        console.error(`Validation error: ${error.message}`);
    }
}

Shell Completions

Enable shell autocompletions for your CLI.

Installation

Install the required dependency:

bash pnpm add @bomb.sh/tab
bash npm install @bomb.sh/tab
bash yarn add @bomb.sh/tab

Setup

Add the completion command to your CLI:

import completionCommand from "@visulima/cerebro/command/completion";

cli.addCommand(completionCommand);

Then generate completions:

cli completion --shell=zsh > ~/.zshrc
cli completion --shell=bash > ~/.bashrc
cli completion --shell=fish > ~/.config/fish/completions/cli.fish
cli completion --shell=powershell > cli.ps1

README Generation

Generate comprehensive README documentation for your CLI commands automatically.

Installation

First, install the required dependency:

bash pnpm add github-slugger
bash npm install github-slugger
bash yarn add github-slugger

Setup

Add the readme command to your CLI:

import readmeCommand from "@visulima/cerebro/command/readme-generator";

cli.addCommand(readmeCommand);

Basic Usage

Generate a README with all your commands:

cli readme

This will:

  • Read or create a README.md file
  • Generate usage examples
  • List all commands with descriptions
  • Create a table of contents
  • Replace content between special tags: <!-- usage -->, <!-- commands -->, <!-- toc -->

README Template

The command uses special HTML comment tags to mark sections:

# My CLI

<!-- usage -->
<!-- usagestop -->

<!-- commands -->
<!-- commandsstop -->

<!-- toc -->
<!-- tocstop -->

The readme command will replace content between these tags.

Options

--readme-path

Specify a custom README file path:

cli readme --readme-path=DOCS.md

--output-dir

Set output directory for multi-file mode (default: docs):

cli readme --multi --output-dir=documentation

--multi

Generate multi-file documentation grouped by command groups:

cli readme --multi

This creates separate markdown files for each command group in the output directory.

--aliases

Include command aliases in the command list:

cli readme --aliases

--dry-run

Preview what would be generated without writing files:

cli readme --dry-run

--version

Specify a custom version for the generated documentation:

cli readme --version=2.0.0

--nested-topics-depth

Control maximum depth for nested topics in multi-file mode:

cli readme --multi --nested-topics-depth=2

Example Workflow

  1. Create a README template with the special tags:
# My Awesome CLI

<!-- usage -->
<!-- usagestop -->

<!-- commands -->
<!-- commandsstop -->

<!-- toc -->
<!-- tocstop -->
  1. Add the readme command to your CLI:
import readmeCommand from "@visulima/cerebro/command/readme-generator";

cli.addCommand(readmeCommand);
  1. Generate the README:
cli readme
  1. The README will be updated with:
    • Installation and usage instructions
    • Complete command list with descriptions
    • Command documentation with options and examples
    • Table of contents

Multi-File Documentation

For larger CLIs, use multi-file mode to organize documentation:

cli readme --multi --output-dir=docs

This creates:

  • docs/Build.md - Commands in the "Build" group
  • docs/Deploy.md - Commands in the "Deploy" group
  • And updates README.md with links to these files

Cross-Platform Line Endings

The readme command automatically handles different line ending formats (LF, CRLF, CR) and normalizes them to LF for consistent output across platforms.

Verbosity Levels

Control output verbosity:

// In your CLI options
{
  name: "verbose",
  type: Boolean,
  description: "Verbose output"
}

// Or use environment variable
process.env.CEREBRO_OUTPUT_LEVEL = "64"; // VERBOSITY_VERBOSE

Available levels:

  • VERBOSITY_QUIET (16) - Minimal output
  • VERBOSITY_NORMAL (32) - Normal output
  • VERBOSITY_VERBOSE (64) - Verbose output
  • VERBOSITY_DEBUG (128) - Debug output

Command Groups

Organize commands:

cli.addCommand({
    name: "build",
    group: "Development",
    execute: () => {},
});

cli.addCommand({
    name: "test",
    group: "Development",
    execute: () => {},
});

cli.addCommand({
    name: "deploy",
    group: "Production",
    execute: () => {},
});

Help output will group commands accordingly.

Multiple Commands

Register multiple commands efficiently:

const commands = [
    {
        name: "build",
        execute: () => {},
    },
    {
        name: "test",
        execute: () => {},
    },
    {
        name: "deploy",
        execute: () => {},
    },
];

commands.forEach((cmd) => cli.addCommand(cmd));

TypeScript Best Practices

Extending Toolbox Type

declare global {
    namespace Cerebro {
        interface ExtensionOverrides {
            myFeature: () => void;
            api: {
                get: (url: string) => Promise<unknown>;
            };
        }
    }
}

Typed Commands

interface BuildOptions {
    production: boolean;
    output: string;
}

cli.addCommand({
    name: "build",
    options: [
        { name: "production", type: Boolean },
        { name: "output", type: String },
    ],
    execute: ({ options }: { options: BuildOptions }) => {
        // TypeScript knows the options structure
    },
});

Performance Tips

  1. Lazy load plugins - Only load plugins when needed
  2. Use command groups - Organize commands for faster lookups
  3. Cache expensive operations - Cache results in plugin init
  4. Minimize toolbox extensions - Only add what's needed

Testing

Test your CLI commands:

import { describe, it, expect } from "vitest";
import { Cerebro } from "@visulima/cerebro";

describe("CLI", () => {
    it("should execute command", async () => {
        const cli = new Cerebro("test-cli", {
            argv: ["hello", "World"],
        });

        const execute = vi.fn();
        cli.addCommand({
            name: "hello",
            execute,
        });

        await cli.run({ shouldExitProcess: false });

        expect(execute).toHaveBeenCalled();
    });
});

Best Practices

  1. Use command composition - Break complex operations into smaller commands
  2. Handle errors gracefully - Use error hooks and provide helpful messages
  3. Document commands - Always include descriptions and examples
  4. Type everything - Use TypeScript for better DX
  5. Test your CLI - Write tests for your commands
  6. Follow CLI conventions - Use standard option names and patterns
Support

Contribute to our work and keep us going

Community is the heart of open source. The success of our packages wouldn't be possible without the incredible contributions of users, testers, and developers who collaborate with us every day.Want to get involved? Here are some tips on how you can make a meaningful impact on our open source projects.

Ready to help us out?

Be sure to check out the package's contribution guidelines first. They'll walk you through the process on how to properly submit an issue or pull request to our repositories.

Submit a pull request

Found something to improve? Fork the repo, make your changes, and open a PR. We review every contribution and provide feedback to help you get merged.

Good first issues

Simple issues suited for people new to open source development, and often a good place to start working on a package.
View good first issues