CerebroGuidesPlugins

Plugins

Last updated:

Plugins

Plugins extend Cerebro's functionality by hooking into the command lifecycle and extending the toolbox.

What are Plugins?

Plugins allow you to:

  • Add functionality to the toolbox (like file system operations, HTTP clients, etc.)
  • Hook into command lifecycle (before/after execution)
  • Handle errors globally
  • Share code across commands

Basic Plugin

A plugin requires a name and can have lifecycle hooks:

cli.addPlugin({
    name: "my-plugin",
    description: "My custom plugin",
    execute: (toolbox) => {
        // Extend toolbox
        toolbox.myFeature = () => {
            console.log("My feature!");
        };
    },
});

Plugin Lifecycle

Plugins have access to several lifecycle hooks:

Init Hook

Called once during plugin initialization, before any commands run:

cli.addPlugin({
    name: "database",
    init: async ({ cli, cwd, logger }) => {
        // Initialize database connection
        logger.info("Database plugin initialized");
    },
});

Execute Hook

Called during command execution to extend the toolbox:

cli.addPlugin({
    name: "filesystem",
    execute: (toolbox) => {
        // Add file system utilities to toolbox
        toolbox.fs = {
            readFile: async (path: string) => {
                // Implementation
            },
            writeFile: async (path: string, content: string) => {
                // Implementation
            },
        };
    },
});

Then use in commands:

cli.addCommand({
    name: "read",
    execute: async ({ fs }) => {
        const content = await fs.readFile("file.txt");
        console.log(content);
    },
});

BeforeCommand Hook

Called before each command executes:

cli.addPlugin({
    name: "analytics",
    beforeCommand: async (toolbox) => {
        console.log(`Executing command: ${toolbox.commandName}`);
        // Track command usage
    },
});

AfterCommand Hook

Called after successful command execution:

cli.addPlugin({
    name: "cleanup",
    afterCommand: async (toolbox, result) => {
        // Cleanup resources
        console.log("Command completed:", result);
    },
});

OnError Hook

Called when an error occurs:

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

        // Send to error tracking service
        await sendToErrorTracking(error, toolbox);
    },
});

Plugin Dependencies

Plugins can depend on other plugins:

cli.addPlugin({
    name: "advanced-fs",
    dependencies: ["filesystem"], // Requires filesystem plugin
    execute: (toolbox) => {
        // Extend filesystem plugin
        toolbox.fs.copy = async (src, dest) => {
            // Implementation
        };
    },
});

Dependencies are loaded in the correct order automatically.

Complete Plugin Example

cli.addPlugin({
    name: "http-client",
    version: "1.0.0",
    description: "HTTP client utilities",
    dependencies: ["logger"],

    init: async ({ logger }) => {
        logger.info("HTTP client plugin initialized");
    },

    execute: (toolbox) => {
        toolbox.http = {
            get: async (url: string) => {
                const response = await fetch(url);
                return response.json();
            },
            post: async (url: string, data: unknown) => {
                const response = await fetch(url, {
                    method: "POST",
                    body: JSON.stringify(data),
                });
                return response.json();
            },
        };
    },

    beforeCommand: async (toolbox) => {
        toolbox.logger.debug(`Preparing HTTP client for ${toolbox.commandName}`);
    },

    afterCommand: async (toolbox, result) => {
        toolbox.logger.debug(`Command ${toolbox.commandName} completed`);
    },

    onError: async (error, toolbox) => {
        toolbox.logger.error(`HTTP error in ${toolbox.commandName}:`, error);
    },
});

Usage in commands:

cli.addCommand({
    name: "fetch",
    execute: async ({ http, logger }) => {
        const data = await http.get("https://api.example.com/data");
        logger.info("Data fetched:", data);
    },
});

Built-in Plugins

Cerebro includes a built-in logger plugin that adds logging to the toolbox:

execute: ({ logger }) => {
    logger.info("Info message");
    logger.error("Error message");
    logger.warn("Warning message");
    logger.debug("Debug message");
};

TypeScript Support

Extend the toolbox type for type safety:

declare global {
    namespace Cerebro {
        interface ExtensionOverrides {
            myFeature: () => void;
            fs: {
                readFile: (path: string) => Promise<string>;
            };
        }
    }
}

Now TypeScript will provide autocomplete and type checking:

execute: ({ myFeature, fs }) => {
    myFeature(); // TypeScript knows this exists
    const content = await fs.readFile("file.txt"); // TypeScript knows the signature
};

Best Practices

  1. Keep plugins focused - Each plugin should have a single responsibility
  2. Use dependencies - If your plugin extends another, declare it as a dependency
  3. Handle errors - Use onError hook for error handling, not execute
  4. Initialize resources in init - Set up connections, load configs, etc.
  5. Extend toolbox in execute - Add utilities to toolbox here
  6. Document your plugin - Always include description and version

Plugin vs Command

  • Plugins - Extend functionality, add to toolbox, lifecycle hooks
  • Commands - User-facing actions, invoked via CLI

Use plugins for reusable functionality, commands for user actions.

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