Welcome to the first chapter of the add-dir project tutorial!
In this series, we will build a command-line tool that helps users add and manage directories. Before we can build fancy interactive screens or validate user input, we need to establish the foundation.
Imagine you are running a restaurant. You have a brilliant chef in the kitchen ready to cook a specific dish (the implementation), and hungry customers outside (the users).
However, if that dish isn't written on the Menu, nobody knows they can order it!
The Command Definition is exactly like that menu entry. It solves a specific problem: Discoverability. It tells the main CLI program:
add-dir."We want a user to be able to open their terminal and see our command listed in the help menu, and then be able to run it like this:
my-cli add-dir ./new-folder
In this chapter, we will create the entry point that makes this possible.
We need to export a lightweight object that defines the "metadata" of our command. This file is usually index.ts.
Let's look at the implementation in two small parts.
First, we define how the command looks to the user.
// --- File: index.ts ---
const addDir = {
name: 'add-dir', // The command the user types
description: 'Add a new working directory', // Shown in help
argumentHint: '<path>', // Hints that a path is required
// ... more properties below
}
Explanation:
--help flag, this text explains what the command does../src).Next, we tell the CLI how to run the command and ensure our code follows the rules.
import type { Command } from '../../commands.js'
const addDir = {
// ... properties from Part 1
type: 'local-jsx',
load: () => import('./add-dir.js'),
} satisfies Command
export default addDir
Explanation:
import(). This is called Lazy Loading. The heavy code inside add-dir.js is not loaded when the CLI starts. It is only loaded if the user actually selects this specific command. This makes the CLI start up much faster.name and description) so we don't make mistakes.What actually happens when you run the CLI? Let's trace the flow from the moment the user types a command.
index.ts. It sees the "Menu" (name and description) but does not enter the "Kitchen" (load the implementation) yet.add-dir.load() function defined in our object.add-dir.js) is imported and executed.Here is a sequence diagram showing this interaction:
The beauty of this abstraction is that it keeps the main entry point (index.ts) extremely small. It acts as a router.
This pattern prevents "bloat." If you had 50 commands in your CLI tool, you wouldn't want to load the code for all 50 of them just to print the help menu. By using the load function with a dynamic import, we ensure that resources are only used when absolutely necessary.
// The 'load' function returns a Promise
// This Promise resolves to the module containing the real logic
load: () => import('./add-dir.js'),
Once load is finished, the CLI hands over control to the code inside ./add-dir.js, which will handle the visual elements and logic.
In this chapter, we created the "Menu Entry" for our command. We learned:
satisfies Command) to ensure our definition is valid.Now that the CLI knows our command exists and knows how to load it, we need to define what the user actually sees when the command runs.
In the next chapter, we will build the visual interface for this command.
Next Chapter: Interactive Command UI
Generated by Code IQ