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

@h3ravel/musket

v2.2.14

Published

Musket CLI is a framework-agnostic CLI framework designed to allow you build artisan-like CLI apps and for use in the H3ravel framework.

Readme

Framework Musket Version Downloads Tests License

About Musket CLI

Musket CLI is a framework-agnostic CLI framework designed to allow you build artisan-like CLI apps and for use in the H3ravel framework.

Installation

Install Musket CLI using your preferred package manager:

npm install @h3ravel/musket
pnpm add @h3ravel/musket
yarn add @h3ravel/musket

Quick Setup

Musket requires an application class that extends its base Application class:

import { Application as BaseApplication } from '@h3ravel/musket';

export class Application extends BaseApplication {}

When Musket is initialized, it binds itself to the application instance through the musket property:

const app = new Application();

// Initialize Musket CLI here.

console.log(app.musket);

The musket property is only available after Musket has been initialized.

Initialization

Use Kernel.init() to initialize Musket:

import { Kernel } from '@h3ravel/musket';
import { Application } from './Application';

const app = new Application();

await Kernel.init(app);

After initialization, the current Musket instance can be accessed from the application:

console.log(app.musket);

The init() method returns Commander.js' Command instance, allowing you to further extend or customize the underlying CLI program:

const program = await Kernel.init(app);

program.option('--debug', 'Enable debug mode');

Passing Configuration

Musket accepts a configuration object as the second argument to Kernel.init():

import path from 'node:path';
import { Kernel } from '@h3ravel/musket';

await Kernel.init(app, {
  name: 'musket-cli',
  packages: ['@h3ravel/shared', '@h3ravel/support'],
  discoveryPaths: [path.join(process.cwd(), 'tests/Commands/*.ts')],
});

The configuration can be used to define the CLI name, package discovery, command discovery paths and other Musket behaviour.

Advanced Initialization

For more control over the initialization process, create the Kernel instance directly and configure it using the available methods:

import path from 'node:path';
import { Kernel } from '@h3ravel/musket';
import { Application } from './Application';
import { TestCommand } from './TestCommand';

const app = new Application();

const kernel = new Kernel(app)
  .setCwd(process.cwd())
  .setConfig({
    name: 'musket-cli',
    discoveryPaths: [path.join(process.cwd(), 'tests/Commands/*.ts')],
  })
  .setPackages([
    {
      name: '@h3ravel/shared',
      alias: 'Shared Package',
    },
    '@h3ravel/support',
  ])
  .registerCommands([TestCommand])
  .bootstrap();

await kernel.run();

When initializing Musket manually, the packages property passed through setConfig() is ignored.

Use setPackages() instead:

kernel.setPackages(['@h3ravel/shared', '@h3ravel/support']);

Application Lifecycle

The application constructor runs before Musket has been initialized. This means this.musket should not be accessed from the constructor:

export class Application extends BaseApplication {
  constructor() {
    super();

    /*
     * Musket has not been attached at this point.
     */
    console.log(this.musket);
  }
}

Use the registerMusketListeners() lifecycle method when you need to access the Musket instance or register command lifecycle listeners:

import { Application as BaseApplication, Musket } from '@h3ravel/musket';

export class Application extends BaseApplication {
  protected registerMusketListeners(musket: Musket<this>): void {
    console.log('Musket has been attached', musket);
  }
}

This method is called after Musket has been attached to the application instance.

Application properties and services are also available inside the method:

export class Application extends BaseApplication {
  logger = console;

  protected registerMusketListeners(musket: Musket<this>): void {
    musket.beforeHandle.on(({ command }) => {
      this.logger.log(`Handling ${command.constructor.name}`);
    });
  }
}

Command Lifecycle Events

Musket exposes events that allow applications and packages to listen to the command execution lifecycle.

The available events are:

  • beforeHandle
  • afterHandle
  • handleFailed

Before Handle

The beforeHandle event is emitted immediately before the command's handle() method or custom resolver is called:

protected registerMusketListeners(
  musket: Musket<this>,
): void {
  musket.beforeHandle.on(({ app, command }) => {
    console.log(
      `Handling ${command.constructor.name}`,
    );

    console.log(app);
  });
}

The listener receives:

{
  app,
  command,
}

Listeners may also be asynchronous:

musket.beforeHandle.on(async ({ command }) => {
  await prepareCommand(command);
});

After Handle

The afterHandle event is emitted after a command has completed successfully:

protected registerMusketListeners(
  musket: Musket<this>,
): void {
  musket.afterHandle.on(({
    command,
    result,
  }) => {
    console.log(
      `Handled ${command.constructor.name}`,
      result,
    );
  });
}

The listener receives:

{
  app,
  command,
  result,
}

The result property contains the value returned by the command's handle() method or custom resolver.

Handle Failed

The handleFailed event is emitted when the command's handle() method or custom resolver throws an error:

protected registerMusketListeners(
  musket: Musket<this>,
): void {
  musket.handleFailed.on(({
    command,
    error,
  }) => {
    console.error(
      `Failed to handle ${command.constructor.name}`,
      error,
    );
  });
}

