This page starts from the command a user wants to type, then maps it to the smallest func model that keeps the CLI predictable. The examples assume the executable name is ship.
Static Command
The user needs a quick health check for the SaaS service, such as whether the API, database, and background workers are available. status is a stable top-level verb, so model it as a normal @Command with one default @Handler. This keeps command discovery, help output, and future options attached to the right scope.
import { Command, Handler } from 'func'
@Command({
path: 'status',
description: 'Print service status',
})
export class StatusCommand {
@Handler()
run() {
console.log('All systems operational')
}
}Command With a Path
The user needs to manage domains, and adding a domain is one fixed action under that domain workflow. domain is the command, and add is a static action under that command. Use @Handler('add') when the action is a fixed path inside an already selected command.
import { Command, Handler } from 'func'
@Command({
path: 'domain',
description: 'Manage custom domains',
})
export class DomainCommand {
@Handler('add')
add() {
console.log('Adding domain')
}
}Command With Flags and a Value
The user needs to register a domain and optionally create an issue or run DNS checks during the same operation. register is still a static command. Boolean switches become @Flag fields, and the domain becomes a required @Value. This lets func parse, assign, and validate the options before the handler runs.
import { Command, Flag, Handler, Required, Value } from 'func'
@Command({
path: 'register',
description: 'Register a domain and optional checks',
})
export class RegisterCommand {
@Flag({ description: 'Create an issue after registration' })
issue = false
@Flag({ description: 'Run DNS checks' })
dns = false
@Required()
@Value()
domain?: string
@Handler()
run() {
console.log(this.domain, {
issue: this.issue,
dns: this.dns,
})
}
}Fallback search input
The search accepts one username and several aliases, with optional profile details and a verified-only filter. The first token is search data rather than a fixed command name, so @CommandMissing handles it as a fallback while the result requirements remain typed flags.
import { Args, CommandMissing, Flag, Handler } from 'func'
import type { Args as CommandArgs } from 'func'
@CommandMissing()
export class UserSearchFallback {
@Flag({ description: 'Include profile details' })
includeProfile = false
@Flag({ description: 'Only return verified users' })
verified = false
@Handler()
async search(@Args() args: CommandArgs) {
const [username, ...aliases] = args.inputs
console.log('Searching user:', {
username,
aliases,
includeProfile: this.includeProfile,
verified: this.verified,
})
}
}Repeated values with an enum
The user needs to resize the deployment to multiple machines in one command, and each machine must use a supported size. scale is a static command, but --machine can be repeated. Use @ArrayString for repeated input and @Enum to reject machine types outside the allowed set.
import { ArrayString, Command, Enum, Handler } from 'func'
const MACHINES = ['1x-1024m', '1x-2048m', '2x-1024m', '2x-2048m']
@Command({
path: 'scale',
description: 'Scale machines',
})
export class ScaleCommand {
@Enum(MACHINES)
@ArrayString('machine')
machines: string[] = []
@Handler()
run() {
console.log('Scaling to:', this.machines)
}
}Check the model before adding files
- Write the exact invocation a user should remember before choosing decorators.
- Use a named command only for a stable top-level word, not for arbitrary user data.
- Use a handler path for fixed nested words and
@Args().inputsfor variable positional data. - Use field options for named data that benefits from a type, default, alias, description, or validation.
- Keep one invocation to one Command or action entry; coordinate multi-step business work through injected Providers.
- Test the canonical input plus every alias, path, and validation rule your public contract adds.
If two models appear to work, prefer the one that makes invalid input impossible to confuse with a valid command. The detailed selection rules are in Commands and Field Options.