Skip to content

Command Line Tools

Creating Custom Commands Guide

Learn how to extend Wheels CLI with your own custom commands.

Wheels CLI is built on CommandBox, making it easy to add custom commands. Commands can be simple scripts or complex operations using the service architecture.

Step 1. Create a box.json if not exist in Directory “cli/src”

Section titled “Step 1. Create a box.json if not exist in Directory “cli/src””

Make sure these properties exist: “name”, “version”, “slug” and “type”

{
"name": "wheels-cli",
"version": "3.0.0-SNAPSHOT",
"slug": "wheels-cli",
"type": "modules"
}

Open CommandBox in the directory “cli/src” and link this directory as a module:

Terminal window
box package link --force

This allows you to develop and test CLI commands locally.

Create a new file in cli/src/commands/wheels/:

commands/wheels/hello.cfc
component extends="wheels-cli.models.BaseCommand" {
/**
* Say hello
*/
function run(string name = "World") {
print.line("Hello, #arguments.name#!");
}
}
Terminal window
wheels hello
# Output: Hello, World!
wheels hello John
# Output: Hello, John!
component extends="wheels-cli.models.BaseCommand" {
// Command metadata
property name="name" default="mycommand";
property name="description" default="Does something useful";
// Service injection
property name="myService" inject="MyService@wheels-cli";
/**
* Main command entry point
*
* @name Name of something
* @force Force overwrite
* @name.hint The name to use
* @force.hint Whether to force
*/
function run(
required string name,
boolean force = false
) {
// Reconstruct arguments for handling -- prefixed options
arguments = reconstructArgs(argStruct=arguments);
// Command logic here
}
}

CommandBox generates help from your code:

Terminal window
wheels hello --help
NAME
wheels hello
SYNOPSIS
wheels hello [name]
DESCRIPTION
Say hello
ARGUMENTS
name = World
Name to greet

Create nested command structure:

commands/wheels/deploy.cfc
component extends="wheels-cli.models.BaseCommand" {
function run() {
print.line("Usage: wheels deploy [staging|production]");
}
}
// commands/wheels/deploy/staging.cfc
component extends="wheels-cli.models.BaseCommand" {
function run() {
print.line("Deploying to staging...");
}
}
// commands/wheels/deploy/production.cfc
component extends="wheels-cli.models.BaseCommand" {
function run() {
print.line("Deploying to production...");
}
}

Usage:

Terminal window
wheels deploy staging
wheels deploy production

Get user input:

component extends="wheels-cli.models.BaseCommand" {
function run() {
// Simple input
var name = ask("What's your name? ");
// Masked input (passwords)
var password = ask("Enter password: ", "*");
// Confirmation
if (confirm("Are you sure?")) {
print.line("Proceeding...");
}
// Multiple choice
var choice = multiselect()
.setQuestion("Select features to install:")
.setOptions([
"Authentication",
"API",
"Admin Panel",
"Blog"
])
.ask();
}
}

Show progress for long operations:

component extends="wheels-cli.models.BaseCommand" {
function run() {
// Progress bar
var progressBar = progressBar.create(total=100);
for (var i = 1; i <= 100; i++) {
// Do work
sleep(50);
// Update progress
progressBar.update(
current = i,
message = "Processing item #i#"
);
}
progressBar.clear();
print.greenLine("✓ Complete!");
// Spinner
var spinner = progressSpinner.create();
spinner.start("Loading...");
// Do work
sleep(2000);
spinner.stop();
}
}
component extends="wheels-cli.models.BaseCommand" {
property name="codeGenerationService" inject="CodeGenerationService@wheels-cli";
property name="templateService" inject="TemplateService@wheels-cli";
function run(required string name) {
// Use services
var template = templateService.getTemplate("custom");
var result = codeGenerationService.generateFromTemplate(
template = template,
data = {name: arguments.name}
);
print.greenLine("Generated: #result.path#");
}
}
models/CustomService.cfc
component singleton {
function processData(required struct data) {
// Service logic
return data;
}
}
// Register in ModuleConfig.cfc
binder.map("CustomService@wheels-cli")
.to("wheels.cli.models.CustomService")
.asSingleton();
function run(required string file) {
var filePath = resolvePath(arguments.file);
if (!fileExists(filePath)) {
error("File not found: #filePath#");
}
var content = fileRead(filePath);
print.line(content);
}
function run(required string name) {
var content = "Hello, #arguments.name#!";
var filePath = resolvePath("output.txt");
if (fileExists(filePath) && !confirm("Overwrite existing file?")) {
return;
}
fileWrite(filePath, content);
print.greenLine("✓ File created: #filePath#");
}
function run(required string dir) {
// Create directory
ensureDirectoryExists(arguments.dir);
// List files
var files = directoryList(
path = resolvePath(arguments.dir),
recurse = true,
filter = "*.cfc"
);
for (var file in files) {
print.line(file);
}
}
function run() {
// Basic colors
print.line("Normal text");
print.redLine("Error message");
print.greenLine("Success message");
print.yellowLine("Warning message");
print.blueLine("Info message");
// Bold
print.boldLine("Important!");
print.boldRedLine("Critical error!");
// Inline colors
print.line("This is #print.red('red')# and #print.green('green')#");
}
function run() {
// Create table
print.table([
["Name", "Type", "Size"],
["users.cfc", "Model", "2KB"],
["posts.cfc", "Model", "3KB"],
["comments.cfc", "Model", "1KB"]
]);
// With headers
var data = queryNew("name,type,size", "varchar,varchar,varchar", [
["users.cfc", "Model", "2KB"],
["posts.cfc", "Model", "3KB"]
]);
print.table(
data = data,
headers = ["File Name", "Type", "File Size"]
);
}
function run() {
print.tree([
{
label: "models",
children: [
{label: "User.cfc"},
{label: "Post.cfc"},
{label: "Comment.cfc"}
]
},
{
label: "controllers",
children: [
{label: "Users.cfc"},
{label: "Posts.cfc"}
]
}
]);
}
function run(required string file) {
try {
var content = fileRead(arguments.file);
processFile(content);
print.greenLine("✓ Success");
} catch (any e) {
print.redLine("✗ Error: #e.message#");
if (arguments.verbose ?: false) {
print.line(e.detail);
print.line(e.stacktrace);
}
// Set exit code
return 1;
}
}
function run(required string name) {
// Validation
if (!isValidName(arguments.name)) {
error("Invalid name. Names must be alphanumeric.");
}
// Warnings
if (hasSpecialChars(arguments.name)) {
print.yellowLine("Warning: Special characters detected");
}
// Success
print.greenLine("Name is valid");
}
private function error(required string message) {
print.redLine("#arguments.message#");
exit(1);
}
tests/commands/HelloTest.cfc
component extends="wheels.Testbox" {
function run() {
describe("Hello Command", function() {
it("greets with default name", function() {
var result = execute("wheels hello");
expect(result).toInclude("Hello, World!");
});
it("greets with custom name", function() {
var result = execute("wheels hello John");
expect(result).toInclude("Hello, John!");
});
});
}
private function execute(required string command) {
// Capture output
savecontent variable="local.output" {
shell.run(arguments.command);
}
return local.output;
}
}
it("generates files correctly", function() {
// Run command
execute("wheels generate custom test");
// Verify files created
expect(fileExists("/custom/test.cfc")).toBeTrue();
// Verify content
var content = fileRead("/custom/test.cfc");
expect(content).toInclude("component");
// Cleanup
fileDelete("/custom/test.cfc");
});
  • Use verbs for actions: generate, create, deploy
  • Use nouns for resources: model, controller, migration
  • Be consistent with existing commands