The listener receives:

{
  app,
  command,
  error,
}

The original error is rethrown after all failure listeners have completed.

The command lifecycle follows this order:

beforeHandle
    ├── success → afterHandle
    └── failure → handleFailed → rethrow

Errors thrown by a beforeHandle listener are not considered command handling failures and therefore do not emit handleFailed.

Removing Listeners

The on() method returns a function that can be used to remove the listener:

const removeListener = musket.beforeHandle.on(({ command }) => {
  console.log(command.constructor.name);
});

removeListener();

Listeners may also be registered to run only once:

musket.beforeHandle.once(({ command }) => {
  console.log(`First command: ${command.constructor.name}`);
});

Event Listener Shortcut

Musket provides a listen() method as a convenient alternative to accessing its lifecycle event properties directly.

musket.listen('handling', ({ command }) => {
  console.log(`Handling ${command.constructor.name}`);
});

musket.listen('handled', ({ command, result }) => {
  console.log(`${command.constructor.name} completed`, result);
});

musket.listen('error', ({ command, error }) => {
  console.error(`${command.constructor.name} failed`, error);
});

The supported event names are:

| Event | Lifecycle event | Emitted when | | ---------- | --------------- | ---------------------------------------- | | handling | beforeHandle | Before command handling begins | | handled | afterHandle | After the command completes successfully | | error | handleFailed | When command handling throws an error |

The following registrations are equivalent:

musket.listen('handling', callback);
musket.beforeHandle.on(callback);
musket.listen('handled', callback);
musket.afterHandle.on(callback);
musket.listen('error', callback);
musket.handleFailed.on(callback);

The method returns a function that removes the listener:

const removeListener = musket.listen('handled', ({ command }) => {
  console.log(command.constructor.name);
});

removeListener();

Listening from Commands

The base Command class also exposes a listen() method. It delegates listener registration to the current Musket instance:

export default class GreetCommand extends Command {
  protected signature = 'greet {name}';

  async handle(): Promise<void> {
    const removeListener = this.listen('error', ({ command, error }) => {
      console.error(`${command.constructor.name} failed`, error);
    });

    this.info(`Hello, ${this.argument('name')}!`);

    removeListener();
  }
}

The command shortcut supports the same event names and payloads as Musket.listen():

this.listen('handling', ({ app, command }) => {
  //
});

this.listen('handled', ({ app, command, result }) => {
  //
});

this.listen('error', ({ app, command, error }) => {
  //
});

When Musket has not yet been attached to the application, Command.listen() does not register the listener and returns an empty removal function.

Listeners registered during handle() remain active until removed. A handling listener registered from inside handle() will not receive the current command's event because that event has already been emitted.

Creating Commands

Musket commands extend the base Command class and define a signature, description and handle() method:

import { Command } from '@h3ravel/musket';

export default class GreetCommand extends Command {
  protected signature = 'greet {name}';

  protected description = 'Display a personalized greeting.';

  async handle(): Promise<void> {
    const name = this.argument('name');

    this.info(`Hello, ${name}!`);
  }
}

Every command has access to the current application instance through this.app:

export default class EnvironmentCommand extends Command {
  protected signature = 'environment';

  protected description = 'Display the current environment.';

  async handle(): Promise<void> {
    this.info(this.app.environment);
  }
}

The application type can also be passed to the command when stronger type inference is required:

import { Command } from '@h3ravel/musket';
import type { Application } from './Application';

export default class EnvironmentCommand extends Command<Application> {
  protected signature = 'environment';

  async handle(): Promise<void> {
    this.info(this.app.environment);
  }
}

Registering Commands

When command discovery paths are configured, matching commands are registered automatically:

await Kernel.init(app, {
  discoveryPaths: [path.join(process.cwd(), 'app/Commands/*.ts')],
});

Commands may also be registered directly on the application:

import { BuildCommand } from './Commands/BuildCommand';
import { GreetCommand } from './Commands/GreetCommand';

export class Application extends BaseApplication {
  registeredCommands = [GreetCommand, BuildCommand];
}

Alternatively, pass commands through the kernel configuration:

await Kernel.init(app, {
  baseCommands: [GreetCommand, BuildCommand],
});

Commands can also be registered during advanced initialization:

const kernel = new Kernel(app)
  .registerCommands([GreetCommand, BuildCommand])
  .bootstrap();

await kernel.run();

Running Commands

Once the CLI has been compiled or built, commands can be executed using Node.js:

node dist/cli.js greet Legacy

Output:

Hello, Legacy!

Documentation

The full musket documentation is available Here

Contributing

Thank you for considering contributing to the H3ravel framework! The Contribution Guide can be found in the H3ravel documentation and will provide you with all the information you need to get started.

Code of Conduct

In order to ensure that the H3ravel community is welcoming to all, please review and abide by the Code of Conduct.

Security Vulnerabilities

If you discover a security vulnerability within H3ravel, please send an e-mail to Legacy via [email protected]. All security vulnerabilities will be promptly addressed.

License

The H3ravel framework is open-sourced software licensed under the MIT license.