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:
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:
| Option | Type | Description |
|---|---|---|
wooksCli | WooksCli | object | A WooksCli instance or configuration options |
debug | boolean | Enable internal logging (default: false) |
globalCliOptions | array | Options 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 case | Recommendation |
|---|---|
| Standard CLI app | CliApp — less boilerplate |
| Need a pre-configured WooksCli instance | MoostCli — pass your own instance |
| Multiple adapters (CLI + HTTP) | MoostCli — attach to existing Moost instance |
| Custom error handling on WooksCli | MoostCli — 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:
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:
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:
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:
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:
| Scope | Behavior | CLI use case |
|---|---|---|
SINGLETON | One instance for the entire app lifetime | Shared config, database connections |
FOR_EVENT | New instance per CLI command execution | Command-specific state |
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():
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:
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:
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:
import { cliKind } from '@moostjs/event-cli'What's next
- Interceptors guide — full interceptor lifecycle and class-based patterns
- Dependency Injection — scopes, constructor injection, circular dependencies
- Pipes & Validation — transform and validate command arguments