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 defaultRunning 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.ps1README 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 readmeThis will:
- Read or create a
README.mdfile - 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 --multiThis 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=2Example Workflow
- Create a README template with the special tags:
# My Awesome CLI
<!-- usage -->
<!-- usagestop -->
<!-- commands -->
<!-- commandsstop -->
<!-- toc -->
<!-- tocstop -->- Add the readme command to your CLI:
import readmeCommand from "@visulima/cerebro/command/readme-generator";
cli.addCommand(readmeCommand);- Generate the README:
cli readme- 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=docsThis creates:
docs/Build.md- Commands in the "Build" groupdocs/Deploy.md- Commands in the "Deploy" group- And updates
README.mdwith 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_VERBOSEAvailable levels:
VERBOSITY_QUIET(16) - Minimal outputVERBOSITY_NORMAL(32) - Normal outputVERBOSITY_VERBOSE(64) - Verbose outputVERBOSITY_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
- Lazy load plugins - Only load plugins when needed
- Use command groups - Organize commands for faster lookups
- Cache expensive operations - Cache results in plugin
init - 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
- Use command composition - Break complex operations into smaller commands
- Handle errors gracefully - Use error hooks and provide helpful messages
- Document commands - Always include descriptions and examples
- Type everything - Use TypeScript for better DX
- Test your CLI - Write tests for your commands
- Follow CLI conventions - Use standard option names and patterns