Skip to content

Commands & Options

This guide covers how to work with commands, options, positional arguments, and nested command hierarchies in Padrone.

Options are defined using Zod schemas. Each property in the schema becomes a CLI option:

import { createPadrone } from 'padrone';
import * as z from 'zod/v4';
const program = createPadrone('app')
.options(
z.object({
port: z.number().default(3000).describe('Port to listen on'),
host: z.string().default('localhost').describe('Host to bind to'),
verbose: z.boolean().optional().describe('Enable verbose logging'),
})
)
.action((options) => {
// options: { port: number; host: string; verbose?: boolean }
});

Padrone supports these Zod types:

Zod TypeCLI Input Example
z.string()--name "John"
z.number()--port 3000
z.boolean()--verbose or --no-verbose
z.enum(['a', 'b'])--level high
z.array(z.string())--tags foo --tags bar or --tags=[foo,bar]

Add short aliases using .meta():

z.object({
port: z.number().default(3000).meta({ alias: 'p' }),
verbose: z.boolean().optional().meta({ alias: 'v' }),
})

Users can now use -p 8080 instead of --port 8080.

The .meta() method supports several properties:

z.string().meta({
alias: 'o', // Short alias
examples: ['file.txt'], // Example values for help text
deprecated: 'Use --out', // Deprecation warning
hidden: true, // Hide from help output
env: 'OUTPUT_FILE', // Bind to environment variable
configKey: 'output.file' // Bind to config file key
})

Positional arguments let users provide values without option names:

.options(
z.object({
source: z.string().describe('Source file'),
dest: z.string().describe('Destination file'),
}),
{ positional: ['source', 'dest'] }
)
Terminal window
# Both are equivalent:
app copy file.txt backup.txt
app copy --source file.txt --dest backup.txt

Use ... prefix for variadic (rest) arguments that capture multiple values:

.options(
z.object({
files: z.array(z.string()).describe('Files to process'),
output: z.string().describe('Output directory'),
}),
{ positional: ['...files', 'output'] }
)
Terminal window
app process a.txt b.txt c.txt ./out
# files: ['a.txt', 'b.txt', 'c.txt'], output: './out'

Add commands using the .command() method:

const program = createPadrone('git')
.command('clone', (c) =>
c
.options(
z.object({
url: z.string().describe('Repository URL'),
depth: z.number().optional().describe('Clone depth'),
}),
{ positional: ['url'] }
)
.action((options) => {
console.log(`Cloning ${options.url}`);
})
)
.command('status', (c) =>
c.action(() => {
console.log('On branch main');
})
);

Configure commands with .configure():

.command('serve', (c) =>
c
.configure({
description: 'Start the development server',
examples: ['serve --port 8080', 'serve -p 3000'],
})
.options(schema)
.action(handler)
)

Commands can contain subcommands to any depth:

const program = createPadrone('db')
.command('migrate', (c) =>
c
.command('up', (c) =>
c.action(() => console.log('Running migrations'))
)
.command('down', (c) =>
c
.options(z.object({ steps: z.number().default(1) }))
.action((opts) => console.log(`Rolling back ${opts.steps} migrations`))
)
.command('status', (c) =>
c.action(() => console.log('Migration status'))
)
);
Terminal window
db migrate up
db migrate down --steps 3
db migrate status

Bind options to environment variables:

.options(
z.object({
apiKey: z.string().describe('API key'),
debug: z.boolean().optional(),
}),
{
options: {
apiKey: { env: 'API_KEY' },
debug: { env: ['DEBUG', 'APP_DEBUG'] }, // Multiple env vars
}
}
)

Priority order: CLI argument > Environment variable > Default value

Load options from configuration files:

const program = createPadrone('app')
.configure({
configFiles: ['app.config.json', '.apprc', 'app.config.yaml'],
})
.options(
z.object({
port: z.number().default(3000),
host: z.string().default('localhost'),
}),
{
options: {
port: { configKey: 'server.port' },
host: { configKey: 'server.host' },
}
}
);

With app.config.json:

{
"server": {
"port": 8080,
"host": "0.0.0.0"
}
}

Priority order: CLI argument > Environment variable > Config file > Default value

Padrone automatically generates help text:

// Print help for the program
console.log(program.help());
// Print help for a specific command
console.log(program.help('migrate up'));
// Different formats
program.help('', { format: 'text' }); // Plain text
program.help('', { format: 'ansi' }); // With colors
program.help('', { format: 'markdown' });
program.help('', { format: 'html' });
program.help('', { format: 'json' });

Look up commands programmatically:

const migrateUp = program.find('migrate up');
if (migrateUp) {
console.log(migrateUp.name); // 'up'
}