function run(required string name, string type = "default") {
// Validate required
if (!len(trim(arguments.name))) {
error("Name cannot be empty");
}
// Validate options
var validTypes = ["default", "custom", "advanced"];
if (!arrayFind(validTypes, arguments.type)) {
error("Invalid type. Must be one of: #arrayToList(validTypes)#");
}
}
function run() {
print.line("Starting process...").toConsole();
// Show what's happening
print.indentedLine("→ Loading configuration");
var config = loadConfig();
print.indentedLine("→ Processing files");
var count = processFiles();
print.indentedLine("→ Saving results");
saveResults();
print.greenBoldLine("✓ Complete! Processed #count# files.");
}
function run(required string name) {
var filePath = resolvePath("#arguments.name#.txt");
// Check if already exists
if (fileExists(filePath)) {
print.yellowLine("File already exists, skipping");
return;
}
// Create file
fileWrite(filePath, "content");
print.greenLine("✓ Created file");
}

Create box.json:

{
"name": "my-wheels-commands",
"version": "1.0.0",
"type": "commandbox-modules",
"dependencies": {
"wheels-cli": "^3.0.0"
}
}
my-wheels-commands/
├── ModuleConfig.cfc
├── commands/
│ └── wheels/
│ └── mycommand.cfc
└── models/
└── MyService.cfc
Terminal window
box forgebox publish
commands/wheels/db/backup.cfc
component extends="wheels-cli.models.BaseCommand" {
property name="datasource" inject="coldbox:datasource";
function run(string file = "backup-#dateFormat(now(), 'yyyy-mm-dd')#.sql") {
arguments = reconstructArgs(argStruct=arguments);
print.line("Creating database backup...").toConsole();
var spinner = progressSpinner.create();
spinner.start("Backing up database");
try {
// Get database info
var dbInfo = getDatabaseInfo();
// Create backup
var backupPath = resolvePath(arguments.file);
createBackup(dbInfo, backupPath);
spinner.stop();
print.greenBoldLine("✓ Backup created: #backupPath#");
} catch (any e) {
spinner.stop();
print.redLine("✗ Backup failed: #e.message#");
return 1;
}
}
}
commands/wheels/quality.cfc
component extends="wheels-cli.models.BaseCommand" {
property name="analysisService" inject="AnalysisService@wheels-cli";
function run(string path = ".", boolean fix = false) {
arguments = reconstructArgs(argStruct=arguments);
var analysisService = application.wirebox.getInstance("AnalysisService@wheels-cli");
var issues = analysisService.analyze(arguments.path);
if (arrayLen(issues)) {
print.redLine("Found #arrayLen(issues)# issues:");
for (var issue in issues) {
print.line("#issue.file#:#issue.line# - #issue.message#");
}
if (arguments.fix) {
print.line().line("Attempting fixes...");
var fixed = analysisService.fix(issues);
print.greenLine("Fixed #fixed# issues");
}
} else {
print.greenLine("✓ No issues found!");
}
}
}