Skip to content

Advanced

This page covers topics you won't need for most CLI apps but that become useful as your project grows: manual adapter configuration, Wooks composables, dependency injection scopes, and running CLI and HTTP from the same codebase.

MoostCli adapter (manual setup)

CliApp is a convenience wrapper. For full control, use MoostCli directly:

ts
import { MoostCli, cliHelpInterceptor } from '@moostjs/event-cli'
import { Moost } from 'moost'
import { AppController } from './controllers/app.controller'

const app = new Moost()

app.applyGlobalInterceptors(
  cliHelpInterceptor({
    colors: true,
    lookupLevel: 3,
  }),
)

app.registerControllers(AppController)

app.adapter(new MoostCli({
  wooksCli: {
    cliHelp: { name: 'my-cli' },
  },
  globalCliOptions: [
    { keys: ['help'], description: 'Display instructions for the command.' },
  ],
}))

void app.init()

MoostCli options

The constructor accepts a TMoostCliOpts object:

OptionTypeDescription
wooksCliWooksCli | objectA WooksCli instance or configuration options
debugbooleanEnable internal logging (default: false)
globalCliOptionsarrayOptions shown in help for every command

When debug is false (default), DI container logging is suppressed for a clean terminal.

When to use MoostCli vs CliApp

Use caseRecommendation
Standard CLI appCliApp — less boilerplate
Need a pre-configured WooksCli instanceMoostCli — pass your own instance
Multiple adapters (CLI + HTTP)MoostCli — attach to existing Moost instance
Custom error handling on WooksCliMoostCli — configure wooksCli.onError

Composables

Moost CLI builds on Wooks, which provides composable functions for reading CLI context inside handlers. These work anywhere within the handler call chain — in the handler itself, in services, or in interceptors.

useCliOption(name)

Read a single option value. This is what @CliOption() uses under the hood:

ts
import { useCliOption } from '@wooksjs/event-cli'

@Cli('build')
build() {
  const verbose = useCliOption('verbose')
  if (verbose) console.log('Verbose mode on')
  return 'Building...'
}

useCliOptions()

Read all parsed options at once:

ts
import { useCliOptions } from '@wooksjs/event-cli'

@Cli('build')
build() {
  const opts = useCliOptions()
  // opts: { verbose: true, target: 'production', ... }
  return `Building with ${JSON.stringify(opts)}`
}

useRouteParams()

Read positional arguments. This is what @Param() uses under the hood:

ts
import { useRouteParams } from '@wooksjs/event-core'

@Cli('deploy/:env')
deploy() {
  const params = useRouteParams()
  return `Deploying to ${params.get('env')}`
}

useCliHelp()

Access the help renderer programmatically:

ts
import { useCliHelp } from '@wooksjs/event-cli'

@Cli('info')
info() {
  const { print } = useCliHelp()
  print(true) // print help with colors
}

Dependency injection scopes

Moost's DI container supports two scopes, both relevant to CLI:

ScopeBehaviorCLI use case
SINGLETONOne instance for the entire app lifetimeShared config, database connections
FOR_EVENTNew instance per CLI command executionCommand-specific state
ts
import { Injectable } from 'moost'

@Injectable('SINGLETON')
export class ConfigService {
  readonly env = process.env.NODE_ENV ?? 'development'
}

@Injectable('FOR_EVENT')
export class CommandState {
  startedAt = Date.now()
}

Controllers default to SINGLETON scope. For most CLI apps this is correct — each command invocation uses the same controller instance, with fresh argument values resolved per call.

TIP

See Dependency Injection for constructor injection, circular dependencies, and advanced patterns.

Multi-adapter: CLI + HTTP

A single Moost instance can serve both CLI commands and HTTP routes. Shared controllers work across adapters — decorate methods with both @Cli() and @Get():

ts
import { Cli, Controller, Param } from '@moostjs/event-cli'
import { Get } from '@moostjs/event-http'

@Controller('health')
export class HealthController {
  @Cli('check')
  @Get('check')
  check() {
    return { status: 'ok', uptime: process.uptime() }
  }
}

Wire up both adapters on the same Moost instance:

ts
import { MoostCli } from '@moostjs/event-cli'
import { MoostHttp } from '@moostjs/event-http'
import { Moost } from 'moost'

const app = new Moost()
app.registerControllers(HealthController)

// CLI adapter
app.adapter(new MoostCli())

// HTTP adapter
app.adapter(new MoostHttp()).listen(3000)

void app.init()

Now my-cli health check and GET /health/check both invoke the same handler.

Every launch is parsed as a CLI command

On init(), the CLI adapter immediately runs the command from process.argv — and the default unknown-command handler prints ERROR: Unknown command and calls process.exit(1). Starting the combined app without a valid CLI command (the normal way to start an HTTP server) kills the process before any request is served. Either override the unknown-command behavior:

ts
app.adapter(new MoostCli({
  wooksCli: { onUnknownCommand: () => {} },
}))

or attach the CLI adapter only when the process is actually invoked as a CLI (e.g. gate it behind a process.argv check).

Event type identifier

The cliKind export identifies CLI events in the Wooks context system. This is useful when writing adapters or composables that need to distinguish event types:

ts
import { cliKind } from '@moostjs/event-cli'

What's next

Released under the MIT License.