Welcome to the first chapter of the Login project tutorial! In this series, we will build a robust login feature for a Command Line Interface (CLI).
Before we write any complex logic or draw pixels on the screen, we need to tell our CLI tool that this feature exists. This brings us to the Command Definition.
Imagine a CLI tool with 50 different features (login, logout, deploy, build, test, etc.). If the CLI tries to load the code for all 50 features every time you type a command, it will be very slow to start.
We need a way to list the features without doing the heavy lifting immediately.
The Solution: Think of the Command Definition as a Restaurant Menu.
In our project, the index.ts file acts as this menu item for the login command.
To solve our use caseβcreating a lightweight entry point for our login featureβwe use a specific object structure. Here are the parts:
--help.isEnabled): A rule that decides if this command should be available right now.load): A function that imports the heavy code only when the user selects this command.
Let's look at how we construct this in code. We are working in index.ts.
First, we export a function that returns an object. We give it a name (what the user types) and a type.
import type { Command } from '../../commands.js'
export default () =>
({
type: 'local-jsx', // Tells the CLI this uses a UI
name: 'login', // The user types: $ cli login
// ... logic continues below
}) satisfies Command
type: local-jsx means we will eventually render a UI (we cover this in React-based Terminal UI).name: This is the keyword that triggers the command.Descriptions can change based on context. If you are already logged in, the text should reflect that.
import { hasAnthropicApiKeyAuth } from '../../utils/auth.js'
// Inside the object...
description: hasAnthropicApiKeyAuth()
? 'Switch Anthropic accounts'
: 'Sign in with your Anthropic account',
isEnabled)Sometimes, we want to disable a command entirely based on environment variables (like a "Kill Switch").
import { isEnvTruthy } from '../../utils/envUtils.js'
// Inside the object...
isEnabled: () => !isEnvTruthy(process.env.DISABLE_LOGIN_COMMAND),
isEnabled: This function returns true or false.DISABLE_LOGIN_COMMAND is set to true in your system, this command vanishes from the menu.load)
This is the most important part for performance. We use a dynamic import.
// Inside the object...
load: () => import('./login.js'),
./login.js contains the actual heavy logic (the "steak"). It is not read or executed until the CLI specifically asks for it.How does the CLI Core interact with this definition? Let's visualize the flow.
When you type cli login in your terminal, the following conversation happens inside the program:
index.ts file. Because this file is tiny and has no heavy imports, this is instant.isEnabled function. If it returns false, the CLI prints "Command not found."login, the load() function is triggered../login.js file is loaded into memory, and the CLI hands control over to it.
This login.js file usually contains the User Interface, which we will discuss in depth in the next chapter: React-based Terminal UI.
In this chapter, we learned how to create a Command Definition. We solved the problem of slow startup times by creating a lightweight "menu item" that describes our feature without loading it immediately.
We defined:
isEnabled logic to control availability.load function to import the heavy code on demand.Now that we have successfully registered our command, we need to build the actual interface that the user sees when the command loads.
Next Chapter: React-based Terminal UI
Generated by Code IQ