Loading
Create a command-line task manager with Node.js — file I/O, argument parsing, and colored output.
In this tutorial, you'll build a fully functional command-line task manager called taskr. It stores tasks in a local JSON file and supports adding, listing, completing, and deleting tasks — all from the terminal.
What you'll learn:
Create a new directory and initialize the project:
Create the main file:
Edit package.json to add a bin field so we can run taskr as a command:
Your project structure is ready.
The first thing any CLI needs is argument parsing. Add this to index.js:
Test it:
Your CLI recognizes commands. Next, make them do something.
Tasks need to persist between runs. We'll use a JSON file:
Why homedir()? Storing in the user's home directory means tasks persist regardless of which directory you run the command from.
Tasks now persist between terminal sessions.
Replace the add case with real functionality:
The \x1b[32m sequences add color:
\x1b[32m = green\x1b[31m = red\x1b[33m = yellow\x1b[0m = reset to defaultTest it:
You can now add tasks from the command line.
Test it:
You should see a formatted list with status icons and a summary.
Tasks display with colored status indicators.
Test it:
The first task should now show as completed with a green checkmark.
Tasks can be marked as done by their number.
Tasks can be removed permanently.
Add clear functionality too:
Your CLI is now user-friendly with help text and cleanup commands.
Link your project globally so you can run taskr from anywhere:
Now test it:
To unlink later:
Your tool is now a real CLI command, runnable from any directory.
Your CLI works! Here are ways to extend it:
Easy:
taskr count to show total/done/pending countstaskr add "Buy groceries" --due tomorrowMedium:
taskr add "Fix login bug" --tag worktaskr search <term> to filter tasksHard:
taskr timer <number> for Pomodoro-style focus sessionsYou've built a real CLI tool from scratch. The skills transfer to any Node.js project.
What you built: A fully functional CLI task manager with file persistence, colored output, and multiple commands. This is the same pattern used by tools like npm, git, and vercel CLI.
mkdir taskr
cd taskr
npm init -ytouch index.js{
"name": "taskr",
"version": "1.0.0",
"bin": {
"taskr": "./index.js"
},
"type": "module"
}#!/usr/bin/env node
const args = process.argv.slice(2);
const command = args[0];
const rest = args.slice(1).join(" ");
switch (command) {
case "add":
console.log(`Adding task: ${rest}`);
break;
case "list":
console.log("Listing all tasks...");
break;
case "done":
console.log(`Completing task: ${rest}`);
break;
case "delete":
console.log(`Deleting task: ${rest}`);
break;
default:
console.log("Usage: taskr <add|list|done|delete> [args]");
}node index.js add "Buy groceries"
node index.js listimport { readFileSync, writeFileSync, existsSync } from "fs";
import { join } from "path";
import { homedir } from "os";
const TASKS_FILE = join(homedir(), ".taskr.json");
function loadTasks() {
if (!existsSync(TASKS_FILE)) return [];
try {
const data = readFileSync(TASKS_FILE, "utf-8");
return JSON.parse(data);
} catch {
return [];
}
}
function saveTasks(tasks) {
writeFileSync(TASKS_FILE, JSON.stringify(tasks, null, 2));
}function addTask(description) {
const tasks = loadTasks();
const task = {
id: Date.now(),
description,
done: false,
createdAt: new Date().toISOString(),
};
tasks.push(task);
saveTasks(tasks);
console.log(`\x1b[32m✓\x1b[0m Added: ${description}`);
}
// In the switch statement:
case "add":
if (!rest) {
console.error("\x1b[31mError:\x1b[0m Please provide a task description");
process.exit(1);
}
addTask(rest);
break;node index.js add "Buy groceries"
node index.js add "Write documentation"
node index.js add "Review pull request"function listTasks() {
const tasks = loadTasks();
if (tasks.length === 0) {
console.log("No tasks yet. Add one with: taskr add <description>");
return;
}
console.log("\n Your Tasks\n");
tasks.forEach((task, index) => {
const status = task.done ? "\x1b[32m✓\x1b[0m" : "\x1b[33m○\x1b[0m";
const text = task.done ? `\x1b[90m${task.description}\x1b[0m` : task.description;
console.log(` ${status} ${index + 1}. ${text}`);
});
const done = tasks.filter((t) => t.done).length;
console.log(`\n ${done}/${tasks.length} completed\n`);
}node index.js listfunction completeTask(identifier) {
const tasks = loadTasks();
const index = parseInt(identifier, 10) - 1;
if (isNaN(index) || index < 0 || index >= tasks.length) {
console.error(`\x1b[31mError:\x1b[0m Invalid task number: ${identifier}`);
process.exit(1);
}
if (tasks[index].done) {
console.log(`Task ${identifier} is already completed.`);
return;
}
tasks[index].done = true;
tasks[index].completedAt = new Date().toISOString();
saveTasks(tasks);
console.log(`\x1b[32m✓\x1b[0m Completed: ${tasks[index].description}`);
}node index.js done 1
node index.js listfunction deleteTask(identifier) {
const tasks = loadTasks();
const index = parseInt(identifier, 10) - 1;
if (isNaN(index) || index < 0 || index >= tasks.length) {
console.error(`\x1b[31mError:\x1b[0m Invalid task number: ${identifier}`);
process.exit(1);
}
const removed = tasks.splice(index, 1)[0];
saveTasks(tasks);
console.log(`\x1b[31m✗\x1b[0m Deleted: ${removed.description}`);
}function showHelp() {
console.log(`
\x1b[1mtaskr\x1b[0m — A simple task manager
\x1b[33mUsage:\x1b[0m
taskr add <description> Add a new task
taskr list Show all tasks
taskr done <number> Mark a task as completed
taskr delete <number> Remove a task
taskr clear Remove all completed tasks
taskr help Show this help message
\x1b[33mExamples:\x1b[0m
taskr add "Buy groceries"
taskr done 1
taskr list
`);
}function clearCompleted() {
const tasks = loadTasks();
const remaining = tasks.filter((t) => !t.done);
const cleared = tasks.length - remaining.length;
saveTasks(remaining);
console.log(`\x1b[32m✓\x1b[0m Cleared ${cleared} completed task(s)`);
}npm linktaskr add "Test global install"
taskr list
taskr done 1
taskr clearnpm unlink -g taskr