Recently, while revising Node.js, I came across the project section Node.js roadmap on roadmap.sh(https://roadmap.sh).
The first project challenge there is to create a CLI (Command-Line Interface) tool using Node.js. So,I decided to build one to refresh my basics.
This post explains how CLI tools actually work under the hood and how to build one yourself from scratch.
How OS Executes Script Files
Before creating any CLI, we must understand how operating systems execute scripts.
Normally, when you run a Node.js file like this:
node index.js
it’s Node.js that runs the file — not your operating system.
The OS doesn’t automatically know how to execute JavaScript — it just knows how to…
Recently, while revising Node.js, I came across the project section Node.js roadmap on roadmap.sh(https://roadmap.sh).
The first project challenge there is to create a CLI (Command-Line Interface) tool using Node.js. So,I decided to build one to refresh my basics.
This post explains how CLI tools actually work under the hood and how to build one yourself from scratch.
How OS Executes Script Files
Before creating any CLI, we must understand how operating systems execute scripts.
Normally, when you run a Node.js file like this:
node index.js
it’s Node.js that runs the file — not your operating system.
The OS doesn’t automatically know how to execute JavaScript — it just knows how to run:
- Compiled binaries (bash, python, node, etc.)
- Or scripts that explicitly tell it which program should interpret them.
If you want your script to be run directly (e.g., by typing mycli in the terminal), then the OS must know which program to use to interpret it.
By default, if you make a file executable, the OS will assume it’s a shell script unless you specify otherwise.
To tell the OS that your script should be run by Node.js, add this line at the top of your file:
#!/usr/bin/env node
This line is called a shebang, and it tells the OS: “Run this file using Node.js, wherever it’s installed.”
How npm Installs CLI Tools Globally
When we install any package globally using npm install -g npm do the following things:
- Installs the package in your system’s global node_modules directory(the path depends on your OS).
- Looks for the bin field inside the package’s package.json.
- Creates a symlink (symbolic link) between the CLI command and the script file defined in bin. So, for example, if your package.json contains:
"bin": {
"taskcli": "./bin/index.mjs"
}
npm will create a command called taskcli that points to your script.
The bin Field in package.json
The bin field defines which commands your package exposes and what files they execute.
I like to keep all CLI entry files inside a bin/ folder for clarity.
Parsing Command-Line Arguments
When we execute a script, Node exposes the arguments through process.argv.
node script.js add "Buy groceries"
process.argv will contain:
[
'/usr/bin/node',
'/path/to/script.js',
'add',
'Buy groceries'
]
we could manually parse this array, but that’s tedious.Instead, use a library like Commander.js,which gives you:
- Argument parsing
- Subcommands
- Automatic –help and –version support
Making the Tool Work Locally — npm link
Before publishing your CLI to npm, we can test it locally using:
npm link
This creates a symlink between your local project and your global npm binaries directory,so your command (taskcli) behaves like a globally installed CLI.
Now you can run:
taskcli add "Learn Node.js"
taskcli list
No global installation needed and everything runs locally.
Final Project
Here’s my implementation:
GitHub: MehakB7/task-cli Roadmap.sh Submission: Task Tracker Project Solution
This small project helped me understand: How the OS executes scripts How npm sets up CLI tools under the hood How to design structured, maintainable command-line apps