Welcome to the final chapter of our series!
In the previous chapter, Persistent State Management, we gave our application a long-term memory. We successfully counted how many times the user installed the app and saved it to the disk.
Now, we face one final challenge. The code has run, the browser has opened, and the data is saved. But how does the command tell the main system (and the user) that it is finished? Does it return true? Does it return a string like "Done"? Does it return a number 1?
If every command returned a different type of answer, our main system would be a mess of confusion. To fix this, we use the Standardized Result Protocol.
Imagine a large office building.
The manager (our Main System) would go crazy trying to organize this.
To restore order, the manager issues a rule: "All departments must submit their results on Standard Form 101." This form has two specific boxes:
In our software, this "Standard Form" ensures that no matter what crazy logic happens inside a command, the output always looks exactly the same to the main system.
Let's look at our install-slack-app command. It tries to open a web browser.
We need a standard way to wrap both of these very different outcomes so the user interface knows how to print them.
LocalCommandResult
We define a contract (an Interface in TypeScript) called LocalCommandResult.
It looks simply like this:
// The contract every command must obey
interface LocalCommandResult {
type: 'text'; // We might add 'image' or 'json' later
value: string; // The message to show the user
}
In our install-slack-app.ts file, when everything goes right, we fill out the form like this:
// ... inside the call() function
if (success) {
return {
type: 'text',
value: 'Opening Slack app installation page in browserβ¦',
}
}
Explanation:
type: 'text': We tell the system, "Treat this result as plain text."value: This is the string that will appear in the user's terminal.If the browser fails to open, we don't crash the program. We just fill out the form differently:
// ... inside the call() function
else {
return {
type: 'text',
value: `Couldn't open browser. Visit: ${SLACK_APP_URL}`,
}
}
Notice that the structure of the object is identical to the success case. This is the power of the protocol. The main system doesn't need to know why it failed; it just sees a result object and displays it.
How does the main system actually use this object? It acts like a relay race.
Here is the sequence:
Let's look at a simplified version of the code that receives our command's output. This code lives in the core of the application (e.g., cli.ts or main.ts).
// This function runs the command and handles the output
async function runCommand(command) {
// 1. Run the logic we wrote in Chapter 3
const result = await command.call()
// 2. The Protocol in action: Check the 'type'
if (result.type === 'text') {
console.log(result.value)
}
// Future proofing:
else if (result.type === 'error') {
console.error(result.value)
}
}
Explanation:
await command.call(): This triggers our install-slack-app.ts file.result: This variable now holds the object { type: 'text', value: '...' }.if (result.type === 'text'): The system now knows exactly what to do. It prints the value to the console.
If we didn't have this protocol, and our command just returned true, the runCommand function wouldn't know what message to show the user!
Let's review the full flow of our install-slack-app project across all 5 chapters.
install-slack-app on the menu (Metadata) but doesn't load the code.install-slack-app.ts from the disk.call() function. The browser opens.{ type: 'text' } object. The system receives it and prints "Opening Slack app..." to the user.Congratulations! You have built a fully functional, professional-grade command for a CLI application.
By adhering to the Standardized Result Protocol, you ensured that your specific command (install-slack-app) behaves like a good citizen within the larger system. It speaks the same language as every other command, making the application stable, predictable, and easy to maintain.
You now understand the lifecycle of a modern command-line tool command:
Happy coding!
Generated by Code IQ