๐Ÿ’ป Day 2 โ€” Build a Real CLI Interface

14 min readยทJan 1, 2026

Welcome to Day 2.

In yesterday's lesson, you built the foundation of the lb_notes command and made --help work from your terminal.

In today's lesson, you'll add the first two features of LBNotes: adding a note and listing notes.

You'll learn how CLI commands are structured, how input is validated, and how to produce clear error messages when the command is used incorrectly.

By the end of this lesson, you'll be able to run lb_notes add "..." and lb_notes list and see the expected behavior.

Implement a fake add() function

Within the src/cli/commands directory, let's create a new file named add.js.

~/projects/
โ””โ”€ lb_notes/
   โ”œโ”€ bin/
   โ”œโ”€ package.json
   โ””โ”€ src/
      โ””โ”€ cli/
         โ”œโ”€ commands/
         โ”‚  โ”œโ”€ add.js
         โ”‚  โ””โ”€ help.js
         โ”œโ”€ index.js
         โ””โ”€ parse_args.js

Within this file, let's export the following placeholder function used to check if the provided note is valid (i.e. a non-empty string) and output a fake validation message, or throw an error otherwise.

src/cli/commands/add.js
export default function add(note) {
  note = note?.trim();

  if (!note || !note.length) {
    throw new Error('Invalid note');
  }
  console.log('Note saved!');
}

Where:

  • note is a string of characters that represents a simple note (e.g., "Get a carton of 12 eggs").

Implement a fake list() function

Within the src/cli/commands directory, let's create a new file named list.js.

~/projects/
โ””โ”€ lb_notes/
   โ”œโ”€ bin/
   โ”œโ”€ package.json
   โ””โ”€ src/
      โ””โ”€ cli/
         โ”œโ”€ commands/
         โ”‚  โ”œโ”€ add.js
         โ”‚  โ”œโ”€ help.js
         โ”‚  โ””โ”€ list.js
         โ”œโ”€ index.js
         โ””โ”€ parse_args.js

Within this file, let's export the following placeholder function used to output a fake list of notes.

src/cli/commands/list.js
export default function list() {
  console.log([
    'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
    'Vivamus vel odio blandit, pretium leo et, pretium nibh.'
  ]);
}

Parse the add and list commands

Within the parseArgs() function, let's add a new conditional statement that checks if the first element of the args array equals to the "add" string, and return an object that contains the add command and the list of remaining command-line arguments, concatenated as a single string.

src/cli/parse_args.js
export default function parseArgs(args) {
  if (!args || !args.length || args[0] === '-h' || args[0] === '--help') {
    return {
      command: 'help'
    };
  }
  else if (args[0] === 'add') {
    return {
      command: 'add',
      value: args.slice(1).join(' ')
    };
  }

  throw new Error(`${args[0]}: Unknown command`);
}

Then, let's add another conditional statement that checks if the first element of the args array equals to the "list" string, and return an object that contains the list command.

src/cli/parse_args.js
export default function parseArgs(args) {
  if (!args || !args.length || args[0] === '-h' || args[0] === '--help') {
    return {
      command: 'help'
    };
  }
  else if (args[0] === 'add') {
    return {
      command: 'add',
      value: args.slice(1).join(' ')
    };
  }
  else if (args[0] === 'list') {
    return {
      command: 'list'
    };
  }

  throw new Error(`${args[0]}: Unknown command`);
}

Execute the add and list commands

Within the run() function, let's import the add.js and list.js modules from the commands directory, and execute the add() and list() functions exported by these modules based on the command property of the object returned by the parseArgs() function.

src/cli/index.js
import parseArgs from './parse_args.js';
import help from './commands/help.js';
import add from './commands/add.js';
import list from './commands/list.js';

export default function run(args) {
  try {
    const { command, value } = parseArgs(args);

    if (command === 'help') {
      help();
    }
    else if (command === 'add') {
      add(value);
    }
    else if (command === 'list') {
      list();
    }
  } catch(error) {
    console.error(`lb_notes: error: ${error.message}`);
    return 1;
  }
  return 0;
}

โš ๏ธ Don't forget to execute the add() function with the value property returned by the parseArgs() function.

Update the help command

Now that our implementation is complete, let's not forget to update the help() function to document the changes made to the CLI.

src/cli/commands/help.js
export default function help() {
  console.log(`NAME
    LBNotes โ€” a tiny CLI notes tool

SYNOPSIS
    lb_notes [-h|--help]
        Output this help menu.

    lb_notes add string ...
        Concatenate and add the specified strings to the list of notes.
    
    lb_notes list
        Output the list of notes.`);
}

Test the CLI

Let's now make sure that everything works as expected.

Test the --help flag

When running the lb_notes command with the --help flag, it should output the help menu.

~/projects/lb_notes$ lb_notes --help

Test the add command

When running the lb_notes command with the add command and no arguments, it should output an error.

~/projects/lb_notes$ lb_notes add
lb_notes: error: Invalid note

When running the lb_notes command with the add command and an empty string as argument, it should output an error.

~/projects/lb_notes$ lb_notes add ""
lb_notes: error: Invalid note

When running the lb_notes command with the add command and a valid string as argument, it should output "Note saved!".

~/projects/lb_notes$ lb_notes add "Get a carton of eggs"
Note saved!

Test the list command

When running the lb_notes command with the list command, it should output a list of notes.

~/projects/lb_notes$ lb_notes list
[
  'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
  'Vivamus vel odio blandit, pretium leo et, pretium nibh.'
]

You're done for today โœ