<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ cli - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ cli - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Thu, 11 Jun 2026 23:14:42 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/cli/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ Learn Command Line Interface (CLI) Development with Dart: From Zero to a Fully Published Developer Tool ]]>
                </title>
                <description>
                    <![CDATA[ Most developers spend a significant portion of their day in the terminal. They run flutter build, push with git, manage packages with dart pub, and orchestrate pipelines from the command line. Every o ]]>
                </description>
                <link>https://www.freecodecamp.org/news/learn-command-line-interface-cli-development-with-dart-from-zero-to-a-fully-published-developer-tool/</link>
                <guid isPermaLink="false">69fe3149f239332df4fdfd46</guid>
                
                    <category>
                        <![CDATA[ Flutter ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Dart ]]>
                    </category>
                
                    <category>
                        <![CDATA[ cli ]]>
                    </category>
                
                    <category>
                        <![CDATA[ command line ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Mobile Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Software Engineering ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Oluwaseyi Fatunmole ]]>
                </dc:creator>
                <pubDate>Fri, 08 May 2026 18:54:01 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/a4c564c2-f5f3-4824-b4e7-d103b5fc488e.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Most developers spend a significant portion of their day in the terminal. They run <code>flutter build</code>, push with <code>git</code>, manage packages with <code>dart pub</code>, and orchestrate pipelines from the command line. Every one of those tools is a CLI, or command line interface: a program that lives in the terminal and responds to text commands.</p>
<p>Yet most developers have never built one.</p>
<p>That's a missed opportunity. CLI tools are one of the most practical things a developer can ship. They automate repetitive workflows, standardise processes across teams, and, when published, become tangible artifacts that the developer community can discover, install, and use.</p>
<p>In this handbook, you'll go from zero to building a fully distributed Dart CLI tool. We'll start with the fundamentals – how CLIs work, how Dart receives and processes terminal input, and the core syntax you need to know. Then we'll build three progressively complex CLIs, starting with the basics and finishing with a real-world API request runner. Finally, we will cover every distribution path available, from <code>pub.dev</code> to compiled binaries, Homebrew taps, Docker, and local team activation.</p>
<p>By the end of the guide, you'll understand both how to build a CLI tool in Dart as well as how to ship it so other developers can actually use it.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-what-is-a-cli-and-why-should-you-build-one">What is a CLI and Why Should You Build One?</a></p>
</li>
<li><p><a href="#heading-cli-syntax-anatomy">CLI Syntax Anatomy</a></p>
</li>
<li><p><a href="#heading-how-dart-receives-terminal-input">How Dart Receives Terminal Input</a></p>
</li>
<li><p><a href="#heading-core-cli-concepts-in-dart">Core CLI Concepts in Dart</a></p>
<ul>
<li><p><a href="#heading-stdout-stderr-and-stdin">stdout, stderr, and stdin</a></p>
</li>
<li><p><a href="#heading-exit-codes">Exit Codes</a></p>
</li>
<li><p><a href="#heading-environment-variables">Environment Variables</a></p>
</li>
<li><p><a href="#heading-file-and-directory-operations">File and Directory Operations</a></p>
</li>
<li><p><a href="#heading-running-external-processes">Running External Processes</a></p>
</li>
<li><p><a href="#heading-platform-detection">Platform Detection</a></p>
</li>
<li><p><a href="#heading-async-in-cli">Async in CLI</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-setting-up-your-dart-cli-project">Setting Up Your Dart CLI Project</a></p>
</li>
<li><p><a href="#heading-cli-1-hello-cli-the-fundamentals">CLI 1 — Hello CLI: The Fundamentals</a></p>
</li>
<li><p><a href="#heading-cli-2-darttodo-a-terminal-task-manager">CLI 2 — dart_todo: A Terminal Task Manager</a></p>
<ul>
<li><p><a href="#heading-introducing-the-args-package">Introducing the args Package</a></p>
</li>
<li><p><a href="#heading-building-darttodo">Building dart_todo</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-cli-3-darthttp-a-lightweight-api-request-runner">CLI 3 — dart_http: A Lightweight API Request Runner</a></p>
<ul>
<li><a href="#heading-building-darthttp">Building dart_http</a></li>
</ul>
</li>
<li><p><a href="#heading-adding-color-and-polish-to-your-cli">Adding Color and Polish to Your CLI</a></p>
</li>
<li><p><a href="#heading-testing-your-cli-tool">Testing Your CLI Tool</a></p>
</li>
<li><p><a href="#heading-deploying-and-distributing-your-cli">Deploying and Distributing Your CLI</a></p>
<ul>
<li><p><a href="#heading-mode-1-pubdev-public-package-distribution">Mode 1: pub.dev — Public Package Distribution</a></p>
</li>
<li><p><a href="#heading-mode-2-local-path-activation">Mode 2: Local Path Activation</a></p>
</li>
<li><p><a href="#heading-mode-3-compiled-binary-via-github-releases">Mode 3: Compiled Binary via GitHub Releases</a></p>
</li>
<li><p><a href="#heading-mode-4-homebrew-tap">Mode 4: Homebrew Tap</a></p>
</li>
<li><p><a href="#heading-mode-5-docker">Mode 5: Docker</a></p>
</li>
</ul>
</li>
<li><p><a href="#heading-choosing-the-right-distribution-mode">Choosing the Right Distribution Mode</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before starting, you should have:</p>
<ul>
<li><p>Dart SDK installed (<code>dart --version</code> should work in your terminal)</p>
</li>
<li><p>Basic familiarity with Dart syntax</p>
</li>
<li><p>Comfort with the terminal and running commands</p>
</li>
<li><p>A pub.dev account (for the publishing section)</p>
</li>
<li><p>A GitHub account (for the binary distribution section)</p>
</li>
</ul>
<h2 id="heading-what-is-a-cli-and-why-should-you-build-one">What is a CLI and Why Should You Build One?</h2>
<p>A CLI (or <strong>Command Line Interface</strong>) is a program you interact with entirely through text commands in a terminal, rather than through buttons and screens in a graphical interface.</p>
<p>Many of the tools you likely already rely on as a developer are CLI tools:</p>
<pre><code class="language-yaml">flutter build apk
git commit -m "fix: auth flow"
dart pub get
npm install
</code></pre>
<p>Flutter, Git, Dart, npm – all CLIs. You are already a CLI user every single day. This article is about becoming a CLI builder.</p>
<p>There are three strong reasons to build CLI tools as a developer:</p>
<ol>
<li><p><strong>Automating repetitive work:</strong> Anything you type more than twice a week is a candidate for automation. Generating boilerplate folder structures, running sequences of commands, scaffolding files, checking environments before a build a CLI turns a seven-step manual process into a single command.</p>
</li>
<li><p><strong>Standardising team workflows:</strong> Instead of a README that says "run these commands in this order," you ship one command that does all of it – consistently, every time, with no room for human error or a missed step.</p>
</li>
<li><p><strong>Building and publishing tooling.</strong> A published Dart CLI package is a tangible artifact. It shows up on pub.dev, gets installed and used by other developers, and communicates real engineering depth in a way that a portfolio or resume cannot.</p>
</li>
</ol>
<h2 id="heading-cli-syntax-anatomy">CLI Syntax Anatomy</h2>
<p>Before writing a single line of code, it helps to understand the structure of a CLI command. Every command follows a consistent pattern:</p>
<pre><code class="language-bash">tool [subcommand] [arguments] [options/flags]
</code></pre>
<p>Breaking down a real example:</p>
<pre><code class="language-bash">flutter build apk --release --obfuscate
│       │     │   │
tool    sub   arg  flags
</code></pre>
<ul>
<li><p><strong>Tool</strong> — the program itself (<code>flutter</code>, <code>dart</code>, <code>git</code>)</p>
</li>
<li><p><strong>Subcommand</strong> — the action being performed (<code>build</code>, <code>run</code>, <code>pub</code>)</p>
</li>
<li><p><strong>Arguments</strong> — what the action operates on (<code>apk</code>, <code>main.dart</code>, a filename)</p>
</li>
<li><p><strong>Flags and Options</strong> — modifiers that change behaviour</p>
</li>
</ul>
<p>There are two types of options:</p>
<pre><code class="language-plaintext">--release              # Boolean flag — either present or absent

--output=build/app     # Key-value option — name and a value
-v                     # Short flag — single hyphen, single character
</code></pre>
<p>This is the anatomy your CLIs will follow. Understanding it before writing any code means you will design your commands intentionally rather than stumbling into structure by accident.</p>
<h2 id="heading-how-dart-receives-terminal-input">How Dart Receives Terminal Input</h2>
<p>In Dart, everything the user types after your tool name is passed into your program through the <code>main</code> function:</p>
<pre><code class="language-dart">void main(List&lt;String&gt; args) {
  print(args);
}
</code></pre>
<p>Run it:</p>
<pre><code class="language-bash">dart run bin/mytool.dart hello world --name=Seyi
# [hello, world, --name=Seyi]
</code></pre>
<p>That <code>List&lt;String&gt; args</code> is just a list of strings. Each word or flag the user typed becomes an element in that list. Everything else you build on top of a CLI subcommands, flags, validation — is ultimately just processing this list.</p>
<h2 id="heading-core-cli-concepts-in-dart">Core CLI Concepts in Dart</h2>
<p>Before building anything, there's a set of foundational concepts that every CLI developer needs to understand. These are the building blocks that everything else sits on top of.</p>
<h3 id="heading-stdout-stderr-and-stdin">stdout, stderr, and stdin</h3>
<p>Most developers use <code>print()</code> for all output when they start building CLIs. That works for learning but it's incorrect in production.</p>
<p>There are two separate output streams in a terminal program:</p>
<ul>
<li><p><code>stdout</code> — regular output, meant for the user</p>
</li>
<li><p><code>stderr</code> — error output, meant for diagnostic messages and failures</p>
</li>
</ul>
<pre><code class="language-dart">import 'dart:io';

void main(List&lt;String&gt; args) {
  if (args.isEmpty) {
    stderr.writeln('Error: no arguments provided');
    exit(1);
  }

  stdout.writeln('Processing: ${args[0]}');
}
</code></pre>
<p>Keeping these separate matters because users can redirect stdout to a file without errors polluting it:</p>
<pre><code class="language-bash">dart run bin/tool.dart &gt; output.txt
# Errors still appear in the terminal
# Normal output goes cleanly to the file
</code></pre>
<p>Tools like <code>git</code>, <code>flutter</code>, and <code>curl</code> all do this correctly. Your CLI should too.</p>
<p><code>stdin</code> is the third stream — reading input from the user interactively at runtime:</p>
<pre><code class="language-dart">import 'dart:io';

void main() {
  stdout.write('Enter your name: ');
  final name = stdin.readLineSync();

  if (name == null || name.trim().isEmpty) {
    stderr.writeln('Error: no name provided');
    exit(1);
  }

  stdout.writeln('Hello, $name!');
}
</code></pre>
<p><code>stdout.write</code> (without <code>ln</code>) keeps the cursor on the same line so the user types right after the prompt. <code>stdin.readLineSync()</code> blocks until the user presses Enter and returns the typed string, or <code>null</code> if the stream closes unexpectedly. Always handle the null case.</p>
<h3 id="heading-exit-codes">Exit Codes</h3>
<p>Every program returns an exit code when it finishes. This is how the shell – and any script or CI system calling your tool – knows whether it succeeded or failed.</p>
<pre><code class="language-dart">import 'dart:io';

void main(List&lt;String&gt; args) {
  if (args.isEmpty) {
    stderr.writeln('Error: please provide an argument');
    exit(1); // failure
  }

  stdout.writeln('Done');
  exit(0); // success — also the default if you don't call exit()
}
</code></pre>
<p>The conventions are:</p>
<ul>
<li><p><code>0</code> — success</p>
</li>
<li><p><code>1</code> — general failure</p>
</li>
<li><p><code>2</code> — incorrect usage (wrong arguments, missing flags)</p>
</li>
</ul>
<p>Exit codes are critical when your CLI is called inside shell scripts or GitHub Actions workflows. A non-zero exit code stops a pipeline immediately. That's exactly the behaviour you want from a quality gate or a validation step.</p>
<h3 id="heading-environment-variables">Environment Variables</h3>
<p>Your CLI can read environment variables set in the user's shell:</p>
<pre><code class="language-dart">import 'dart:io';

void main() {
  final token = Platform.environment['API_TOKEN'];

  if (token == null) {
    stderr.writeln('Error: API_TOKEN environment variable is not set');
    exit(1);
  }

  stdout.writeln('Token found — proceeding...');
}
</code></pre>
<p>Set it in the terminal and run:</p>
<pre><code class="language-bash">export API_TOKEN=mytoken123
dart run bin/tool.dart
# Token found — proceeding...
</code></pre>
<p>This pattern is essential for CLI tools that interact with APIs, cloud services, or CI environments where credentials should never be hardcoded.</p>
<h3 id="heading-file-and-directory-operations">File and Directory Operations</h3>
<p>Many CLI tools read from or write to the file system. Dart's <code>dart:io</code> library covers everything you need:</p>
<pre><code class="language-dart">import 'dart:io';

void main(List&lt;String&gt; args) {
  if (args.isEmpty) {
    stderr.writeln('Usage: tool &lt;filename&gt;');
    exit(2);
  }

  final file = File(args[0]);

  if (!file.existsSync()) {
    stderr.writeln('Error: "${args[0]}" not found');
    exit(1);
  }

  final contents = file.readAsStringSync();
  stdout.writeln(contents);

  final output = File('output.txt');
  output.writeAsStringSync('Processed:\n$contents');
  stdout.writeln('Written to output.txt');
}
</code></pre>
<p>Working with directories:</p>
<pre><code class="language-dart">import 'dart:io';

void main() {
  // Where the command was run from
  final cwd = Directory.current.path;
  stdout.writeln('Working directory: $cwd');

  // Create a directory relative to current location
  final dir = Directory('$cwd/generated');

  if (!dir.existsSync()) {
    dir.createSync(recursive: true);
    stdout.writeln('Created: ${dir.path}');
  } else {
    stdout.writeln('Already exists: ${dir.path}');
  }
}
</code></pre>
<p>The <code>recursive: true</code> flag on <code>createSync</code> means it creates all intermediate directories — equivalent to <code>mkdir -p</code> in bash.</p>
<h3 id="heading-running-external-processes">Running External Processes</h3>
<p>One of the most powerful things a CLI can do is call other programs. Your Dart CLI can run <code>git</code>, <code>flutter</code>, <code>dart</code>, or any shell command programmatically:</p>
<pre><code class="language-dart">import 'dart:io';

void main() async {
  // Run a command and wait for it to finish
  final result = await Process.run('dart', ['pub', 'get']);

  stdout.write(result.stdout);

  if (result.exitCode != 0) {
    stderr.write(result.stderr);
    exit(result.exitCode);
  }

  stdout.writeln('Dependencies installed successfully');
}
</code></pre>
<p>For long-running commands where you want output to stream live as it happens:</p>
<pre><code class="language-dart">import 'dart:io';

void main() async {
  final process = await Process.start('flutter', ['build', 'apk']);

  // Pipe output directly to the terminal in real time
  process.stdout.pipe(stdout);
  process.stderr.pipe(stderr);

  final exitCode = await process.exitCode;
  exit(exitCode);
}
</code></pre>
<p><code>Process.run</code> — waits for completion, returns all output at once. Use for short commands.</p>
<p><code>Process.start</code> — streams output live as it arrives. Use for long-running commands where the user needs to see progress.</p>
<h3 id="heading-platform-detection">Platform Detection</h3>
<p>Sometimes your CLI needs to behave differently depending on the operating system it is running on:</p>
<pre><code class="language-dart">import 'dart:io';

void main() {
  if (Platform.isWindows) {
    stdout.writeln('Running on Windows');
  } else if (Platform.isMacOS) {
    stdout.writeln('Running on macOS');
  } else if (Platform.isLinux) {
    stdout.writeln('Running on Linux');
  }

  // Useful for path handling across operating systems
  stdout.writeln(Platform.pathSeparator); // \ on Windows, / elsewhere
  stdout.writeln(Platform.operatingSystem); // 'macos', 'linux', 'windows'
}
</code></pre>
<p>This matters when your CLI creates files, resolves paths, or calls shell commands that differ between operating systems.</p>
<h3 id="heading-async-in-cli">Async in CLI</h3>
<p>Dart CLIs support <code>async/await</code> natively. Any <code>main</code> function can be made async:</p>
<pre><code class="language-dart">import 'dart:io';

void main() async {
  stdout.writeln('Starting...');

  await Future.delayed(const Duration(seconds: 1)); // simulating async work

  stdout.writeln('Done');
}
</code></pre>
<p>Any operation involving file I/O, HTTP requests, or spawning processes will be asynchronous. Get comfortable with async <code>main</code> functions early — you'll use them constantly.</p>
<h2 id="heading-setting-up-your-dart-cli-project">Setting Up Your Dart CLI Project</h2>
<p>Create a new Dart console project:</p>
<pre><code class="language-bash">dart create -t console my_cli_tool
cd my_cli_tool
</code></pre>
<p>This generates a clean structure:</p>
<pre><code class="language-plaintext">my_cli_tool/
  bin/
    my_cli_tool.dart    ← entry point
  lib/                  ← shared library code
  test/                 ← tests
  pubspec.yaml
  README.md
</code></pre>
<p>The <code>bin/</code> directory is where your executable entry point lives. The <code>lib/</code> directory is where you put everything else — commands, utilities, models — that <code>bin/</code> imports and uses.</p>
<p>Open <code>pubspec.yaml</code>. You'll need to add an <code>executables</code> block before publishing:</p>
<pre><code class="language-yaml">name: my_cli_tool
description: A sample CLI tool built with Dart
version: 1.0.0

environment:
  sdk: '&gt;=3.0.0 &lt;4.0.0'

executables:
  my_cli_tool: my_cli_tool  # executable name: bin file name

dependencies:
  args: ^2.4.2

dev_dependencies:
  lints: ^3.0.0
  test: ^1.24.0
</code></pre>
<p>The <code>executables</code> block is what makes <code>dart pub global activate my_cli_tool</code> work. It tells Dart which script in <code>bin/</code> to expose as a runnable command after installation.</p>
<h2 id="heading-cli-1-hello-cli-the-fundamentals">CLI 1 — Hello CLI: The Fundamentals</h2>
<p>This first CLI uses pure Dart — no packages. The goal is to get comfortable with args, subcommands, input validation, and exit codes before introducing any external dependencies.</p>
<p>Replace the contents of <code>bin/my_cli_tool.dart</code>:</p>
<pre><code class="language-dart">import 'dart:io';

void main(List&lt;String&gt; args) {
  if (args.isEmpty) {
    printHelp();
    exit(0);
  }

  final command = args[0];

  switch (command) {
    case 'greet':
      handleGreet(args.sublist(1));
    case 'time':
      handleTime();
    case 'echo':
      handleEcho(args.sublist(1));
    case 'help':
      printHelp();
    default:
      stderr.writeln('Unknown command: "$command"');
      stderr.writeln('Run "mytool help" to see available commands.');
      exit(1);
  }
}

void handleGreet(List&lt;String&gt; args) {
  if (args.isEmpty) {
    stderr.writeln('Usage: mytool greet &lt;name&gt;');
    exit(2);
  }

  final name = args[0];
  stdout.writeln('Hello, $name! Welcome to your first Dart CLI.');
}

void handleTime() {
  final now = DateTime.now();
  stdout.writeln(
    'Current time: ${now.hour.toString().padLeft(2, '0')}:'
    '${now.minute.toString().padLeft(2, '0')}:'
    '${now.second.toString().padLeft(2, '0')}',
  );
}

void handleEcho(List&lt;String&gt; args) {
  if (args.isEmpty) {
    stderr.writeln('Usage: mytool echo &lt;message&gt;');
    exit(2);
  }

  stdout.writeln(args.join(' '));
}

void printHelp() {
  stdout.writeln('''
mytool — a simple Dart CLI

Usage:
  mytool &lt;command&gt; [arguments]

Commands:
  greet &lt;name&gt;      Greet someone by name
  time              Show the current time
  echo &lt;message&gt;    Echo a message back to the terminal
  help              Show this help message

Examples:
  mytool greet Seyi
  mytool echo "Hello from the terminal"
  mytool time
  ''');
}
</code></pre>
<p>Run it:</p>
<pre><code class="language-bash">dart run bin/my_cli_tool.dart help

dart run bin/my_cli_tool.dart greet Seyi
# Hello, Seyi! Welcome to your first Dart CLI.

dart run bin/my_cli_tool.dart time
# Current time: 14:32:10

dart run bin/my_cli_tool.dart echo "Dart CLIs are powerful"
# Dart CLIs are powerful

dart run bin/my_cli_tool.dart unknown
# Unknown command: "unknown"
# Run "mytool help" to see available commands.
</code></pre>
<p>Three things this CLI demonstrates that are worth internalising:</p>
<ol>
<li><p><strong>Subcommands are just a switch on</strong> <code>args[0]</code><strong>.</strong> The pattern is simple and scalable — add a new <code>case</code> to add a new command.</p>
</li>
<li><p><code>args.sublist(1)</code> <strong>passes remaining args to the handler.</strong> When <code>greet</code> receives <code>['greet', 'Seyi']</code>, it calls <code>handleGreet(['Seyi'])</code> — clean and isolated.</p>
</li>
<li><p><strong>Every error path has a message and a non-zero exit code.</strong> The user always knows what went wrong and what to do next.</p>
</li>
</ol>
<h2 id="heading-cli-2-darttodo-a-terminal-task-manager">CLI 2 — dart_todo: A Terminal Task Manager</h2>
<p>This CLI introduces the <code>args</code> package, JSON file persistence, and structured terminal output. It's meaningfully more complex than CLI 1 and reflects real patterns you will use in production tools.</p>
<h3 id="heading-introducing-the-args-package">Introducing the args Package</h3>
<p>Manually parsing <code>List&lt;String&gt; args</code> works for simple cases, but breaks down quickly when you add flags like <code>--priority=high</code>, boolean options like <code>--done</code>, or commands with multiple optional arguments.</p>
<p>The <code>args</code> package handles all of that cleanly.</p>
<p>Add it to your <code>pubspec.yaml</code>:</p>
<pre><code class="language-yaml">dependencies:
  args: ^2.4.2
</code></pre>
<p>Run:</p>
<pre><code class="language-bash">dart pub get
</code></pre>
<p>The core concept in <code>args</code> is the <code>ArgParser</code>. You define what your CLI accepts, and <code>args</code> handles parsing, validation, and generating help text automatically:</p>
<pre><code class="language-dart">import 'package:args/args.dart';

void main(List&lt;String&gt; arguments) {
  final parser = ArgParser()
    ..addCommand('add')
    ..addCommand('list')
    ..addFlag('help', abbr: 'h', negatable: false);

  final results = parser.parse(arguments);

  if (results['help'] as bool) {
    print(parser.usage);
    return;
  }
}
</code></pre>
<p>For more complex CLIs with subcommands that each have their own flags, use <code>ArgParser</code> per command:</p>
<pre><code class="language-dart">final parser = ArgParser();

final addCommand = ArgParser()
  ..addOption('priority', abbr: 'p', defaultsTo: 'normal');

parser.addCommand('add', addCommand);
</code></pre>
<h3 id="heading-building-darttodo">Building dart_todo</h3>
<p>Create a fresh project:</p>
<pre><code class="language-bash">dart create -t console dart_todo
cd dart_todo
</code></pre>
<p>Update <code>pubspec.yaml</code>:</p>
<pre><code class="language-yaml">name: dart_todo
description: A terminal task manager built with Dart
version: 1.0.0

environment:
  sdk: '&gt;=3.0.0 &lt;4.0.0'

executables:
  dart_todo: dart_todo

dependencies:
  args: ^2.4.2

dev_dependencies:
  lints: ^3.0.0
  test: ^1.24.0
</code></pre>
<p>Run <code>dart pub get</code>.</p>
<p>Create the folder structure:</p>
<pre><code class="language-plaintext">dart_todo/
  bin/
    dart_todo.dart
  lib/
    models/
      task.dart
    storage/
      task_storage.dart
    commands/
      add_command.dart
      list_command.dart
      complete_command.dart
      delete_command.dart
      clear_command.dart
  pubspec.yaml
</code></pre>
<h4 id="heading-step-1-the-task-model-libmodelstaskdart">Step 1 — The Task Model (<code>lib/models/task.dart</code>)</h4>
<pre><code class="language-dart">class Task {
  final int id;
  final String title;
  final String priority;
  final bool isComplete;
  final DateTime createdAt;

  Task({
    required this.id,
    required this.title,
    required this.priority,
    this.isComplete = false,
    required this.createdAt,
  });

  Task copyWith({bool? isComplete}) {
    return Task(
      id: id,
      title: title,
      priority: priority,
      isComplete: isComplete ?? this.isComplete,
      createdAt: createdAt,
    );
  }

  Map&lt;String, dynamic&gt; toJson() =&gt; {
        'id': id,
        'title': title,
        'priority': priority,
        'isComplete': isComplete,
        'createdAt': createdAt.toIso8601String(),
      };

  factory Task.fromJson(Map&lt;String, dynamic&gt; json) =&gt; Task(
        id: json['id'] as int,
        title: json['title'] as String,
        priority: json['priority'] as String,
        isComplete: json['isComplete'] as bool,
        createdAt: DateTime.parse(json['createdAt'] as String),
      );
}
</code></pre>
<h4 id="heading-step-2-storage-libstoragetaskstoragedart">Step 2 — Storage (<code>lib/storage/task_storage.dart</code>)</h4>
<p>This class handles reading and writing tasks to a local JSON file so they persist between CLI runs:</p>
<pre><code class="language-dart">import 'dart:convert';
import 'dart:io';

import '../models/task.dart';

class TaskStorage {
  static final _file = File(
    '${Platform.environment['HOME'] ?? Directory.current.path}/.dart_todo.json',
  );

  static List&lt;Task&gt; loadAll() {
    if (!_file.existsSync()) return [];

    try {
      final content = _file.readAsStringSync();
      final List&lt;dynamic&gt; json = jsonDecode(content) as List&lt;dynamic&gt;;
      return json
          .map((e) =&gt; Task.fromJson(e as Map&lt;String, dynamic&gt;))
          .toList();
    } catch (_) {
      return [];
    }
  }

  static void saveAll(List&lt;Task&gt; tasks) {
    final json = jsonEncode(tasks.map((t) =&gt; t.toJson()).toList());
    _file.writeAsStringSync(json);
  }
}
</code></pre>
<p>Tasks are stored in a hidden JSON file in the user's home directory — a common pattern for CLI tools that need lightweight local persistence.</p>
<h4 id="heading-step-3-commands">Step 3 — Commands</h4>
<p><code>lib/commands/add_command.dart</code>:</p>
<pre><code class="language-dart">import 'dart:io';

import '../models/task.dart';
import '../storage/task_storage.dart';

void runAdd(List&lt;String&gt; args, String priority) {
  if (args.isEmpty) {
    stderr.writeln('Usage: dart_todo add &lt;title&gt; [--priority=high|normal|low]');
    exit(2);
  }

  final title = args.join(' ');
  final tasks = TaskStorage.loadAll();

  final newTask = Task(
    id: tasks.isEmpty ? 1 : tasks.last.id + 1,
    title: title,
    priority: priority,
    createdAt: DateTime.now(),
  );

  tasks.add(newTask);
  TaskStorage.saveAll(tasks);

  stdout.writeln('Added task #\({newTask.id}: "\)title" [$priority]');
}
</code></pre>
<p><code>lib/commands/list_command.dart</code>:</p>
<pre><code class="language-cpp">import 'dart:io';

import '../storage/task_storage.dart';

void runList() {
  final tasks = TaskStorage.loadAll();

  if (tasks.isEmpty) {
    stdout.writeln('No tasks yet. Add one with: dart_todo add &lt;title&gt;');
    return;
  }

  stdout.writeln('');
  stdout.writeln('  ID   Status      Priority   Title');
  stdout.writeln('  ───  ──────────  ─────────  ────────────────────────');

  for (final task in tasks) {
    final status = task.isComplete ? 'done  ' : 'pending';
    final id = task.id.toString().padRight(4);
    final priority = task.priority.padRight(9);
    stdout.writeln('  \(id \)status  \(priority  \){task.title}');
  }

  stdout.writeln('');
}
</code></pre>
<p><code>lib/commands/complete_command.dart</code>:</p>
<pre><code class="language-dart">import 'dart:io';

import '../storage/task_storage.dart';

void runComplete(List&lt;String&gt; args) {
  if (args.isEmpty) {
    stderr.writeln('Usage: dart_todo complete &lt;id&gt;');
    exit(2);
  }

  final id = int.tryParse(args[0]);
  if (id == null) {
    stderr.writeln('Error: "${args[0]}" is not a valid task ID');
    exit(1);
  }

  final tasks = TaskStorage.loadAll();
  final index = tasks.indexWhere((t) =&gt; t.id == id);

  if (index == -1) {
    stderr.writeln('Error: No task found with ID $id');
    exit(1);
  }

  if (tasks[index].isComplete) {
    stdout.writeln('Task #$id is already complete.');
    return;
  }

  tasks[index] = tasks[index].copyWith(isComplete: true);
  TaskStorage.saveAll(tasks);

  stdout.writeln('Task #\(id marked as complete: "\){tasks[index].title}"');
}
</code></pre>
<p><code>lib/commands/delete_command.dart</code>:</p>
<pre><code class="language-dart">import 'dart:io';

import '../storage/task_storage.dart';

void runDelete(List&lt;String&gt; args) {
  if (args.isEmpty) {
    stderr.writeln('Usage: dart_todo delete &lt;id&gt;');
    exit(2);
  }

  final id = int.tryParse(args[0]);
  if (id == null) {
    stderr.writeln('Error: "${args[0]}" is not a valid task ID');
    exit(1);
  }

  final tasks = TaskStorage.loadAll();
  final index = tasks.indexWhere((t) =&gt; t.id == id);

  if (index == -1) {
    stderr.writeln('Error: No task found with ID $id');
    exit(1);
  }

  final title = tasks[index].title;
  tasks.removeAt(index);
  TaskStorage.saveAll(tasks);

  stdout.writeln('Deleted task #\(id: "\)title"');
}
</code></pre>
<p><code>lib/commands/clear_command.dart</code>:</p>
<pre><code class="language-dart">import 'dart:io';

import '../storage/task_storage.dart';

void runClear() {
  stdout.write('Are you sure you want to delete all tasks? (y/N): ');
  final input = stdin.readLineSync()?.trim().toLowerCase();

  if (input != 'y') {
    stdout.writeln('Cancelled.');
    return;
  }

  TaskStorage.saveAll([]);
  stdout.writeln('All tasks cleared.');
}
</code></pre>
<h4 id="heading-step-4-entry-point-bindarttododart">Step 4 — Entry Point (<code>bin/dart_todo.dart</code>)</h4>
<pre><code class="language-dart">import 'dart:io';

import 'package:args/args.dart';

import '../lib/commands/add_command.dart';
import '../lib/commands/clear_command.dart';
import '../lib/commands/complete_command.dart';
import '../lib/commands/delete_command.dart';
import '../lib/commands/list_command.dart';

void main(List&lt;String&gt; arguments) {
  final parser = ArgParser();

  // Add subcommand parsers
  final addParser = ArgParser()
    ..addOption(
      'priority',
      abbr: 'p',
      defaultsTo: 'normal',
      allowed: ['high', 'normal', 'low'],
      help: 'Task priority level',
    );

  parser
    ..addCommand('add', addParser)
    ..addCommand('list')
    ..addCommand('complete')
    ..addCommand('delete')
    ..addCommand('clear')
    ..addFlag('help', abbr: 'h', negatable: false, help: 'Show help');

  ArgResults results;

  try {
    results = parser.parse(arguments);
  } catch (e) {
    stderr.writeln('Error: $e');
    stderr.writeln(parser.usage);
    exit(2);
  }

  if (results['help'] as bool || results.command == null) {
    printHelp(parser);
    exit(0);
  }

  final command = results.command!;

  switch (command.name) {
    case 'add':
      runAdd(command.rest, command['priority'] as String);
    case 'list':
      runList();
    case 'complete':
      runComplete(command.rest);
    case 'delete':
      runDelete(command.rest);
    case 'clear':
      runClear();
    default:
      stderr.writeln('Unknown command: "${command.name}"');
      exit(1);
  }
}

void printHelp(ArgParser parser) {
  stdout.writeln('''
dart_todo — a terminal task manager

Usage:
  dart_todo &lt;command&gt; [arguments]

Commands:
  add &lt;title&gt;        Add a new task
    -p, --priority   Priority: high, normal, low (default: normal)
  list               List all tasks
  complete &lt;id&gt;      Mark a task as complete
  delete &lt;id&gt;        Delete a task
  clear              Delete all tasks

Examples:
  dart_todo add "Write the CLI article" --priority=high
  dart_todo list
  dart_todo complete 1
  dart_todo delete 2
  dart_todo clear
  ''');
}
</code></pre>
<p>Run it:</p>
<pre><code class="language-bash">dart run bin/dart_todo.dart add "Write the CLI article" --priority=high
# Added task #1: "Write the CLI article" [high]

dart run bin/dart_todo.dart add "Review PR comments"
# Added task #2: "Review PR comments" [normal]

dart run bin/dart_todo.dart list
#   ID   Status      Priority   Title
#   ───  ──────────  ─────────  ────────────────────────
#   1    ⬜ pending  high       Write the CLI article
#   2    ⬜ pending  normal     Review PR comments

dart run bin/dart_todo.dart complete 1
# Task #1 marked as complete: "Write the CLI article"

dart run bin/dart_todo.dart delete 2
# Deleted task #2: "Review PR comments"
</code></pre>
<p><code>dart_todo</code> demonstrates the patterns that form the backbone of almost every real CLI tool — argument parsing with <code>args</code>, JSON persistence, interactive prompts, structured output, and clean error handling across every command.</p>
<h2 id="heading-cli-3-darthttp-a-lightweight-api-request-runner">CLI 3 — dart_http: A Lightweight API Request Runner</h2>
<p>This is the most complex CLI in this article – and the most immediately useful. <code>dart_http</code> lets developers make HTTP requests directly from the terminal, with pretty-printed JSON responses, response metadata, header support, and the ability to save responses to a file.</p>
<pre><code class="language-bash">dart_http get https://jsonplaceholder.typicode.com/users/1
dart_http post https://jsonplaceholder.typicode.com/posts --body='{"title":"Hello"}'
dart_http get https://jsonplaceholder.typicode.com/users --save=users.json
dart_http get https://api.example.com/me --header="Authorization: Bearer mytoken"
</code></pre>
<h3 id="heading-building-darthttp">Building dart_http</h3>
<p>Create the project:</p>
<pre><code class="language-bash">dart create -t console dart_http
cd dart_http
</code></pre>
<p>Update <code>pubspec.yaml</code>:</p>
<pre><code class="language-yaml">name: dart_http
description: A lightweight API request runner for the terminal
version: 1.0.0

environment:
  sdk: '&gt;=3.0.0 &lt;4.0.0'

executables:
  dart_http: dart_http

dependencies:
  args: ^2.4.2
  http: ^1.2.1

dev_dependencies:
  lints: ^3.0.0
  test: ^1.24.0
</code></pre>
<p>Run <code>dart pub get</code>.</p>
<p>Project structure:</p>
<pre><code class="language-plaintext">dart_http/
  bin/
    dart_http.dart
  lib/
    runner/
      request_runner.dart
    printer/
      response_printer.dart
    utils/
      headers_parser.dart
  pubspec.yaml
</code></pre>
<h4 id="heading-step-1-headers-parser-libutilsheadersparserdart">Step 1 — Headers Parser (<code>lib/utils/headers_parser.dart</code>)</h4>
<pre><code class="language-dart">Map&lt;String, String&gt; parseHeaders(List&lt;String&gt; rawHeaders) {
  final headers = &lt;String, String&gt;{};

  for (final header in rawHeaders) {
    final index = header.indexOf(':');
    if (index == -1) continue;

    final key = header.substring(0, index).trim();
    final value = header.substring(index + 1).trim();
    headers[key] = value;
  }

  return headers;
}
</code></pre>
<h4 id="heading-step-2-response-printer-libprinterresponseprinterdart">Step 2 — Response Printer (<code>lib/printer/response_printer.dart</code>)</h4>
<pre><code class="language-dart">import 'dart:convert';
import 'dart:io';

void printResponse({
  required int statusCode,
  required String body,
  required int durationMs,
  required int bodyBytes,
}) {
  final statusLabel = _statusLabel(statusCode);
  final size = _formatSize(bodyBytes);

  stdout.writeln('');
  stdout.writeln('\(statusLabel | \){durationMs}ms | $size');
  stdout.writeln('─' * 50);

  try {
    final decoded = jsonDecode(body);
    const encoder = JsonEncoder.withIndent('  ');
    stdout.writeln(encoder.convert(decoded));
  } catch (_) {
    // Not JSON — print as plain text
    stdout.writeln(body);
  }

  stdout.writeln('');
}

String _statusLabel(int code) {
  if (code &gt;= 200 &amp;&amp; code &lt; 300) return '✅ $code';
  if (code &gt;= 300 &amp;&amp; code &lt; 400) return '↪️  $code';
  if (code &gt;= 400 &amp;&amp; code &lt; 500) return '❌ $code';
  return '$code';
}

String _formatSize(int bytes) {
  if (bytes &lt; 1024) return '${bytes}b';
  if (bytes &lt; 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)}kb';
  return '${(bytes / (1024 * 1024)).toStringAsFixed(1)}mb';
}
</code></pre>
<h4 id="heading-step-3-request-runner-librunnerrequestrunnerdart">Step 3 — Request Runner (<code>lib/runner/request_runner.dart</code>)</h4>
<pre><code class="language-dart">import 'dart:io';

import 'package:http/http.dart' as http;

import '../printer/response_printer.dart';

Future&lt;void&gt; runRequest({
  required String method,
  required String url,
  required Map&lt;String, String&gt; headers,
  String? body,
  String? saveToFile,
}) async {
  final uri = Uri.tryParse(url);

  if (uri == null) {
    stderr.writeln('Error: "$url" is not a valid URL');
    exit(1);
  }

  stdout.writeln('→ \({method.toUpperCase()} \)url');

  http.Response response;
  final stopwatch = Stopwatch()..start();

  try {
    switch (method.toLowerCase()) {
      case 'get':
        response = await http.get(uri, headers: headers);
      case 'post':
        response = await http.post(uri, headers: headers, body: body);
      case 'put':
        response = await http.put(uri, headers: headers, body: body);
      case 'patch':
        response = await http.patch(uri, headers: headers, body: body);
      case 'delete':
        response = await http.delete(uri, headers: headers);
      default:
        stderr.writeln('Error: unsupported method "$method"');
        exit(2);
    }
  } catch (e) {
    stderr.writeln('Error: request failed — $e');
    exit(1);
  }

  stopwatch.stop();

  printResponse(
    statusCode: response.statusCode,
    body: response.body,
    durationMs: stopwatch.elapsedMilliseconds,
    bodyBytes: response.bodyBytes.length,
  );

  if (saveToFile != null) {
    final file = File(saveToFile);
    file.writeAsStringSync(response.body);
    stdout.writeln('Response saved to $saveToFile');
  }
}
</code></pre>
<h4 id="heading-step-4-entry-point-bindarthttpdart">Step 4 — Entry Point (<code>bin/dart_http.dart</code>)</h4>
<pre><code class="language-dart">import 'dart:io';

import 'package:args/args.dart';

import '../lib/runner/request_runner.dart';
import '../lib/utils/headers_parser.dart';

void main(List&lt;String&gt; arguments) async {
  final parser = ArgParser();

  for (final method in ['get', 'post', 'put', 'patch', 'delete']) {
    final commandParser = ArgParser()
      ..addMultiOption('header', abbr: 'H', help: 'Request header (repeatable)')
      ..addOption('body', abbr: 'b', help: 'Request body (for POST/PUT/PATCH)')
      ..addOption('save', abbr: 's', help: 'Save response body to a file');

    parser.addCommand(method, commandParser);
  }

  parser.addFlag('help', abbr: 'h', negatable: false, help: 'Show help');

  ArgResults results;

  try {
    results = parser.parse(arguments);
  } catch (e) {
    stderr.writeln('Error: $e');
    printHelp();
    exit(2);
  }

  if (results['help'] as bool || results.command == null) {
    printHelp();
    exit(0);
  }

  final command = results.command!;
  final method = command.name!;
  final rest = command.rest;

  if (rest.isEmpty) {
    stderr.writeln('Error: please provide a URL');
    stderr.writeln('Usage: dart_http $method &lt;url&gt;');
    exit(2);
  }

  final url = rest[0];
  final rawHeaders = command['header'] as List&lt;String&gt;;
  final body = command['body'] as String?;
  final saveToFile = command['save'] as String?;

  final headers = parseHeaders(rawHeaders);

  // Default Content-Type for requests with a body
  if (body != null &amp;&amp; !headers.containsKey('Content-Type')) {
    headers['Content-Type'] = 'application/json';
  }

  await runRequest(
    method: method,
    url: url,
    headers: headers,
    body: body,
    saveToFile: saveToFile,
  );
}

void printHelp() {
  stdout.writeln('''
dart_http — a lightweight API request runner

Usage:
  dart_http &lt;method&gt; &lt;url&gt; [options]

Methods:
  get       Send a GET request
  post      Send a POST request
  put       Send a PUT request
  patch     Send a PATCH request
  delete    Send a DELETE request

Options:
  -H, --header    Add a request header (repeatable)
  -b, --body      Request body (JSON string)
  -s, --save      Save response body to a file
  -h, --help      Show this help message

Examples:
  dart_http get https://jsonplaceholder.typicode.com/users
  dart_http get https://api.example.com/me --header="Authorization: Bearer token"
  dart_http post https://api.example.com/posts --body=\'{"title":"Hello"}\'
  dart_http get https://api.example.com/users --save=users.json
  ''');
}
</code></pre>
<p>Run it:</p>
<pre><code class="language-bash">dart run bin/dart_http.dart get https://jsonplaceholder.typicode.com/users/1

# → GET https://jsonplaceholder.typicode.com/users/1
# 200 | 87ms | 510b
# ──────────────────────────────────────────────────
# {
#   "id": 1,
#   "name": "Leanne Graham",
#   "username": "Bret",
#   "email": "Sincere@april.biz"
# }

dart run bin/dart_http.dart get https://jsonplaceholder.typicode.com/users --save=users.json
# → GET https://jsonplaceholder.typicode.com/users
# 200 | 143ms | 5.3kb
# ──────────────────────────────────────────────────
# [ ... ]
# Response saved to users.json

dart run bin/dart_http.dart post https://jsonplaceholder.typicode.com/posts \
  --body='{"title":"Hello from dart_http","userId":1}'
# → POST https://jsonplaceholder.typicode.com/posts
# 201 | 312ms | 72b
</code></pre>
<h2 id="heading-adding-color-and-polish-to-your-cli">Adding Color and Polish to Your CLI</h2>
<p>The CLIs above are functional, but terminal output can be made significantly more readable with color. The <code>ansi_styles</code> package provides ANSI escape code support for coloring text in the terminal.</p>
<p>Add it to <code>pubspec.yaml</code>:</p>
<pre><code class="language-yaml">dependencies:
  ansi_styles: ^0.3.0
</code></pre>
<p>Using it:</p>
<pre><code class="language-dart">import 'package:ansi_styles/ansi_styles.dart';

stdout.writeln(AnsiStyles.green('✅ Success'));
stdout.writeln(AnsiStyles.red('❌ Error: something went wrong'));
stdout.writeln(AnsiStyles.yellow('⚠️  Warning: check your config'));
stdout.writeln(AnsiStyles.bold('dart_http — API request runner'));
stdout.writeln(AnsiStyles.cyan('→ GET https://api.example.com/users'));
</code></pre>
<p>Apply color intentionally and consistently:</p>
<ul>
<li><p><strong>Green</strong> — success states, completed operations</p>
</li>
<li><p><strong>Red</strong> — errors and failures</p>
</li>
<li><p><strong>Yellow</strong> — warnings and non-blocking issues</p>
</li>
<li><p><strong>Cyan</strong> — informational output, URLs, paths</p>
</li>
<li><p><strong>Bold</strong> — headers, tool names, important values</p>
</li>
</ul>
<p>Avoid coloring everything. Color loses meaning when it is everywhere. Use it to draw the user's eye to what actually matters.</p>
<h2 id="heading-testing-your-cli-tool">Testing Your CLI Tool</h2>
<p>CLI tools are testable, and they should be tested. The most reliable approach is to test the logic inside your commands directly — not the terminal output formatting, but the behaviour.</p>
<p>Add <code>test</code> to your dev dependencies if it's not already there:</p>
<pre><code class="language-yaml">dev_dependencies:
  test: ^1.24.0
</code></pre>
<p><strong>Testing command logic:</strong></p>
<pre><code class="language-dart">import 'package:test/test.dart';

import '../lib/models/task.dart';

void main() {
  group('Task model', () {
    test('copyWith updates isComplete correctly', () {
      final task = Task(
        id: 1,
        title: 'Write tests',
        priority: 'high',
        createdAt: DateTime.now(),
      );

      final completed = task.copyWith(isComplete: true);

      expect(completed.isComplete, isTrue);
      expect(completed.title, equals('Write tests'));
      expect(completed.id, equals(1));
    });

    test('toJson and fromJson round-trips correctly', () {
      final task = Task(
        id: 2,
        title: 'Ship the tool',
        priority: 'normal',
        createdAt: DateTime.parse('2025-01-01T00:00:00.000'),
      );

      final json = task.toJson();
      final restored = Task.fromJson(json);

      expect(restored.id, equals(task.id));
      expect(restored.title, equals(task.title));
      expect(restored.priority, equals(task.priority));
    });
  });
}
</code></pre>
<p><strong>Testing the headers parser:</strong></p>
<pre><code class="language-dart">import 'package:test/test.dart';

import '../lib/utils/headers_parser.dart';

void main() {
  group('parseHeaders', () {
    test('parses a single header correctly', () {
      final result = parseHeaders(['Authorization: Bearer mytoken']);
      expect(result['Authorization'], equals('Bearer mytoken'));
    });

    test('parses multiple headers', () {
      final result = parseHeaders([
        'Authorization: Bearer token',
        'Accept: application/json',
      ]);
      expect(result.length, equals(2));
      expect(result['Accept'], equals('application/json'));
    });

    test('ignores malformed headers without a colon', () {
      final result = parseHeaders(['malformed-header']);
      expect(result.isEmpty, isTrue);
    });
  });
}
</code></pre>
<p>Run your tests:</p>
<pre><code class="language-bash">dart test
</code></pre>
<h2 id="heading-deploying-and-distributing-your-cli">Deploying and Distributing Your CLI</h2>
<p>Building a CLI tool is half the work. Getting it into the hands of developers is the other half. There are five distribution paths available, each suited to a different use case.</p>
<h3 id="heading-mode-1-pubdev-public-package-distribution">Mode 1: pub.dev — Public Package Distribution</h3>
<p>Publishing to pub.dev makes your tool installable by anyone in the Dart and Flutter community with a single command.</p>
<h4 id="heading-prepare-your-package">Prepare your package:</h4>
<p>Your <code>pubspec.yaml</code> needs to be complete:</p>
<pre><code class="language-yaml">name: dart_http
description: A lightweight API request runner for Dart developers.
version: 1.0.0
homepage: https://github.com/yourname/dart_http

environment:
  sdk: '&gt;=3.0.0 &lt;4.0.0'

executables:
  dart_http: dart_http
</code></pre>
<p>The <code>executables</code> block is critical. It tells pub.dev which script in <code>bin/</code> to expose as a runnable command.</p>
<p>You also need:</p>
<ul>
<li><p><code>README.md</code> — what the tool does, how to install it, usage examples</p>
</li>
<li><p><code>CHANGELOG.md</code> — version history</p>
</li>
<li><p><code>LICENSE</code> — an open source license (MIT is standard)</p>
</li>
</ul>
<h4 id="heading-validate-before-publishing">Validate before publishing:</h4>
<pre><code class="language-bash">dart pub publish --dry-run
</code></pre>
<p>This runs all validation checks without actually publishing. Fix any warnings before proceeding.</p>
<h4 id="heading-publish">Publish:</h4>
<pre><code class="language-bash">dart pub publish
</code></pre>
<p>You will be prompted to authenticate with your pub.dev account. Once published, your tool is available globally:</p>
<pre><code class="language-bash">dart pub global activate dart_http
dart_http get https://api.example.com/users
</code></pre>
<h3 id="heading-mode-2-local-path-activation">Mode 2: Local Path Activation</h3>
<p>For internal team tools that you don't want to publish publicly, activate directly from a local or cloned repository:</p>
<pre><code class="language-bash">dart pub global activate --source path /path/to/dart_http
</code></pre>
<p>Any developer on the team clones the repo and runs this command once. The tool is then available globally in their terminal without needing a pub.dev publish.</p>
<p>This is the right distribution mode for:</p>
<ul>
<li><p>Internal company tooling</p>
</li>
<li><p>Tools that depend on private packages</p>
</li>
<li><p>Work-in-progress tools shared within a team before a public release</p>
</li>
</ul>
<h3 id="heading-mode-3-compiled-binary-via-github-releases">Mode 3: Compiled Binary via GitHub Releases</h3>
<p>Dart can compile to a self-contained native executable — no Dart SDK required on the target machine. This makes your tool accessible to developers outside the Dart ecosystem.</p>
<h4 id="heading-compile">Compile:</h4>
<pre><code class="language-bash"># macOS
dart compile exe bin/dart_http.dart -o dist/dart_http-macos

# Linux
dart compile exe bin/dart_http.dart -o dist/dart_http-linux

# Windows
dart compile exe bin/dart_http.dart -o dist/dart_http-windows.exe
</code></pre>
<p>The compiled binary is fully self-contained. Copy it to any machine and run it — no Dart installation needed.</p>
<h4 id="heading-automate-with-github-actions">Automate with GitHub Actions:</h4>
<p>Create <code>.github/workflows/release.yml</code>:</p>
<pre><code class="language-yaml">name: Release

on:
  push:
    tags:
      - 'v*'

jobs:
  build:
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
    runs-on: ${{ matrix.os }}

    steps:
      - uses: actions/checkout@v3

      - uses: dart-lang/setup-dart@v1
        with:
          sdk: stable

      - name: Install dependencies
        run: dart pub get

      - name: Compile binary
        run: |
          mkdir -p dist
          dart compile exe bin/dart_http.dart -o dist/dart_http-${{ runner.os }}

      - name: Upload binary to release
        uses: softprops/action-gh-release@v1
        with:
          files: dist/dart_http-${{ runner.os }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
</code></pre>
<p>Every time you push a version tag (<code>v1.0.0</code>), GitHub Actions compiles binaries for all three platforms and attaches them to the GitHub Release automatically.</p>
<h4 id="heading-write-an-install-script">Write an install script:</h4>
<pre><code class="language-bash">#!/usr/bin/env bash
set -euo pipefail

VERSION="1.0.0"
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
BINARY="dart_http-$OS"
INSTALL_DIR="/usr/local/bin"

curl -L "https://github.com/yourname/dart_http/releases/download/v\(VERSION/\)BINARY" \
  -o "$INSTALL_DIR/dart_http"

chmod +x "$INSTALL_DIR/dart_http"
echo "dart_http installed successfully"
</code></pre>
<p>Developers install it with:</p>
<pre><code class="language-bash">curl -fsSL https://raw.githubusercontent.com/yourname/dart_http/main/install.sh | bash
</code></pre>
<h3 id="heading-mode-4-homebrew-tap">Mode 4: Homebrew Tap</h3>
<p>Homebrew is the standard package manager for macOS and is widely used on Linux. A Homebrew tap makes your tool installable with <code>brew install</code> — the most familiar installation pattern for macOS developers.</p>
<h4 id="heading-create-your-tap-repository">Create your tap repository:</h4>
<p>Create a new GitHub repository named <code>homebrew-tools</code> (the <code>homebrew-</code> prefix is required by Homebrew's naming convention).</p>
<h4 id="heading-write-the-formula">Write the formula:</h4>
<p>Create <code>Formula/dart_http.rb</code> in that repository:</p>
<pre><code class="language-ruby">class DartHttp &lt; Formula
  desc "A lightweight API request runner for the terminal"
  homepage "https://github.com/yourname/dart_http"
  version "1.0.0"

  on_macos do
    url "https://github.com/yourname/dart_http/releases/download/v1.0.0/dart_http-macOS"
    sha256 "YOUR_SHA256_HASH_HERE"
  end

  on_linux do
    url "https://github.com/yourname/dart_http/releases/download/v1.0.0/dart_http-Linux"
    sha256 "YOUR_SHA256_HASH_HERE"
  end

  def install
    bin.install "dart_http-#{OS.mac? ? 'macOS' : 'Linux'}" =&gt; "dart_http"
  end

  test do
    system "#{bin}/dart_http", "--help"
  end
end
</code></pre>
<p>Generate the SHA256 hash for each binary:</p>
<pre><code class="language-bash">shasum -a 256 dist/dart_http-macOS
</code></pre>
<h4 id="heading-install-from-the-tap">Install from the tap:</h4>
<pre><code class="language-bash">brew tap yourname/tools
brew install dart_http
</code></pre>
<p>When you release a new version, update the <code>url</code> and <code>sha256</code> values in the formula and push the change. Users run <code>brew upgrade dart_http</code> to update.</p>
<h3 id="heading-mode-5-docker">Mode 5: Docker</h3>
<p>Docker distribution is best suited for CI environments, teams that standardise on containers, or tools with complex dependencies.</p>
<h4 id="heading-write-a-dockerfile">Write a Dockerfile:</h4>
<pre><code class="language-dockerfile">FROM dart:stable AS build

WORKDIR /app
COPY pubspec.* ./
RUN dart pub get

COPY . .
RUN dart compile exe bin/dart_http.dart -o /app/dart_http

FROM debian:stable-slim
COPY --from=build /app/dart_http /usr/local/bin/dart_http

ENTRYPOINT ["dart_http"]
</code></pre>
<p>This uses a multi-stage build: the first stage compiles the binary using the Dart SDK image, and the second stage copies only the binary into a minimal Debian image. The final image has no Dart SDK — just the compiled binary.</p>
<h4 id="heading-build-and-run">Build and run:</h4>
<pre><code class="language-bash">docker build -t dart_http .
docker run dart_http get https://jsonplaceholder.typicode.com/users/1
</code></pre>
<h4 id="heading-publish-to-docker-hub">Publish to Docker Hub:</h4>
<pre><code class="language-bash">docker tag dart_http yourname/dart_http:1.0.0
docker push yourname/dart_http:1.0.0
</code></pre>
<p>Users can then run your tool without installing anything locally:</p>
<pre><code class="language-bash">docker run yourname/dart_http get https://api.example.com/users
</code></pre>
<h2 id="heading-choosing-the-right-distribution-mode">Choosing the Right Distribution Mode</h2>
<table>
<thead>
<tr>
<th>Mode</th>
<th>Best for</th>
<th>Dart SDK required</th>
</tr>
</thead>
<tbody><tr>
<td>pub.dev</td>
<td>Public Dart/Flutter developer tools</td>
<td>Yes</td>
</tr>
<tr>
<td>Local path activation</td>
<td>Internal team tools, pre-release builds</td>
<td>Yes</td>
</tr>
<tr>
<td>Compiled binary</td>
<td>Language-agnostic tools, broad adoption</td>
<td>No</td>
</tr>
<tr>
<td>Homebrew tap</td>
<td>macOS/Linux developer tools</td>
<td>No</td>
</tr>
<tr>
<td>Docker</td>
<td>CI environments, complex dependencies</td>
<td>No</td>
</tr>
</tbody></table>
<p>For most tools, the practical recommendation is:</p>
<ul>
<li><p>Start with <strong>pub.dev</strong> if your audience is Dart developers</p>
</li>
<li><p>Add <strong>compiled binary + GitHub Releases</strong> once you want broader adoption</p>
</li>
<li><p>Add a <strong>Homebrew tap</strong> when macOS developers start asking for it</p>
</li>
<li><p>Use <strong>Docker</strong> only when it is already part of your team's workflow</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>You've gone from understanding what a CLI is to building three progressively complex tools and distributing them across five different channels.</p>
<p>The foundational skills – <code>args</code>, <code>stdin</code>, <code>stdout</code>, <code>stderr</code>, exit codes, file I/O, and process spawning – are the same building blocks that tools like <code>flutter</code>, <code>git</code>, and <code>dart</code> themselves are built on. Everything else is composition.</p>
<p>The three CLIs we built (Hello CLI, <code>dart_todo</code>, and <code>dart_http</code>) each introduced a new layer: raw Dart fundamentals, the <code>args</code> package with JSON persistence, and real-world HTTP interaction. The distribution section ensures that whatever you build next, you have a clear path to getting it in front of the developers who will use it.</p>
<p>Dart is a powerful language for CLI development. Its strong typing, async support, native compilation, and pub.dev ecosystem make it a serious choice for building developer tooling, not just mobile apps.</p>
<p>The next step is building something that solves a real problem for you or your team, and shipping it.</p>
<p>Happy coding!!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Get Information About Your Linux System Through the Command Line ]]>
                </title>
                <description>
                    <![CDATA[ Whether you’ve just gained access to a new Linux system, ethically hacked into one as part of a security test, or you’re just curious to know more about your current machine, this article will guide you through the process. You’ll learn how you can g... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/get-linux-system-info-through-cli/</link>
                <guid isPermaLink="false">68495738cb7b75f7a33a73c4</guid>
                
                    <category>
                        <![CDATA[ Linux ]]>
                    </category>
                
                    <category>
                        <![CDATA[ handbook ]]>
                    </category>
                
                    <category>
                        <![CDATA[ cli ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Zaira Hira ]]>
                </dc:creator>
                <pubDate>Wed, 11 Jun 2025 10:15:20 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1749636399891/4b457f71-2d18-463a-b98a-e19ff5a6b769.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Whether you’ve just gained access to a new Linux system, ethically hacked into one as part of a security test, or you’re just curious to know more about your current machine, this article will guide you through the process.</p>
<p>You’ll learn how you can get information related to your OS (operating system), kernel, CPU, memory, processes, disks, networks, and installed software. You’ll explore the commands and their outputs in detail.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-why-its-important-to-understand-your-linux-system">Why It's Important to Understand Your Linux System</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-get-your-os-amp-kernel-information-in-linux">How to Get Your OS &amp; Kernel Information in Linux</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-get-your-cpu-information-in-linux">How to Get Your CPU Information in Linux</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-get-your-memory-information-in-linux">How to Get Your Memory Information in Linux</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-get-your-disk-amp-filesystem-information-in-linux">How to Get Your Disk &amp; Filesystem Information in Linux</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-get-your-hardware-information-in-linux">How to Get Your Hardware Information in Linux</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-get-your-network-interfaces-amp-status-information-in-linux">How to Get Your Network Interfaces &amp; Status Information in Linux</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-get-your-software-amp-services-information-in-linux">How to Get Your Software &amp; Services Information in Linux</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-get-your-logs-amp-dmesg-in-formation-in-linux">How to Get Your Logs &amp; Dmesg In formation in Linux</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-get-your-securityuser-audit-information-in-linux">How to Get Your Security/User Audit Information in Linux</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-visually-appealing-commands">Visually Appealing Commands</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-why-its-important-to-understand-your-linux-system">Why It's Important to Understand Your Linux System</h2>
<h3 id="heading-system-administration">System Administration</h3>
<p>System administrators need to have an understanding of the system so they are able to:</p>
<ul>
<li><p>Manage users, groups, and permissions effectively.</p>
</li>
<li><p>Configure services like web servers, databases, and so on.</p>
</li>
<li><p>Automate repetitive tasks with scripts and cron jobs.</p>
</li>
</ul>
<h3 id="heading-troubleshooting">Troubleshooting</h3>
<p>When the system is in a problematic state, a solid understanding of the system specification and configuration helps you to:</p>
<ul>
<li><p>Identify and resolve system errors quickly.</p>
</li>
<li><p>Analyze system logs and monitor performance.</p>
</li>
<li><p>Diagnose network and hardware issues.</p>
</li>
</ul>
<h3 id="heading-security-auditing">Security Auditing</h3>
<p>If you are in a security related role, knowing your system in depth helps you to:</p>
<ul>
<li><p>Monitor logs for unauthorized access.</p>
</li>
<li><p>Configure firewalls and security policies.</p>
</li>
<li><p>Detect and remove malicious processes or software.</p>
</li>
</ul>
<h3 id="heading-performance-optimization">Performance Optimization</h3>
<p>If you know how to gather information related to system resources, you can measure them and create a projection for the future use. You can also:</p>
<ul>
<li><p>Tune system parameters for better efficiency.</p>
</li>
<li><p>Monitor resource usage (CPU, memory, disk, I/O).</p>
</li>
<li><p>Eliminate bottlenecks and optimize workloads.</p>
</li>
</ul>
<h3 id="heading-proactive-maintenance">Proactive Maintenance</h3>
<p>It is a good practice to be able to prevent issues before they occur. Once you know your system well, you can:</p>
<ul>
<li><p>Schedule regular updates and backups.</p>
</li>
<li><p>Ensure system reliability and uptime.</p>
</li>
</ul>
<p>Understanding your Linux system gives you greater control, enhances system stability, and improves your overall effectiveness as a system administrator or power user.</p>
<p>In the next section, we’ll discuss some essential commands for gathering system information.</p>
<h2 id="heading-how-to-get-your-os-amp-kernel-information-in-linux">How to Get Your OS &amp; Kernel Information in Linux</h2>
<h3 id="heading-uname-a-command"><code>uname -a</code> Command</h3>
<p><code>uname -a</code> provides full kernel information:</p>
<pre><code class="lang-bash">uname -a
Linux ip-172-31-90-178 6.8.0-1024-aws <span class="hljs-comment">#26-Ubuntu SMP Tue Feb 18 17:22:37 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux</span>
</code></pre>
<p>Here is what each part means in the above command:</p>
<ul>
<li><p><code>Linux</code>: The kernel name.</p>
</li>
<li><p><code>ip-172-31-90-178</code>: The network hostname of the system.</p>
</li>
<li><p><code>6.8.0-1024-aws</code>: The kernel version and AWS-specific build.</p>
</li>
<li><p><code>#26-Ubuntu</code>: The kernel build number.</p>
</li>
<li><p><code>SMP</code>: Symmetric Multi-Processing, indicating that the kernel is compiled for multiple processors.</p>
</li>
<li><p><code>Tue Feb 18 17:22:37 UTC 2025</code>: The date and time when the kernel was compiled.</p>
</li>
<li><p><code>x86_64 x86_64 x86_64</code>: The machine hardware name (architecture), processor type, and platform type, all indicating 64-bit x86 architecture.</p>
</li>
<li><p><code>GNU/Linux</code>: The operating system name.</p>
</li>
</ul>
<p>Based on this output, I’m running on an AWS EC2 instance with a 64-bit Ubuntu Linux distribution using a kernel that was specifically built for AWS infrastructure.</p>
<h3 id="heading-uname-r-and-uname-s-commands"><code>uname -r</code> and <code>uname -s</code> Commands</h3>
<p>The <code>uname -r</code> and <code>uname -s</code> commands specify the kernel version and OS type information:</p>
<pre><code class="lang-bash">uname -r
6.11.0-25-generic

uname -s
Linux
</code></pre>
<h3 id="heading-cat-etcos-release-command"><code>cat /etc/os-release</code> Command</h3>
<p>The <code>cat /etc/os-release</code> command provides distribution information:</p>
<pre><code class="lang-bash">cat /etc/os-release
PRETTY_NAME=<span class="hljs-string">"Ubuntu 24.04.2 LTS"</span>
NAME=<span class="hljs-string">"Ubuntu"</span>
VERSION_ID=<span class="hljs-string">"24.04"</span>
VERSION=<span class="hljs-string">"24.04.2 LTS (Noble Numbat)"</span>
VERSION_CODENAME=noble
ID=ubuntu
ID_LIKE=debian
HOME_URL=<span class="hljs-string">"https://www.ubuntu.com/"</span>
SUPPORT_URL=<span class="hljs-string">"https://help.ubuntu.com/"</span>
BUG_REPORT_URL=<span class="hljs-string">"https://bugs.launchpad.net/ubuntu/"</span>
PRIVACY_POLICY_URL=<span class="hljs-string">"https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"</span>
UBUNTU_CODENAME=noble
LOGO=ubuntu-logo
</code></pre>
<p>Here is what each part means in the above command:</p>
<ul>
<li><p><code>PRETTY_NAME="Ubuntu 24.04.2 LTS"</code>: The user-friendly name of the distribution including version and LTS (Long Term Support) designation.</p>
</li>
<li><p><code>NAME="Ubuntu"</code>: The name of the Linux distribution.</p>
</li>
<li><p><code>VERSION_ID="24.04"</code>: The version number of the Ubuntu release (Year/Month format).</p>
</li>
<li><p><code>VERSION="24.04.2 LTS (Noble Numbat)"</code>: The complete version information including:</p>
<p>  • <code>24.04</code>: Major version (released April 2024)</p>
<p>  • <code>.2</code>: Point release number</p>
<p>  • <code>LTS</code>: Long Term Support</p>
<p>  • <code>Noble Numbat</code>: The release codename</p>
</li>
<li><p><code>VERSION_CODENAME=noble</code>: The codename for this Ubuntu release ("Noble").</p>
</li>
<li><p><code>ID=ubuntu</code>: The machine-readable name of the operating system.</p>
</li>
<li><p><code>ID_LIKE=debian</code>: Indicates that Ubuntu is based on Debian Linux.</p>
</li>
<li><p><code>HOME_URL</code>, <code>SUPPORT_URL</code>, <code>BUG_REPORT_URL</code>, <code>PRIVACY_POLICY_URL</code> : Various official URLs for Ubuntu resources.</p>
</li>
<li><p><code>UBUNTU_CODENAME=noble</code>: Reiterates the codename of this Ubuntu release.</p>
</li>
<li><p><code>LOGO=ubuntu-logo</code>: Specifies the logo identifier for the distribution.</p>
</li>
</ul>
<p>This output shows that I’m running Ubuntu 24.04.2 LTS (codenamed "Noble Numbat"), which is a Long Term Support release of Ubuntu. Being an LTS version means it will receive security updates and support for an extended period (typically 5 years for Ubuntu LTS releases).</p>
<h3 id="heading-hostnamectl-command"><code>hostnamectl</code> Command</h3>
<p><code>hostnamectl</code> shows the hostname, OS, and kernel info:</p>
<pre><code class="lang-bash">hostnamectl
 Static hostname: ip-172-31-90-178
       Icon name: computer-vm
         Chassis: vm 🖴
      Machine ID: ec272830b6dca2da0d11e41b292cfc99
         Boot ID: dd12f48ff01b44a796991d99ce1bcfde
  Virtualization: xen
Operating System: Ubuntu 24.04.2 LTS              
          Kernel: Linux 6.8.0-1024-aws
    Architecture: x86-64
 Hardware Vendor: Xen
  Hardware Model: HVM domU
Firmware Version: 4.11.amazon
   Firmware Date: Thu 2006-08-24
    Firmware Age: 18y 9month 1w 2d
</code></pre>
<p>In the above command, here is what each part means:</p>
<ul>
<li><p><code>Static hostname: "ip-172-31-90-178"</code>: This is the permanent hostname of the system, stored in <code>/etc/hostname</code>.</p>
</li>
<li><p><code>Icon name: "computer-vm"</code>: A symbolic icon identifier for the system, used by some desktop environments.</p>
</li>
<li><p><code>Chassis: "vm"</code>: Indicates this is running in a virtual machine environment.</p>
</li>
<li><p><code>Machine ID: "ec272830b6dca2da0d11e41b292cfc99"</code>: A unique identifier for this system, stored in <code>/etc/machine-id</code>.</p>
</li>
<li><p><code>Boot ID: "dd12f48ff01b44a796991d99ce1bcfde"</code>: A unique identifier that changes with each system boot.</p>
</li>
<li><p><code>Virtualization: "xen"</code>: Shows that this system is running on Xen virtualization (common for AWS instances).</p>
</li>
<li><p><code>Operating System: "Ubuntu 24.04.2 LTS"</code>: The current OS distribution and version.</p>
</li>
<li><p><code>Kernel: "Linux 6.8.0-1024-aws"</code>: The current Linux kernel version, specifically an AWS-optimized kernel.</p>
</li>
<li><p><code>Architecture: "x86-64"</code>: The CPU architecture of the system.</p>
</li>
<li><p><code>Hardware Vendor: "Xen" Hardware Model: "HVM domU"</code>: Indicates this is a Xen HVM (Hardware Virtual Machine) domain user instance.</p>
</li>
<li><p>Firmware Details:</p>
<ul>
<li><p><code>Version: 4.11.amazon</code>: This is the version of the firmware/BIOS specifically customized for AWS environments.</p>
</li>
<li><p><code>Date: Thu 2006-08-24</code>: This is the release date of the firmware. The date might seem old (2006) but this is normal for AWS instances.</p>
</li>
<li><p><code>Age: 18y 9month 1w</code> : This shows how old the firmware is relative to the current date calculated from the firmware date (2006) to now (2025). While the firmware seems old, it is still maintained and secure.</p>
</li>
</ul>
</li>
</ul>
<p>This overall output shows that I’m running Ubuntu 24.04.2 LTS on an AWS EC2 instance using Xen virtualization. The system is using an AWS-optimized kernel and is configured as a HVM (Hardware Virtual Machine) instance.</p>
<h2 id="heading-how-to-get-your-cpu-information-in-linux">How to Get Your CPU Information in Linux</h2>
<h3 id="heading-lscpu-command"><code>lscpu</code> Command</h3>
<p><code>lscpu</code> shows CPU architecture, cores, threads, and virtualization information:</p>
<pre><code class="lang-bash">lscpu
Architecture:             x86_64
  CPU op-mode(s):         32-bit, 64-bit
  Address sizes:          46 bits physical, 48 bits virtual
  Byte Order:             Little Endian
CPU(s):                   1
  On-line CPU(s) list:    0
Vendor ID:                GenuineIntel
  Model name:             Intel(R) Xeon(R) CPU E5-2686 v4 @ 2
                          .30GHz
    CPU family:           6
    Model:                79
    Thread(s) per core:   1
    Core(s) per socket:   1
    Socket(s):            1
    Stepping:             1
    BogoMIPS:             4599.99
    Flags:                fpu vme de pse tsc msr pae mce cx8 
                          apic sep mtrr pge mca cmov pat pse3
                          6 clflush mmx fxsr sse sse2 ht sysc
                          all nx rdtscp lm constant_tsc rep_g
                          ood nopl xtopology cpuid tsc_known_
                          freq pni pclmulqdq ssse3 fma cx16 p
                          cid sse4_1 sse4_2 x2apic movbe popc
                          nt tsc_deadline_timer aes xsave avx
                           f16c rdrand hypervisor lahf_lm abm
                           pti fsgsbase bmi1 avx2 smep bmi2 e
                          rms invpcid xsaveopt
Virtualization features:  
  Hypervisor vendor:      Xen
  Virtualization <span class="hljs-built_in">type</span>:    full
Caches (sum of all):      
  L1d:                    32 KiB (1 instance)
  L1i:                    32 KiB (1 instance)
  L2:                     256 KiB (1 instance)
  L3:                     45 MiB (1 instance)
NUMA:                     
  NUMA node(s):           1
  NUMA node0 CPU(s):      0
Vulnerabilities:          
  Gather data sampling:   Not affected
  Itlb multihit:          KVM: Mitigation: VMX unsupported
  L1tf:                   Mitigation; PTE Inversion
  Mds:                    Vulnerable: Clear CPU buffers attem
                          pted, no microcode; SMT Host state 
                          unknown
  Meltdown:               Mitigation; PTI
  Mmio stale data:        Vulnerable: Clear CPU buffers attem
                          pted, no microcode; SMT Host state 
                          unknown
  Reg file data sampling: Not affected
  Retbleed:               Not affected
  Spec rstack overflow:   Not affected
  Spec store bypass:      Vulnerable
  Spectre v1:             Mitigation; usercopy/swapgs barrier
                          s and __user pointer sanitization
  Spectre v2:             Mitigation; Retpolines; STIBP disab
                          led; RSB filling; PBRSB-eIBRS Not a
                          ffected; BHI Retpoline
  Srbds:                  Not affected
  Tsx async abort:        Not affected
</code></pre>
<p>Here is a brief explanation of the output above:</p>
<p>1. Basic CPU Info</p>
<ul>
<li><p>Architecture: <code>x86_64</code> (64-bit)</p>
</li>
<li><p>CPU Model: Intel Xeon E5-2686 v4 (2.3 GHz)</p>
</li>
<li><p>Cores/Threads: 1 core, 1 thread (no Hyper-Threading)</p>
</li>
<li><p>Physical CPU (Socket): 1</p>
</li>
</ul>
<p>2. Performance &amp; Features</p>
<ul>
<li><p>Cache Sizes:</p>
<ul>
<li><p>L1: 32 KiB (data) + 32 KiB (instructions)</p>
</li>
<li><p>L2: 256 KiB</p>
</li>
<li><p>L3: 45 MiB (large, typical for Xeon)</p>
</li>
</ul>
</li>
<li><p>Flags: Supports AVX, AES, SSE4.1/4.2 (useful for encryption/vector ops).</p>
</li>
</ul>
<p>3. Virtualization</p>
<ul>
<li><p>Hypervisor: Running on Xen (full virtualization).</p>
</li>
<li><p>Virtualization Support: Yes (Intel VT-x).</p>
</li>
</ul>
<p>4. Security (Vulnerabilities)</p>
<ul>
<li><p>Meltdown/Spectre: Mostly mitigated (PTI, Retpolines).</p>
</li>
<li><p>MDS/MMIO: Vulnerable (no microcode fixes).</p>
</li>
<li><p>Spec Store Bypass: Vulnerable (no mitigation).</p>
</li>
</ul>
<p>5. NUMA (Memory)</p>
<ul>
<li>Single NUMA node (no multi-processor complexity).</li>
</ul>
<p>The output shows that my machine is a single-core Intel Xeon (in a virtualized/cloud environment) with large L3 cache but has some unpatched CPU vulnerabilities.</p>
<h3 id="heading-cat-proccpuinfo-command"><code>cat /proc/cpuinfo</code> Command</h3>
<p><code>cat /proc/cpuinfo</code> provides more in-depth details about the CPU:</p>
<pre><code class="lang-bash">cat /proc/cpuinfo 
processor    : 0
vendor_id    : GenuineIntel
cpu family    : 6
model        : 79
model name    : Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz
stepping    : 1
microcode    : 0xd000404
cpu MHz        : 2299.998
cache size    : 46080 KB
physical id    : 0
siblings    : 1
core id        : 0
cpu cores    : 1
apicid        : 0
initial apicid    : 0
fpu        : yes
fpu_exception    : yes
cpuid level    : 13
wp        : yes
flags        : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx rdtscp lm constant_tsc rep_good nopl xtopology cpuid tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm pti fsgsbase bmi1 avx2 smep bmi2 erms invpcid xsaveopt
bugs        : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs itlb_multihit mmio_stale_data bhi
bogomips    : 4599.99
clflush size    : 64
cache_alignment    : 64
address sizes    : 46 bits physical, 48 bits virtual
power management:
</code></pre>
<h3 id="heading-nproc-command"><code>nproc</code> Command</h3>
<p><code>nproc</code> shows the core count:</p>
<pre><code class="lang-bash">nproc
1
</code></pre>
<p>The above command output shows there is one available processor.</p>
<h2 id="heading-how-to-get-your-memory-information-in-linux">How to Get Your Memory Information in Linux</h2>
<h3 id="heading-free-h-command"><code>free -h</code> Command</h3>
<p>You can use the <code>free -h</code> command to know the total/used/free RAM:</p>
<pre><code class="lang-bash">free -h
               total        used        free      shared  buff/cache   available
Mem:           957Mi       406Mi       218Mi       920Ki       522Mi       551Mi
Swap:             0B          0B          0B
</code></pre>
<p>Here is a breakdown of the output shared above:</p>
<ul>
<li><p><code>total</code>: The total amount of physical memory (RAM) or swap space available on the system.</p>
</li>
<li><p><code>used</code>: The amount of memory currently being used by applications and the system. Calculated as: <code>total - free - buffers - cache</code>.</p>
</li>
<li><p><code>free</code>: The amount of memory that is completely unused.</p>
</li>
<li><p><code>shared</code>: Memory that may be simultaneously accessed by multiple programs.</p>
</li>
<li><p><code>buff/cache</code>: Combines two types of memory:</p>
<ul>
<li><p>Buffers: Memory used for block device I/O buffering.</p>
</li>
<li><p>Cache: Memory used for file system page cache - This memory can be reclaimed when needed by applications.</p>
</li>
<li><p><code>available</code>: It includes the 'free' memory plus memory that can be reclaimed from <code>buff/cache</code>. This is the most important column for determining if you have enough memory.</p>
</li>
</ul>
</li>
</ul>
<h3 id="heading-vmstat-command"><strong>vmstat</strong> Command</h3>
<p><code>vmstat</code> stands for Virtual Memory Statistics, a tool to monitor system performance. It provides information about memory usage, CPU activity, Processes, Disk I/O and Swap usage.</p>
<p>You can also use <code>vmstat</code> to extract live information. Here is how you can do that:</p>
<pre><code class="lang-bash">vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- -------cpu-------
 r  b   swpd   free   buff  cache   si   so    bi    bo   <span class="hljs-keyword">in</span>   cs us sy id wa st gu
 1  0      0 238264  46120 489056    0    0     3     8   23    0  0  0 82  0 18  0
 0  0      0 238264  46120 489060    0    0     0     0  240  120  0  1 98  0  1  0
 0  0      0 238264  46120 489060    0    0     0     0  239  124  0  0 98  0  2  0
 0  0      0 238264  46120 489060    0    0     0     0  199  101  0  0 95  0  5  0
 0  0      0 238264  46120 489060    0    0     0     0   36   25  0  0 78  0 22  0
</code></pre>
<p>Here is what the above command is doing:</p>
<ol>
<li><p>Captures 5 snapshots of system performance.</p>
</li>
<li><p>Each snapshot is taken 1 second apart, giving near real-time insights.</p>
</li>
<li><p>Displays key metrics about:</p>
<ul>
<li><p>Memory usage (free, buffered, cached).</p>
</li>
<li><p>CPU activity (user, system, idle, waiting).</p>
</li>
<li><p>Processes (running, blocked).</p>
</li>
<li><p>Disk I/O (blocks read/written).</p>
</li>
<li><p>Swap usage (if swapping is happening).</p>
</li>
</ul>
</li>
</ol>
<p>Note that, you can replace the interval and number of snapshots accordingly.</p>
<p>Here’s a detailed breakdown of the output above:</p>
<ul>
<li><p><code>Procs</code>:</p>
<ul>
<li><p><code>r</code>: Number of processes waiting for run time.</p>
</li>
<li><p><code>b</code>: Number of processes in uninterruptible sleep</p>
</li>
</ul>
</li>
<li><p><code>Memory</code> (in KB):</p>
<ul>
<li><p><code>swpd</code>: Amount of virtual memory used</p>
</li>
<li><p><code>free</code>: Amount of idle memory</p>
</li>
<li><p><code>buff</code>: Memory used as buffers</p>
</li>
<li><p><code>cache</code>: Memory used as cache</p>
</li>
</ul>
</li>
<li><p><code>Swap</code>:</p>
<ul>
<li><p><code>si</code>: Memory swapped in from disk (KB/s)</p>
</li>
<li><p><code>so</code>: Memory swapped out to disk (KB/s)</p>
</li>
</ul>
</li>
<li><p><code>IO</code>:</p>
<ul>
<li><p><code>bi</code>: Blocks received from a block device (blocks/s)</p>
</li>
<li><p><code>bo</code>: Blocks sent to a block device (blocks/s)</p>
</li>
</ul>
</li>
<li><p><code>System</code>:</p>
<ul>
<li><p><code>in</code>: Number of interrupts per second</p>
</li>
<li><p><code>cs</code>: Number of context switches per second</p>
</li>
</ul>
</li>
<li><p><code>CPU</code> (percentages):</p>
<ol>
<li><p><code>us</code>: Time spent running user code</p>
</li>
<li><p><code>sy</code>: Time spent running system code</p>
</li>
<li><p><code>id</code>: Time spent idle</p>
</li>
<li><p><code>wa</code>: Time spent waiting for IO</p>
</li>
<li><p><code>st</code>: Time stolen from a virtual machine</p>
</li>
<li><p><code>gu</code>: Time running guest code (virtual CPU)</p>
</li>
</ol>
</li>
</ul>
<p>From the output, you can see that my system:</p>
<ul>
<li><p>Has very low CPU usage (high idle percentage)</p>
</li>
<li><p>Has no swap being used (<code>swpd = 0</code>)</p>
</li>
<li><p>Has about <code>99MB</code> free memory</p>
</li>
<li><p>Shows minimal IO activity</p>
</li>
<li><p>Is running in a virtualized environment (notice the <code>st</code> (stolen) time column has non-zero value</p>
</li>
</ul>
<p>The first line shows averages since the last reboot, while subsequent lines show the real-time statistics for each second.</p>
<h3 id="heading-cat-procmeminfo-command"><code>cat /proc/meminfo</code> Command</h3>
<p><code>cat /proc/meminfo</code> shows detailed memory stats:</p>
<pre><code class="lang-bash">cat /proc/meminfo
MemTotal:         980384 kB
MemFree:          245100 kB
MemAvailable:     585896 kB
Buffers:           46184 kB
Cached:           393672 kB
SwapCached:            0 kB
Active:           141404 kB
Inactive:         356376 kB
Active(anon):      47672 kB
Inactive(anon):    29300 kB
Active(file):      93732 kB
Inactive(file):   327076 kB
Unevictable:       36528 kB
Mlocked:           27152 kB
SwapTotal:             0 kB
SwapFree:              0 kB
Zswap:                 0 kB
Zswapped:              0 kB
Dirty:                 0 kB
Writeback:             0 kB
AnonPages:         94488 kB
Mapped:            97936 kB
Shmem:               920 kB
KReclaimable:      95396 kB
Slab:             148672 kB
SReclaimable:      95396 kB
SUnreclaim:        53276 kB
KernelStack:        2444 kB
PageTables:         3224 kB
SecPageTables:         0 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:      490192 kB
Committed_AS:     508912 kB
VmallocTotal:   34359738367 kB
VmallocUsed:        9988 kB
VmallocChunk:          0 kB
Percpu:            14848 kB
HardwareCorrupted:     0 kB
AnonHugePages:         0 kB
ShmemHugePages:        0 kB
ShmemPmdMapped:        0 kB
FileHugePages:         0 kB
FilePmdMapped:         0 kB
Unaccepted:            0 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
Hugetlb:               0 kB
DirectMap4k:       71680 kB
DirectMap2M:      976896 kB
</code></pre>
<p>Here is a detailed breakdown of the output shared above:</p>
<ul>
<li><p>Total Memory and Available Memory:</p>
<ul>
<li><p><code>MemTotal</code>: Total physical RAM available.</p>
</li>
<li><p><code>MemFree</code>: Completely unused memory.</p>
</li>
<li><p><code>MemAvailable</code>: Memory available for new applications.</p>
</li>
</ul>
</li>
<li><p>Memory Caches and Buffers:</p>
<ul>
<li><p><code>Buffers</code>: Memory used for block device I/O buffering.</p>
</li>
<li><p><code>Cached</code>: Memory used for file system cache.</p>
</li>
<li><p><code>SwapCached</code>: Memory pages stored in both RAM and swap.</p>
</li>
</ul>
</li>
<li><p>Active vs Inactive Memory:</p>
<ul>
<li><p><code>Active</code>: Recently used memory.</p>
</li>
<li><p><code>Inactive</code>: Less recently used memory.</p>
</li>
<li><p><code>Active(anon)</code>: Recently used anonymous memory.</p>
</li>
<li><p><code>Active(file)</code>: Recently used file-backed memory.</p>
</li>
</ul>
</li>
<li><p>Swap Information:</p>
<ul>
<li><p><code>SwapTotal</code>: Swap space configured.</p>
</li>
<li><p><code>SwapFree</code>: Swap space available.</p>
</li>
<li><p><code>Zswap</code>: Compressed swap in RAM.</p>
</li>
</ul>
</li>
<li><p>Other Important Metrics:</p>
<ul>
<li><p><code>Dirty</code>: Memory waiting to be written to disk.</p>
</li>
<li><p><code>Mapped</code>: Files mapped into memory.</p>
</li>
<li><p><code>Slab</code>: Kernel data structures cache.</p>
</li>
<li><p><code>CommitLimit</code>: Total memory available for allocation.</p>
</li>
<li><p><code>Committed_AS</code>: Total memory currently allocated.</p>
</li>
</ul>
</li>
</ul>
<p>A healthy memory usage is indicated by a good amount of available memory, active caching mechanisms in place and no memory pressure (no swap usage needed).</p>
<h2 id="heading-how-to-get-your-disk-amp-filesystem-information-in-linux">How to Get Your Disk &amp; Filesystem Information in Linux</h2>
<h3 id="heading-tree-d-l-1-command"><code>tree -d -L 1</code> Command</h3>
<p><code>tree -d -L 1</code> shows the file system details from the folder it is executed in. To find the complete file system details, run it from the root <code>/</code> folder:</p>
<pre><code class="lang-bash">tree -d -L 1
.
├── bin -&gt; usr/bin
├── bin.usr-is-merged
├── boot
├── dev
├── etc
├── home
├── lib -&gt; usr/lib
├── lib.usr-is-merged
├── lib64 -&gt; usr/lib64
├── lost+found
├── media
├── mnt
├── opt
├── proc
├── root
├── run
├── sbin -&gt; usr/sbin
├── sbin.usr-is-merged
├── snap
├── srv
├── sys
├── tmp
├── usr
└── var

25 directories
</code></pre>
<p>The command output of <code>tree -d -L 1</code> shows a directory tree structure with the following options:</p>
<ul>
<li><p><code>-d</code>: Shows only directories (ignores files)</p>
</li>
<li><p><code>-L 1</code>: Limits the depth of the tree to one level (only shows the immediate subdirectories)</p>
</li>
<li><p><code>df -h</code>: mounted filesystems and usage:</p>
<pre><code class="lang-bash">  df -h
  Filesystem      Size  Used Avail Use% Mounted on
  /dev/root        29G  2.6G   26G   9% /
  tmpfs           479M     0  479M   0% /dev/shm
  tmpfs           192M  908K  191M   1% /run
  tmpfs           5.0M     0  5.0M   0% /run/lock
  /dev/xvda16     881M  144M  676M  18% /boot
  /dev/xvda15     105M  6.1M   99M   6% /boot/efi
  tmpfs            96M   12K   96M   1% /run/user/1000
</code></pre>
<p>  The above output from the <code>df -h</code> command shows the following disk space usage information:</p>
<ul>
<li><p><code>Filesystem</code>: The name of the mounted filesystem/device.</p>
</li>
<li><p><code>Size</code>: Total size of the filesystem.</p>
</li>
<li><p><code>Used</code>: Amount of space used.</p>
</li>
<li><p><code>Avail</code>: Amount of space available.</p>
</li>
<li><p><code>Use%</code>: Percentage of space used.</p>
</li>
<li><p><code>Mounted on</code>: The mount point where the filesystem is attached</p>
</li>
</ul>
</li>
</ul>
<h3 id="heading-lsblk-command"><code>lsblk</code> Command</h3>
<p><code>lsblk</code> stands for ‘list block devices’ and shows information about all available block devices like hard drives, SSDs, and so on.</p>
<pre><code class="lang-bash">lsblk
NAME     MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
loop0      7:0    0 26.3M  1 loop /snap/amazon-ssm-agent/9881
loop1      7:1    0 73.9M  1 loop /snap/core22/1748
loop2      7:2    0 44.4M  1 loop /snap/snapd/23545
loop3      7:3    0 50.9M  1 loop /snap/snapd/24505
loop4      7:4    0 73.9M  1 loop /snap/core22/1963
loop5      7:5    0 27.2M  1 loop /snap/amazon-ssm-agent/11320
xvda     202:0    0   30G  0 disk 
├─xvda1  202:1    0   29G  0 part /
├─xvda14 202:14   0    4M  0 part 
├─xvda15 202:15   0  106M  0 part /boot/efi
└─xvda16 259:0    0  913M  0 part /boot
</code></pre>
<p>The output above shows the following details:</p>
<ul>
<li><p><code>NAME</code>: Device name.</p>
</li>
<li><p><code>MAJ:MIN</code>: Major and minor device numbers.</p>
</li>
<li><p><code>RM</code>: Removable flag (1 for removable, 0 for fixed).</p>
</li>
<li><p><code>SIZE</code>: Device size.</p>
</li>
<li><p><code>RO</code>: Read-only flag (1 for read-only, 0 for read-write).</p>
</li>
<li><p><code>TYPE</code>: Device type (disk, part for partition, loop for loop device).</p>
</li>
<li><p><code>MOUNTPOINTS</code>: Where the device is mounted.</p>
</li>
</ul>
<h3 id="heading-fdisk-l-command"><code>fdisk -l</code> Command</h3>
<p><code>fdisk -l</code> shows all disk devices and their partitions on your system:</p>
<pre><code class="lang-bash">Disk /dev/xvda: 30 GiB, 32212254720 bytes, 62914560 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel <span class="hljs-built_in">type</span>: gpt
Disk identifier: E3478E01-32E3-4FC2-8E79-1BCCDE89C2D7

Device        Start      End  Sectors  Size Type
/dev/xvda1  2099200 62914526 60815327   29G Linux filesystem
/dev/xvda14    2048    10239     8192    4M BIOS boot
/dev/xvda15   10240   227327   217088  106M EFI System
/dev/xvda16  227328  2097152  1869825  913M Linux extended boot
</code></pre>
<p>The above output shows the partition information for the the main system disk (<code>/dev/xvda</code>) which is 30 GiB in size and has four partitions:</p>
<ul>
<li><p><code>/dev/xvda1</code>: <code>29G</code> Linux filesystem (main system partition).</p>
</li>
<li><p><code>/dev/xvda14</code>: <code>4M</code> BIOS boot partition.</p>
</li>
<li><p><code>/dev/xvda15</code>: <code>106M</code> EFI System partition (for UEFI boot).</p>
</li>
<li><p><code>/dev/xvda16</code>: <code>913M</code> Linux extended boot partition.</p>
</li>
</ul>
<h3 id="heading-mount-command"><code>mount</code> Command</h3>
<p><code>mount</code> shows all currently mounted filesystems in the format: <code>device/source "on" mount_point "type" filesystem_type (mount_options)</code>, displaying where and how each filesystem is attached to your system's directory tree.</p>
<p>Here is an example line from the output of <code>mount</code>:</p>
<pre><code class="lang-bash">/dev/xvda1 on / <span class="hljs-built_in">type</span> ext4 (rw,relatime,discard,errors=remount-ro,commit=30)
</code></pre>
<p>Some common mount options you’ll see are:</p>
<ul>
<li><p><code>rw</code>: Read-write access.</p>
</li>
<li><p><code>ro</code>: Read-only access.</p>
</li>
<li><p><code>nosuid</code>: Disable SUID/SGID bits.</p>
</li>
<li><p><code>nodev</code>: Prevent device file interpretation.</p>
</li>
<li><p><code>noexec</code>: Prevent execution of binaries.</p>
</li>
<li><p><code>relatime</code>: Update access times relatively.</p>
</li>
</ul>
<h3 id="heading-du-sh-command"><code>du -sh *</code> Command</h3>
<p><code>du -sh *</code> provides a summary of the disk usage for each file and directory in the current directory (good for finding disk hogs):</p>
<pre><code class="lang-bash">du -sh *
4.0K    file1.txt
8.0K    file2.txt
12K     directory1
20K     directory2
</code></pre>
<h2 id="heading-how-to-get-your-hardware-information-in-linux">How to Get Your Hardware Information in Linux</h2>
<h3 id="heading-lshw-command"><code>lshw</code> Command</h3>
<p>The <code>lshw</code> command provides detailed information about the computer's hardware configuration. It can report:</p>
<ul>
<li><p>Memory configuration.</p>
</li>
<li><p>Firmware version.</p>
</li>
<li><p>Mainboard configuration.</p>
</li>
<li><p>CPU version and speed.</p>
</li>
<li><p>Cache configuration.</p>
</li>
<li><p>Bus speed and more.</p>
</li>
</ul>
<p>It's particularly useful for system administrators and users who need to gather detailed hardware information. The command can output information in various formats including HTML, XML, JSON, or plain text.</p>
<p>Here is a portion of the output from <code>lshw</code>:</p>
<pre><code class="lang-bash">*-pci
          description: Host bridge
          product: 440FX - 82441FX PMC [Natoma]
          vendor: Intel Corporation
          physical id: 100
          bus info: pci@0000:00:00.0
          version: 02
          width: 32 bits
          clock: 33MHz
        *-isa
             description: ISA bridge
             product: 82371SB PIIX3 ISA [Natoma/Triton II]
             vendor: Intel Corporation
             physical id: 1
             bus info: pci@0000:00:01.0
             version: 00
             width: 32 bits
             clock: 33MHz
             capabilities: isa bus_master
             configuration: latency=0
</code></pre>
<h3 id="heading-lspci-command"><code>lspci</code> Command</h3>
<p><code>lspci</code> displays information about all PCI (Peripheral Component Interconnect) buses and devices connected to your system.</p>
<pre><code class="lang-bash">lspci
00:00.0 Host bridge: Intel Corporation 440FX - 82441FX PMC [Natoma] (rev 02)
00:01.0 ISA bridge: Intel Corporation 82371SB PIIX3 ISA [Natoma/Triton II]
00:01.1 IDE interface: Intel Corporation 82371SB PIIX3 IDE [Natoma/Triton II]
00:01.3 Bridge: Intel Corporation 82371AB/EB/MB PIIX4 ACPI (rev 01)
00:02.0 VGA compatible controller: Cirrus Logic GD 5446
00:03.0 Unassigned class [ff80]: XenSource, Inc. Xen Platform Device (rev 01)
</code></pre>
<p>From the output, we can see that:</p>
<ul>
<li><p>Each line starts with a <code>bus:device.function</code> address (like "<code>00:00.0</code>")</p>
</li>
<li><p>Following the address is the device class and the specific hardware details:</p>
<ul>
<li><p>A Host bridge (<code>Intel 440FX)</code>, which manages communications between the CPU and other components.</p>
</li>
<li><p>An ISA bridge (<code>Intel PIIX3</code>), for legacy device support.</p>
</li>
<li><p>An IDE interface for storage devices.</p>
</li>
<li><p>An ACPI bridge for power management.</p>
</li>
<li><p>A VGA graphics controller (Cirrus Logic).</p>
</li>
<li><p>A Xen Platform Device (this suggests you're running in a Xen virtualized environment).</p>
</li>
</ul>
</li>
</ul>
<p>The command is particularly useful for:</p>
<ul>
<li><p>Troubleshooting hardware issues</p>
</li>
<li><p>Verifying hardware detection</p>
</li>
<li><p>Finding hardware details for driver installation</p>
</li>
<li><p>Checking system configuration</p>
</li>
</ul>
<h2 id="heading-how-to-get-your-network-interfaces-amp-status-information-in-linux">How to Get Your Network Interfaces &amp; Status Information in Linux</h2>
<h3 id="heading-ip-a-command"><code>ip a</code> Command</h3>
<p><code>ip a</code> displays information about all network interfaces on your system:</p>
<pre><code class="lang-bash">ip -a
1: lo: &lt;LOOPBACK,UP,LOWER_UP&gt;
- This is the loopback interface (localhost)
- MTU (Maximum Transmission Unit) is 65536 bytes
- IP address: 127.0.0.1/8 (IPv4)
- IPv6 address: ::1/128

2. Network Interface (enX0):
enX0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt;
- This is your main network interface
- MTU is 9001 bytes
- MAC address (link/ether): 12:16:a6:d3:b3:61
- IPv4 address: 172.31.90.178/20
- IPv6 address: fe80::1016:a6ff:fed3:b361/64 (Link-local)
</code></pre>
<p>Here are the key elements in the output:</p>
<ul>
<li><p>Interface state (UP/DOWN).</p>
</li>
<li><p>MAC address (link/ether).</p>
</li>
<li><p>IPv4 and IPv6 addresses.</p>
</li>
<li><p>Network scope (host, global, link).</p>
</li>
<li><p>Address validity lifetime (valid_lft).</p>
</li>
<li><p>Broadcast address (brd).</p>
</li>
</ul>
<h3 id="heading-ip-r-command"><code>ip r</code> Command</h3>
<p><code>ip r</code> shows the system’s routing table:</p>
<pre><code class="lang-bash">ip r
default via 172.31.80.1 dev enX0 proto dhcp src 172.31.90.178 metric 100 
172.31.0.2 via 172.31.80.1 dev enX0 proto dhcp src 172.31.90.178 metric 100 
172.31.80.0/20 dev enX0 proto kernel scope link src 172.31.90.178 metric 100 
172.31.80.1 dev enX0 proto dhcp scope link src 172.31.90.178 metric 100
</code></pre>
<p>The above <code>ip r</code> output shows my system's routing table with the following routes:</p>
<ul>
<li><p>Default Route (Gateway):</p>
<ul>
<li><p>Default via <code>172.31.80.1</code>: All traffic not matching other rules goes through this gateway.</p>
</li>
<li><p>Using interface <code>enX0</code>.</p>
</li>
<li><p>Configured via DHCP.</p>
</li>
<li><p>Source IP: <code>172.31.90.178</code>.</p>
</li>
</ul>
</li>
<li><p>Local Network:</p>
<ul>
<li><p><code>172.31.80.0/20</code>: Local subnet (covers IPs from <code>172.31.80.0</code> to <code>172.31.95.255</code>)</p>
</li>
<li><p>Directly connected to <code>enX0</code> interface</p>
</li>
<li><p>Kernel-managed route (proto kernel)</p>
</li>
<li><p>For packets originating from <code>172.31.90.178</code></p>
</li>
</ul>
</li>
<li><p>DHCP Route:</p>
<ul>
<li><p>Direct route to DHCP server (<code>172.31.80.1</code>)</p>
</li>
<li><p>Via interface <code>enX0</code></p>
</li>
</ul>
</li>
</ul>
<p>All routes have a metric of 100, which determines route priority (lower values are preferred).</p>
<p><code>netstat -tuln</code> shows active listening ports:</p>
<pre><code class="lang-bash">netstat -tuln
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
tcp        0      0 127.0.0.54:53           0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN     
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN     
tcp6       0      0 :::80                   :::*                    LISTEN     
tcp6       0      0 :::22                   :::*                    LISTEN     
udp        0      0 127.0.0.54:53           0.0.0.0:*                          
udp        0      0 127.0.0.53:53           0.0.0.0:*                          
udp        0      0 172.31.90.178:68        0.0.0.0:*                          
udp        0      0 127.0.0.1:323           0.0.0.0:*                          
udp6       0      0 ::1:323                 :::*
</code></pre>
<h2 id="heading-how-to-get-your-software-amp-services-information-in-linux">How to Get Your Software &amp; Services Information in Linux</h2>
<h3 id="heading-installed-packages">Installed packages</h3>
<p>You can check installed packages with <code>dpkg -l</code>, <code>apt list --installed</code> (Debian/Ubuntu). Here is a snippet from the output:</p>
<pre><code class="lang-bash">vim-common/noble-updates,noble-security,now 2:9.1.0016-1ubuntu7.8 all [installed,automatic]
vim-runtime/noble-updates,noble-security,now 2:9.1.0016-1ubuntu7.8 all [installed,automatic]
vim-tiny/noble-updates,noble-security,now 2:9.1.0016-1ubuntu7.8 amd64 [installed,automatic]
vim/noble-updates,noble-security,now 2:9.1.0016-1ubuntu7.8 amd64 [installed,automatic]
</code></pre>
<h3 id="heading-service-status">Service status</h3>
<p><code>systemctl list-units --type=service</code> lists the services. You can also use <code>systemctl status &lt;service&gt;</code> and replace <code>&lt;service&gt;</code> with the one you want.</p>
<p>Here’s the output for <code>cron.service</code>:</p>
<pre><code class="lang-bash">systemctl status cron.service
● cron.service - Regular background program processing daemon
     Loaded: loaded (/usr/lib/systemd/system/cron.service; enabled; preset: enabled)
     Active: active (running) since Wed 2025-05-14 19:46:58 UTC; 2 weeks 5 days ago
       Docs: man:cron(8)
   Main PID: 625 (cron)
      Tasks: 1 (<span class="hljs-built_in">limit</span>: 1129)
     Memory: 1.7M (peak: 4.7M)
        CPU: 20.890s
     CGroup: /system.slice/cron.service
             └─625 /usr/sbin/cron -f -P

Jun 03 09:25:01 ip-172-31-90-178 CRON[121748]: pam_unix(cron:session): session closed <span class="hljs-keyword">for</span> user root
Jun 03 09:35:01 ip-172-31-90-178 CRON[121817]: pam_unix(cron:session): session opened <span class="hljs-keyword">for</span> user root(uid=0) by root(uid=0)
Jun 03 09:35:01 ip-172-31-90-178 CRON[121818]: (root) CMD (<span class="hljs-built_in">command</span> -v debian-sa1 &gt; /dev/null &amp;&amp; debian-sa1 1 1)
Jun 03 09:35:01 ip-172-31-90-178 CRON[121817]: pam_unix(cron:session): session closed <span class="hljs-keyword">for</span> user root
Jun 03 09:45:01 ip-172-31-90-178 CRON[122050]: pam_unix(cron:session): session opened <span class="hljs-keyword">for</span> user root(uid=0) by root(uid=0)
Jun 03 09:45:01 ip-172-31-90-178 CRON[122051]: (root) CMD (<span class="hljs-built_in">command</span> -v debian-sa1 &gt; /dev/null &amp;&amp; debian-sa1 1 1)
Jun 03 09:45:01 ip-172-31-90-178 CRON[122050]: pam_unix(cron:session): session closed <span class="hljs-keyword">for</span> user root
Jun 03 09:55:01 ip-172-31-90-178 CRON[122318]: pam_unix(cron:session): session opened <span class="hljs-keyword">for</span> user root(uid=0) by root(uid=0)
Jun 03 09:55:01 ip-172-31-90-178 CRON[122319]: (root) CMD (<span class="hljs-built_in">command</span> -v debian-sa1 &gt; /dev/null &amp;&amp; debian-sa1 1 1)
Jun 03 09:55:01 ip-172-31-90-178 CRON[122318]: pam_unix(cron:session): session closed <span class="hljs-keyword">for</span> user root
lines 5-21/21 (END)
</code></pre>
<h3 id="heading-processes"><strong>Processes</strong></h3>
<p><code>ps aux</code> shows all processes with their respective status:</p>
<pre><code class="lang-bash">ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  1.4  22556 13952 ?        Ss   May14   0:35 /usr/lib/systemd/systemd --system --deserialize=63
root           2  0.0  0.0      0     0 ?        S    May14   0:00 [kthreadd]
root           3  0.0  0.0      0     0 ?        S    May14   0:00 [pool_workqueue_release]
root           4  0.0  0.0      0     0 ?        I&lt;   May14   0:00 [kworker/R-rcu_g]
root           5  0.0  0.0      0     0 ?        I&lt;   May14   0:00 [kworker/R-rcu_p]
root           6  0.0  0.0      0     0 ?        I&lt;   May14   0:00 [kworker/R-slub_]
.
.
.
</code></pre>
<p>Here's an explanation of each column in the <code>ps aux</code> output:</p>
<ul>
<li><p><code>USER</code>: The owner of the process</p>
</li>
<li><p><code>PID</code>: Process ID number</p>
</li>
<li><p><code>%CPU</code>: CPU usage percentage</p>
</li>
<li><p><code>%MEM</code>: Memory usage percentage</p>
</li>
<li><p><code>VSZ</code>: Virtual Memory Size in kilobytes (total program size)</p>
</li>
<li><p><code>RSS</code>: Resident Set Size in kilobytes (actual memory used)</p>
</li>
<li><p><code>TTY</code>: Terminal associated with the process ('?' means no terminal)</p>
</li>
<li><p><code>STAT</code>: Process state code:</p>
<ul>
<li><p><code>S</code>: Sleeping</p>
</li>
<li><p><code>R</code>: Running</p>
</li>
<li><p><code>I</code>: Idle</p>
</li>
<li><p><code>Z</code>: Zombie</p>
</li>
<li><p><code>T</code>: Stopped</p>
</li>
<li><p><code>s</code>: Session leader</p>
</li>
<li><p><code>&lt;</code>: High priority</p>
</li>
<li><p><code>N</code>: Low priority</p>
</li>
</ul>
</li>
<li><p><code>START</code>: Time when the process started</p>
</li>
<li><p><code>TIME</code>: Cumulative CPU time used</p>
</li>
<li><p><code>COMMAND</code>: The command with all its arguments</p>
</li>
</ul>
<h3 id="heading-top-and-htop-commands"><code>top</code> and <code>htop</code> Commands</h3>
<p><code>top</code> or <code>htop</code> can be used for live usage overview, and for showing a dynamic view of system performance and running processes. Here's what it displays:</p>
<ul>
<li><p>System Overview:</p>
<ul>
<li><p>System uptime and number of logged-in users.</p>
</li>
<li><p>Load average values for the last 1, 5, and 15 minutes.</p>
</li>
<li><p>Total number of processes and their states (running, sleeping, stopped, zombie)</p>
</li>
</ul>
</li>
<li><p>Resource Usage:</p>
<ul>
<li><p>CPU usage breakdown (user, system, idle, etc.).</p>
</li>
<li><p>Memory usage (total, free, used, cached).</p>
</li>
<li><p>Swap space usage</p>
</li>
<li><p>Process List:Shows a sorted list of running processes (by default sorted by CPU usage)For each process, displays:</p>
<ul>
<li><p>Process ID (PID).</p>
</li>
<li><p>User who owns the process.</p>
</li>
<li><p>CPU and memory usage.</p>
</li>
<li><p>Process priority and nice value.</p>
</li>
<li><p>Memory usage details (virtual, resident, shared).</p>
</li>
<li><p>Process status.</p>
</li>
<li><p>Running time.</p>
</li>
<li><p>Command name.</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code class="lang-bash">    top - 10:04:25 up 19 days, 14:17,  1 user,  load average: 0.00, 0.00, 0.00
    Tasks: 104 total,   1 running, 103 sleeping,   0 stopped,   0 zombie
    %Cpu(s):  0.0 us,  0.0 sy,  0.0 ni, 88.0 id,  0.0 wa,  0.0 hi,  0.0 si, 12.0 st 
    MiB Mem :    957.4 total,    247.3 free,    366.1 used,    533.7 buff/cache     
    MiB Swap:      0.0 total,      0.0 free,      0.0 used.    591.3 avail Mem 

        PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                              
          1 root      20   0   22556  13952   9728 S   0.0   1.4   0:35.08 systemd                                              
          2 root      20   0       0      0      0 S   0.0   0.0   0:00.16 kthreadd                                             
          3 root      20   0       0      0      0 S   0.0   0.0   0:00.00 pool_workqueue_release                               
          4 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 kworker/R-rcu_g                                      
          5 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 kworker/R-rcu_p                                      
          6 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 kworker/R-slub_                                      
          7 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 kworker/R-netns                                      
         10 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 kworker/0:0H-events_highpri                          
         12 root       0 -20       0      0      0 I   0.0   0.0   0:00.00 kworker/R-mm_pe                                      
         13 root      20   0       0      0      0 I   0.0   0.0   0:00.00 rcu_tasks_rude_kthread                               
         14 root      20   0       0      0      0 I   0.0   0.0   0:00.00 rcu_tasks_trace_kthread
</code></pre>
<p>    The top command updates this information regularly (by default every 3 seconds) and is commonly used for:</p>
<ul>
<li><p>Monitoring system performance</p>
</li>
<li><p>Identifying resource-intensive processes</p>
</li>
<li><p>Troubleshooting system slowdowns</p>
</li>
<li><p>Getting a quick overview of system health</p>
<p>  You can also interact with top while it's running using various keyboard commands (like 'k' to kill a process, '1' to see cpu cores, etc.).</p>
</li>
</ul>
<h2 id="heading-how-to-get-your-logs-amp-dmesg-in-formation-in-linux">How to Get Your Logs &amp; Dmesg In formation in Linux</h2>
<p>Based on the system configuration, a number of logs are generated. These can be audit logs, system logs, cron logs, and so on. They all carry useful information. Here are some commands that you can use to view logs:</p>
<ul>
<li><p><code>dmesg | less</code>: Kernel ring buffer (hardware issues, boot messages)</p>
</li>
<li><p><code>journalctl -xe</code>: Recent critical logs (systemd systems)</p>
</li>
<li><p><code>/var/log/syslog</code> or <code>/var/log/messages</code>: General system logs</p>
</li>
</ul>
<h2 id="heading-how-to-get-your-securityuser-audit-information-in-linux">How to Get Your Security/User Audit Information in Linux</h2>
<p><code>whoami</code> shows the current user’s username.</p>
<pre><code class="lang-bash">whoami
ubuntu
</code></pre>
<p><code>id</code> shows detailed information about a user's identity on the system.</p>
<pre><code class="lang-bash">id
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),4(adm),24(cdrom),27(sudo),30(dip),105(lxd)
</code></pre>
<p>Let's break down the output:</p>
<ul>
<li><p>User ID (uid): <code>uid=1000(ubuntu)</code> means the user ID is 1000, with username "ubuntu"</p>
</li>
<li><p>Primary Group ID (gid): <code>gid=1000(ubuntu)</code> means the primary group ID is 1000, named "ubuntu"</p>
</li>
<li><p>Supplementary Groups (groups): The user belong to the following groups:</p>
<ul>
<li><p><code>ubuntu (1000)</code>: Your primary group.</p>
</li>
<li><p><code>adm (4)</code>: For system monitoring tasks.</p>
</li>
<li><p><code>cdrom (24)</code>: For accessing CD-ROM devices.</p>
</li>
<li><p><code>sudo (27)</code>: Allows you to execute commands with superuser privileges.</p>
</li>
<li><p><code>dip (30)</code>: For managing dial-up connections.</p>
</li>
<li><p><code>lxd (105)</code>: For managing LXD containers.</p>
</li>
</ul>
</li>
</ul>
<p>The <code>id</code> command is useful for checking user and group IDs, verifying group memberships, troubleshooting permissions issues and confirming sudo access.</p>
<p><code>who</code> displays information about users currently logged into the system:</p>
<pre><code class="lang-bash">who
ubuntu   pts/0        2025-06-03 08:45 (39.43.159.5)
</code></pre>
<p>The output breakdown is shown below:</p>
<ul>
<li><p>Username: "<code>ubuntu</code>"</p>
</li>
<li><p>Terminal: "<code>pts/0</code>" (pseudo-terminal)</p>
</li>
<li><p>Login time: "<code>2025-06-03 08:45"</code></p>
</li>
<li><p>Remote host: "<code>(39.43.159.5)</code>" - the IP address from where the connection was made</p>
</li>
<li><p><code>w</code>- shows who is logged in and what they are doing:</p>
</li>
</ul>
<pre><code class="lang-bash">w
 10:21:46 up 19 days, 14:35,  1 user,  load average: 0.00, 0.00, 0.00
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU  WHAT
ubuntu   pts/0    39.43.159.5      08:45   44:56   0.00s  0.02s sshd: ubuntu [priv]
</code></pre>
<p>Here is the result breakdown:</p>
<p>First line:</p>
<ul>
<li><p><code>10:21:46</code>: Current system time</p>
</li>
<li><p><code>up 19 days, 14:35</code>: System uptime (how long the system has been running)</p>
</li>
<li><p><code>1 user</code>: Number of users currently logged in</p>
</li>
<li><p><code>load average: 0.24, 0.05, 0.02</code>: System load averages for the past 1, 5, and 15 minutes</p>
<ul>
<li><p>Numbers below 1.0 indicate low system load</p>
</li>
<li><p>Higher numbers indicate more system load/stress</p>
</li>
</ul>
</li>
</ul>
<p>Second line shows the column headers for the user information below:</p>
<ul>
<li><p><code>USER</code>: Username.</p>
</li>
<li><p><code>TTY</code>: Terminal device being used.</p>
</li>
<li><p><code>FROM</code>: Remote host from where the user is connected.</p>
</li>
<li><p><code>LOGIN@</code>: Time when the user logged in.</p>
</li>
<li><p><code>IDLE</code>: Time since the user's last activity.</p>
</li>
<li><p><code>JCPU</code>: CPU time used by all processes attached to the tty.</p>
</li>
<li><p><code>PCPU</code>: CPU time used by the current process.</p>
</li>
<li><p><code>WHAT</code>: Current process/command being run.</p>
</li>
</ul>
<p><code>last</code> shows a history of user logins and system reboots:</p>
<pre><code class="lang-bash">last
ubuntu   pts/1        39.43.159.5      Tue Jun  3 10:15 - 10:17  (00:02)
ubuntu   pts/0        39.43.159.5      Tue Jun  3 08:45   still logged <span class="hljs-keyword">in</span>
ubuntu   pts/0        39.43.159.5      Tue Jun  3 05:23 - 08:29  (03:06)
ubuntu   pts/0        39.43.159.5      Sun Jun  1 06:32 - 12:24  (05:52)
ubuntu   pts/0        39.43.159.5      Thu May 22 05:39 - 05:58  (00:18)
ubuntu   pts/0        139.135.32.93    Wed May 21 14:45 - 14:47  (00:01)
ubuntu   pts/0        139.135.32.93    Wed May 21 11:58 - 13:49  (01:51)
ubuntu   pts/0        39.43.159.5      Wed May 21 05:05 - 05:12  (00:06)
ubuntu   pts/0        39.43.159.5      Tue May 20 18:41 - 21:45  (03:04)
ubuntu   pts/0        39.43.159.5      Thu May 15 06:12 - 06:12  (00:00)
ubuntu   pts/0        39.43.159.5      Thu May 15 06:05 - 06:12  (00:07)
ubuntu   pts/0        18.206.107.27    Wed May 14 20:06 - 20:08  (00:01)
ubuntu   pts/0        182.185.185.39   Wed May 14 19:48 - 19:50  (00:01)
reboot   system boot  6.8.0-1024-aws   Wed May 14 19:46   still running

wtmp begins Wed May 14 19:46:47 2025
</code></pre>
<p>Each line shows:</p>
<ul>
<li><p>Username (in this case, all logins are from 'ubuntu' user).</p>
</li>
<li><p>Terminal device (<code>pts/0</code> indicates a pseudo-terminal, typically used for SSH connections).</p>
</li>
<li><p>Remote host IP address (where the connection came from).</p>
</li>
<li><p>Login time and date.</p>
</li>
<li><p>Logout time or status.</p>
</li>
<li><p>Session duration in parentheses.</p>
</li>
</ul>
<p><code>sudo -l</code> shows what the current user can do with sudo.</p>
<pre><code class="lang-bash">sudo -l
Matching Defaults entries <span class="hljs-keyword">for</span> ubuntu on ip-172-31-90-178:
    env_reset, mail_badpass, secure_path=/usr/<span class="hljs-built_in">local</span>/sbin\:/usr/<span class="hljs-built_in">local</span>/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin,
    use_pty

User ubuntu may run the following commands on ip-172-31-90-178:
    (ALL : ALL) ALL
    (ALL) NOPASSWD: ALL
</code></pre>
<p>This output indicates that the 'ubuntu' user has:</p>
<ul>
<li><p>Full sudo access (can execute any command)</p>
</li>
<li><p>No password requirement for sudo commands</p>
</li>
<li><p>Complete administrative privileges on the system</p>
</li>
</ul>
<h2 id="heading-visually-appealing-commands">Visually Appealing Commands</h2>
<p>In this section you’ll learn about two commands that display the information we have seen before in a presentable and aesthetic form.</p>
<p><code>neofetch</code> - displays system info along with the distribution logo:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748945743174/9cef1af7-fce8-4657-ad26-7d75b5755dd1.png" alt="Terminal output of the neofetch command displaying Ubuntu system information, including OS, kernel, uptime, CPU, GPU, memory, and a colorful ASCII logo" class="image--center mx-auto" width="1026" height="749" loading="lazy"></p>
<p><code>btop</code> displays dynamic stats with different modes:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748945510465/8c8c200c-bb1a-4123-8db7-c30bb6a1c9bf.gif" alt="A realtime snapshot of the btop system monitor showing real-time CPU, memory, disk, and network usage in a terminal. Colorful graphs display performance metrics for processes, temperatures, and uptime" class="image--center mx-auto" width="1920" height="956" loading="lazy"></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Thank you for reading the article until the end. If you found it helpful, consider sharing it with others.</p>
<p><strong>Stay Connected and Continue Your Learning Journey!</strong></p>
<p>I read every message, come say hi 👋</p>
<ol>
<li><p><strong>Connect with me on</strong>:</p>
<ul>
<li><p><a target="_blank" href="https://www.linkedin.com/in/zaira-hira/">LinkedIn</a>: I share content related to Linux, Cyber security and DevOps. Leave a recommendation on LinkedIn and endorse me on relevant skills.</p>
</li>
<li><p><a target="_blank" href="https://discord.gg/9zfbjEDs">Discord</a> community: Hang around with other devs or share your accomplishments.</p>
</li>
<li><p><a target="_blank" href="https://twitter.com/hira_zaira">X</a>: I share pre-launch updates and some behind the scenes.</p>
</li>
</ul>
</li>
<li><p><strong>Get access to exclusive content</strong>: For one-on-one help and exclusive content go <a target="_blank" href="https://buymeacoffee.com/zairah/extras">here</a>.</p>
</li>
</ol>
<p>My <a target="_blank" href="https://www.freecodecamp.org/news/author/zaira/">articles</a> are part of my mission to increase accessibility to quality content for everyone. Each piece takes a lot of time and effort to write. This article will be free, forever. If you've enjoyed my work and want to keep me motivated, consider <a target="_blank" href="https://buymeacoffee.com/zairah">buying me a coffee</a>.</p>
<p>Thank you once again and happy learning!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Essential CLI/TUI Tools for Developers ]]>
                </title>
                <description>
                    <![CDATA[ As developers, we spend a lot of time in our terminals. And there are tons of great CLI/TUI tools that can boost our productivity (as well as some that are just fun to use). From managing Git repositories and navigating file systems to monitoring sys... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/essential-cli-tui-tools-for-developers/</link>
                <guid isPermaLink="false">6798fd7b4666e531b7dd7586</guid>
                
                    <category>
                        <![CDATA[ terminal ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Linux ]]>
                    </category>
                
                    <category>
                        <![CDATA[ command line ]]>
                    </category>
                
                    <category>
                        <![CDATA[ cli ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Alex Pliutau ]]>
                </dc:creator>
                <pubDate>Tue, 28 Jan 2025 15:53:31 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1738077620615/22e3c744-d609-4469-ae10-ef8ad4b515a1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>As developers, we spend a lot of time in our terminals. And there are tons of great CLI/TUI tools that can boost our productivity (as well as some that are just fun to use). From managing Git repositories and navigating file systems to monitoring system performance and even playing retro games, the command line offers a powerful and versatile environment.</p>
<p>In this article, we’ll go through a collection of CLI / TUI tools that have been widely adopted in the developer community, spanning various categories such as version control, system utilities, text editors, and more. I wanted to give you a diverse selection that caters to different needs and workflows.</p>
<p>For each tool, I’ll include an overview, highlighting its key features and use cases, along with clear and concise installation instructions for various operating systems, ensuring you can quickly get up and running with these valuable command-line companions.</p>
<h2 id="heading-table-of-contents"><strong>Table of Contents</strong></h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-kubernetes-tools">Kubernetes Tools</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-container-tools">Container Tools</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-file-and-text-tools">File and Text Tools</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-git-tools">Git Tools</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-development-tools">Development Tools</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-networking-tools">Networking Tools</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-workstation-tools">Workstation Tools</a></p>
</li>
</ul>
<h2 id="heading-kubernetes-tools"><strong>Kubernetes Tools</strong></h2>
<h3 id="heading-k9shttpsgithubcomderailedk9s-kubernetes-cli-to-manage-your-clusters-in-style"><a target="_blank" href="https://github.com/derailed/k9s"><strong>k9s</strong></a> — Kubernetes CLI To Manage Your Clusters In Style</h3>
<p>K9s is a must-have tool for anyone working with Kubernetes. Its intuitive terminal-based UI, real-time monitoring capabilities, and powerful command options make it a standout in the world of Kubernetes management tools.</p>
<p>The K9s project is designed to continually watch Kubernetes cluster for changes and offer subsequent commands to interact with observed resources. This makes it easier to manage applications, especially in a complex, multi-cluster environment. The project’s aim is to make Kubernetes management more accessible and less daunting, especially for those who are not Kubernetes experts.</p>
<p>Just launch k9s in your terminal and start exploring the Kubernetes resources with ease.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:700/0*tkfwKS01NCnUBE-N.png" alt="K9s interface" width="700" height="367" loading="lazy"></p>
<p>To install K9s:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># via Homebrew for macOS</span>
brew install derailed/k9s/k9s

<span class="hljs-comment"># via snap for Linux</span>
snap install k9s --devmode

<span class="hljs-comment"># via Chocolatey for Windows</span>
choco install k9s

<span class="hljs-comment"># via go install</span>
go install github.com/derailed/k9s@latest
</code></pre>
<h3 id="heading-kubectxhttpsgithubcomahmetbkubectx-switch-between-contexts-clusters-on-kubectl-faster"><a target="_blank" href="https://github.com/ahmetb/kubectx"><strong>kubectx</strong></a> — switch between contexts (clusters) on kubectl faster.</h3>
<p>Kubectx is the most popular tool for switching Kubernetes contexts, but it has the fewest features! It displays all the contexts in your Kubernetes config as a selectable list and lets you pick one. That’s it!</p>
<p>This project comes with 2 tools:</p>
<ul>
<li><p><strong>kubectx</strong> is a tool that helps you switch between contexts (clusters) on kubectl faster.</p>
</li>
<li><p><strong>kubens</strong> is a tool to switch between Kubernetes namespaces (and configure them for kubectl) easily.</p>
</li>
</ul>
<p>These tools make it very easy to switch between Kubernetes clusters and namespaces if you work with many of them daily. Here you can see it in action:</p>
<p><img src="https://miro.medium.com/v2/resize:fit:700/0*g442WF-cXW-z1dKQ.gif" alt="0*g442WF-cXW-z1dKQ" width="1367" height="472" loading="lazy"></p>
<p>To install kubectx:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># via Homebrew for macOS</span>
brew install kubectx

<span class="hljs-comment"># via apt for Debian</span>
sudo apt install kubectx

<span class="hljs-comment"># via pacman for Arch Linux</span>
sudo pacman -S kubectx

<span class="hljs-comment"># via Chocolatey for Windows</span>
choco install kubens kubectx
</code></pre>
<h3 id="heading-kubescapehttpsgithubcomkubescapekubescape-kubernetes-security-platform-for-your-ide-cicd-pipelines-and-clusters"><a target="_blank" href="https://github.com/kubescape/kubescape"><strong>kubescape</strong></a> — Kubernetes security platform for your IDE, CI/CD pipelines, and clusters.</h3>
<p>I hope you take the security of your Kubernetes clusters seriously. If so, <strong>kubescape</strong> is really great for testing if your Kubernetes cluster is deployed securely according to multiple frameworks.</p>
<p>Kubescape can scan clusters, YAML files, and Helm charts and detects the misconfigurations according to multiple sources.</p>
<p>I usually use it in my CI/CD to scan for vulnerabilities automatically when changing Kubernetes manifests or Helm templates.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:700/0*Ft2r01ij9Rxj2-V0.png" alt="kubescape scan" width="700" height="556" loading="lazy"></p>
<p>To install kubescape:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># via Homebrew for macOS</span>
brew install kubescape

<span class="hljs-comment"># via apt for Debian</span>
sudo add-apt-repository ppa:kubescape/kubescape
sudo apt update
sudo apt install kubescape

<span class="hljs-comment"># via Chocolatey for Windows</span>
choco install kubescape
</code></pre>
<h2 id="heading-container-tools"><strong>Container Tools</strong></h2>
<h3 id="heading-ctophttpsgithubcombcicenctop-a-top-like-interface-for-container-metrics"><a target="_blank" href="https://github.com/bcicen/ctop"><strong>ctop</strong></a> — A top-like interface for container metrics.</h3>
<p><strong>ctop</strong> is basically a better version of <code>docker stats</code>. It provides a concise and condensed overview of real-time metrics for multiple containers. It comes with built-in support for Docker and runC, and connectors for other container and cluster systems are planned for future releases.</p>
<p>Using ctop is simple. Once you have the tool open, you’ll see all of your currently active containers listed.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:700/0*EJ5kdlEs5M5QxDBy.gif" alt="ctop in action" width="1195" height="414" loading="lazy"></p>
<p>To install ctop:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># via Homebrew for macOS</span>
brew install ctop

<span class="hljs-comment"># via pacman for Arch Linux</span>
sudo pacman -S ctop

<span class="hljs-comment"># via scoop for Windows</span>
scoop install ctop
</code></pre>
<h3 id="heading-lazydockerhttpsgithubcomjesseduffieldlazydocker-a-simple-terminal-ui-for-both-docker-and-docker-compose"><a target="_blank" href="https://github.com/jesseduffield/lazydocker"><strong>lazydocker</strong></a> — A simple terminal UI for both docker and docker-compose.</h3>
<p>While Docker's command-line interface is powerful, sometimes you might want a more visual approach without the overhead of a full GUI. This is especially true when managing Docker containers on a headless Linux server where installing a web-based GUI might be undesirable.</p>
<p>Lazydocker was created by <a target="_blank" href="https://github.com/jesseduffield">Jesse Duffield</a> to help make <a target="_blank" href="https://github.com/jesseduffield"></a>managing docker containers a bit easier. Simply put, Lazydocker is a terminal UI (written in Golang) for the docker and docker-compose commands.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:700/0*Cbmx4ShRSO7ccVy2.gif" alt="lazydocker in action" width="1456" height="819" loading="lazy"></p>
<p>To install lazydocker:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># via Homebrew for macOS</span>
brew install lazydocker

<span class="hljs-comment"># via Chocolatey for Windows</span>
choco install lazydocker

<span class="hljs-comment"># via go install</span>
go install github.com/jesseduffield/lazydocker@latest
</code></pre>
<h3 id="heading-divehttpsgithubcomwagoodmandive-a-tool-for-exploring-each-layer-in-a-docker-image"><a target="_blank" href="https://github.com/wagoodman/dive"><strong>dive</strong></a> — A tool for exploring each layer in a Docker image.</h3>
<p>A Docker image is made up of layers, and with every layer you add on, more space will be taken up by the image. Therefore, the more layers in the image, the more space the image will require.</p>
<p>That’s where <strong>dive</strong> shines, it helps you explore your Docker image and layer contents. It can also help you find ways to shrink the size of your Docker/OCI image.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:700/0*swo_hrKJ9EV7hyMs.gif" alt="0*swo_hrKJ9EV7hyMs" width="1456" height="909" loading="lazy"></p>
<p>To install dive:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># via Homebrew for macOS</span>
brew install dive

<span class="hljs-comment"># via pacman for Arch Linux</span>
pacman -S dive

<span class="hljs-comment"># via go install</span>
go get github.com/wagoodman/dive
</code></pre>
<h2 id="heading-file-and-text-tools"><strong>File and Text Tools</strong></h2>
<h3 id="heading-jqhttpsgithubcomjqlangjq-command-line-json-processor"><a target="_blank" href="https://github.com/jqlang/jq"><strong>jq</strong></a> — Command-line JSON processor.</h3>
<p>You may be aware of this one already as it’s well known in the developer community.</p>
<p>Unfortunately, shells such as Bash can’t interpret and work with JSON directly. That’s where you can use <strong>jq</strong> as a command-line JSON processor that’s similar to sed, awk, grep, and so on for JSON data. It’s written in portable C and doesn’t have any runtime dependencies. This lets you slice, filter, map, and transform structured data with ease.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:700/0*uwysqWprpmrLrJQP.png" alt="0*uwysqWprpmrLrJQP" width="700" height="328" loading="lazy"></p>
<p>To install jq, you can download the latest releases from the <a target="_blank" href="https://github.com/jqlang/jq/releases">GitHub release page.</a></p>
<h3 id="heading-bathttpsgithubcomsharkdpbat-a-cat1-clone-with-wings"><a target="_blank" href="https://github.com/sharkdp/bat"><strong>bat</strong></a> — A cat(1) clone with wings.</h3>
<p>This is the most used CLI on my machine currently. A few years ago it was <strong>cat</strong>, which is great but doesn’t provide syntax highlighting, or Git integration</p>
<p>Bat’s syntax highlighting supports many programming and markup languages, helping you make your code more readable directly in the terminal. Git integration lets you see modifications in relation to the index, highlighting the lines you’ve added or changed.</p>
<p>Simply run <code>bat filename</code> and enjoy its output.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:656/0*L02HhsqDcq2_G_z4.png" alt="Bat example" width="656" height="450" loading="lazy"></p>
<p>To install bat:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># via Homebrew for macOS</span>
brew install bat

<span class="hljs-comment"># via apt for Debian</span>
sudo apt install bat

<span class="hljs-comment"># via pacman for Arch Linux</span>
pacman -S bat

<span class="hljs-comment"># via Chocolatey for Windows</span>
choco install bat
</code></pre>
<h3 id="heading-ripgrephttpsgithubcomburntsushiripgrep-recursively-search-directories-for-a-regex-pattern-while-respecting-your-gitignore"><a target="_blank" href="https://github.com/BurntSushi/ripgrep"><strong>ripgrep</strong></a> — Recursively search directories for a regex pattern while respecting your gitignore.</h3>
<p><strong>ripgrep</strong> is definitely becoming a popular alternative (if not the most popular) to the <strong>grep</strong> command. Even some editors like <a target="_blank" href="https://code.visualstudio.com/updates/v1_11">Visual Studio Code</a> are using ripgrep to power their search offerings.</p>
<p>The major selling point is its default behavior for recursive search and speed.</p>
<p>I now rarely use grep on my personal machine, as ripgrep is much faster.</p>
<p>To install ripgrep:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># via Homebrew for macOS</span>
brew install ripgrep

<span class="hljs-comment"># via apt for Debian</span>
sudo apt-get install ripgrep

<span class="hljs-comment"># via pacman for Arch Linux</span>
pacman -S ripgrep

<span class="hljs-comment"># via Chocolatey for Windows</span>
choco install ripgrep
</code></pre>
<h2 id="heading-git-tools"><strong>Git Tools</strong></h2>
<h3 id="heading-lazygithttpsgithubcomjesseduffieldlazygit-simple-terminal-ui-for-git-commands"><a target="_blank" href="https://github.com/jesseduffield/lazygit"><strong>lazygit</strong></a> — Simple terminal UI for git commands.</h3>
<p><strong>lazygit</strong> is another great terminal UI for Git commands developed by <a target="_blank" href="https://github.com/jesseduffield"><strong>Jesse Duffield</strong></a> using Go.</p>
<p>I don’t mind using the Git CLI directly for simple things, but it is famously verbose for more advanced use cases. I am just too lazy to memorize longer commands.</p>
<p>And lazigit has made me a more productive Git user than ever.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:700/0*ykEtn2HQ9QgU40jx.png" alt="lazygit interface" width="700" height="381" loading="lazy"></p>
<p>To install lazygit:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># via Homebrew for macOS</span>
brew install jesseduffield/lazygit/lazygit

<span class="hljs-comment"># via pacman for Arch Linux</span>
pacman -S lazygit

<span class="hljs-comment"># via scoop for Windows</span>
scoop install lazygit
</code></pre>
<h2 id="heading-development-tools"><strong>Development Tools</strong></h2>
<h3 id="heading-atachttpsgithubcomjulien-cpsnatac-a-simple-api-client-postman-like-in-your-terminal"><a target="_blank" href="https://github.com/Julien-cpsn/ATAC"><strong>ATAC</strong></a> — A simple API client (Postman-like) in your terminal.</h3>
<p>ATAC stands for Arguably a Terminal API Client. It’s based on popular clients like Postman, Insomnia, and Bruno, but it runs inside your terminal without needing any particular graphical environment.</p>
<p>It works best for developers who need an offline, cross-platform API client right at their fingertips (terminal).</p>
<p><img src="https://miro.medium.com/v2/resize:fit:700/0*NoOMeMxkELNFI9RS.png" alt="ATAC" width="700" height="376" loading="lazy"></p>
<p>To install ATAC:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># via Homebrew for macOS</span>
brew tap julien-cpsn/atac
brew install atac

<span class="hljs-comment"># via pacman for Arch Linux</span>
pacman -S atac
</code></pre>
<h3 id="heading-k6httpsgithubcomgrafanak6-a-modern-load-testing-tool-using-go-and-javascript"><a target="_blank" href="https://github.com/grafana/k6"><strong>k6</strong></a> — A modern load testing tool, using Go and JavaScript.</h3>
<p>I’ve used many load-testing tools in my career, such as <a target="_blank" href="https://github.com/tsenart/vegeta">vegeta</a> or even <a target="_blank" href="https://httpd.apache.org/docs/2.4/programs/ab.html">ab</a> in the past. But now I mostly use <strong>k6s</strong> as it has everything I need and has a great GUI and TUI.</p>
<p>Why it works well for me:</p>
<ul>
<li><p>k6 has really good <a target="_blank" href="https://k6.io/docs/">documentation</a></p>
</li>
<li><p>Many integrations available: Swagger, JMeter scripts, and so on.</p>
</li>
<li><p>Results reporting is quite good</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737552000859/df5af273-3706-4d41-9dbe-717d2f2d18b7.webp" alt="K6 interface" class="image--center mx-auto" width="1698" height="1184" loading="lazy"></p>
<p>To install k6:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># via Homebrew for macOS</span>
brew install k6

<span class="hljs-comment"># via apt for Debian</span>
sudo apt-get install k6

<span class="hljs-comment"># via Chocolatey for Windows</span>
choco install k6
</code></pre>
<h3 id="heading-httpiehttpsgithubcomhttpiecli-modern-user-friendly-command-line-http-client-for-the-api-era"><a target="_blank" href="https://github.com/httpie/cli"><strong>httpie</strong></a> — modern, user-friendly command-line HTTP client for the API era.</h3>
<p>Don’t get me wrong, curl is great, but not very human-friendly.</p>
<p>HTTPie has a simple and expressive syntax, supports JSON and form data, handles authentication and headers, and displays colorized and formatted output.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:700/0*Bqi3gBKgIkeEPEI_.gif" alt="0*Bqi3gBKgIkeEPEI_" width="1024" height="512" loading="lazy"></p>
<p>To install httpie:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># via Homebrew for macOS</span>
brew install httpie

<span class="hljs-comment"># via apt for Debian</span>
sudo apt install httpie

<span class="hljs-comment"># via pacman for Arch Linux</span>
pacman -Syu httpie

<span class="hljs-comment"># via Chocolatey for Windows</span>
choco install httpie
</code></pre>
<h3 id="heading-asciinemahttpsgithubcomasciinemaasciinema-terminal-session-recorder"><a target="_blank" href="https://github.com/asciinema/asciinema"><strong>asciinema</strong></a> — Terminal session recorder.</h3>
<p>I call it a terminal YouTube :)</p>
<p>asciinema is a great tool when you want to share your terminal sessions with someone else, instead of recording heavy videos.</p>
<p>I use it often when I develop some CLI tools and want to share the demo of how they work (on GitHub, for example).</p>
<p><img src="https://miro.medium.com/v2/resize:fit:700/0*Exg2XuZlIPaJJ-iB.png" alt="0*Exg2XuZlIPaJJ-iB" width="700" height="389" loading="lazy"></p>
<p>To install asciinema:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># via Homebrew for macOS</span>
brew install asciinema

<span class="hljs-comment"># via apt for Debian</span>
sudo apt install asciinema

<span class="hljs-comment"># via pacman for Arch Linux</span>
sudo pacman -S asciinema
</code></pre>
<h2 id="heading-networking"><strong>Networking</strong></h2>
<h3 id="heading-doggohttpsgithubcommr-karandoggo-a-command-line-dns-client"><a target="_blank" href="https://github.com/mr-karan/doggo">doggo</a> — A command-line DNS client.</h3>
<p>It's totally inspired by <strong>dog</strong> which is written in Rust.</p>
<p>In the past I would use <strong>dig</strong> to inspect the DNS, but its output is often verbose and difficult to parse visually.</p>
<p><strong>doggo</strong> addresses these shortcomings by offering two key improvements:</p>
<ul>
<li><p>doggo provides the JSON output support for easy scripting and parsing.</p>
</li>
<li><p>doggo offers a human-readable output format that uses color-coding and a tabular layout to present DNS information clearly and concisely.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1737552264803/bb902365-bc0d-4a56-9a87-6b065ee5608a.png" alt="bb902365-bc0d-4a56-9a87-6b065ee5608a" class="image--center mx-auto" width="3680" height="2572" loading="lazy"></p>
<p>To install doggo:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># via Homebrew for macOS</span>
brew install doggo

<span class="hljs-comment"># via scoop for Windows</span>
scoop install doggo

<span class="hljs-comment"># via go install</span>
go install github.com/mr-karan/doggo/cmd/doggo@latest
</code></pre>
<h3 id="heading-gpinghttpsgithubcomorfgping-ping-but-with-a-graph"><a target="_blank" href="https://github.com/orf/gping"><strong>gping</strong></a> — Ping, but with a graph.</h3>
<p>The well-known <strong>ping</strong> command is not the most interesting to look at, and interpreting its output in a useful way can be difficult.</p>
<p><strong>gping</strong> gives a plot of the ping latency to a host, and the most useful feature is the ability to run concurrent pings to multiple hosts and plot all of them on the same graph.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:700/0*IPi1TOpiMnWPN1VU.gif" alt="0*IPi1TOpiMnWPN1VU" width="1411" height="757" loading="lazy"></p>
<p>To install gping:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># via Homebrew for macOS</span>
brew install gping

<span class="hljs-comment"># via Chocolatey for Windows</span>
choco install gping

<span class="hljs-comment"># via apt for Debian</span>
apt install gping
</code></pre>
<h2 id="heading-workstation"><strong>Workstation</strong></h2>
<h3 id="heading-tmuxhttpsgithubcomtmuxtmuxwiki-a-terminal-multiplexer"><a target="_blank" href="https://github.com/tmux/tmux/wiki"><strong>tmux</strong></a> — A terminal multiplexer.</h3>
<p>Why is tmux such a big deal?</p>
<p>You may have run into situations where you need to view multiple terminal consoles at the same time. For example, you may have a few servers running (for example, web, database, debugger) and you might want to monitor all the output coming from these servers in real-time to validate behavior or run commands.</p>
<p>Before tmux, you might have just opened a few different tabs in the terminal and switched between them to see the output.</p>
<p>Thankfully, there’s an easier way — <strong>tmux</strong>.</p>
<p>In a nutshell, here are some of its most popular features:</p>
<ul>
<li><p>Window/Pane management</p>
</li>
<li><p>Session management with persistence</p>
</li>
<li><p>Sharable sessions with other users</p>
</li>
<li><p>Scriptable configurations</p>
</li>
</ul>
<p><img src="https://miro.medium.com/v2/resize:fit:700/0*u8o0WxutrPXxg6FG.png" alt="0*u8o0WxutrPXxg6FG" width="700" height="294" loading="lazy"></p>
<p>To install tmux:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># via Homebrew for macOS</span>
brew install tmux

<span class="hljs-comment"># via apt for Debian</span>
apt install tmux

<span class="hljs-comment"># via pacman for Arch Linux</span>
pacman -S tmux
</code></pre>
<h3 id="heading-zellijhttpsgithubcomzellij-orgzellij-a-terminal-workspace-with-batteries-included"><a target="_blank" href="https://github.com/zellij-org/zellij"><strong>zellij</strong></a> — A terminal workspace with batteries included.</h3>
<p>Since I listed tmux here, it also makes sense to include a new competitor, <strong>Zellij</strong>, which has been gaining traction in the developer community. Both have their own unique features and purposes.</p>
<p>Compared to traditional terminal multiplexers, zellij offers a more user-friendly interface, modern design elements, built-in layout systems, and a plugin system, making it easier for newcomers to get started.</p>
<p>I still like tmux. It has a special place in my heart because it has served a great purpose for years. But zellij is another good option.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:700/0*VwAit4tO1IjxH9dp.gif" alt="0*VwAit4tO1IjxH9dp" width="825" height="435" loading="lazy"></p>
<p>To install zellij:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># via Homebrew for macOS</span>
brew install zellij

<span class="hljs-comment"># via apt for Debian</span>
apt install zellij

<span class="hljs-comment"># via pacman for Arch Linux</span>
pacman -S zellij
</code></pre>
<h3 id="heading-btophttpsgithubcomaristocratosbtop-a-monitor-of-resources"><a target="_blank" href="https://github.com/aristocratos/btop"><strong>btop</strong></a> — A monitor of resources.</h3>
<p>I can’t live without btop, and it’s installed on all my machines via my personal <a target="_blank" href="https://github.com/plutov/dotfiles">dotfiles</a>. I rarely use now built-in OS GUIs to check the resource utilization on my host machine, because <strong>btop</strong> can do it much better.</p>
<p>I use to to quickly explore what uses the most memory, monitor and kill some processes, and more.</p>
<p><img src="https://miro.medium.com/v2/resize:fit:700/0*HbuJrCbT6xVApLoh.png" alt="0*HbuJrCbT6xVApLoh" width="700" height="441" loading="lazy"></p>
<p>To install btop:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># via Homebrew for macOS</span>
brew install btop

<span class="hljs-comment"># via snap for Debian</span>
sudo snap install btop
</code></pre>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>These CLIs/TUIs should work well in any modern terminal. I personally use <a target="_blank" href="https://ghostty.org/">Ghostty</a> currently and it works great, but other popular options like <strong>iTerm2, Kitty</strong>, and the default terminal applications on macOS and Linux should also provide a seamless experience. The key is to ensure your terminal supports features like 256-color palettes and UTF-8 encoding for optimal display of these tools.</p>
<p>There’s a huge amount of CLIs/TUIs out there, and I couldn’t list them all (though I tried to list some of the best). This selection represents a starting point for exploring the rich ecosystem of command-line tools available to developers. I encourage you to explore further, discover new tools that fit your specific needs, and contribute back to the community by sharing your findings.</p>
<p><a target="_blank" href="https://packagemain.tech">Explore more articles on packagemain.tech</a></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use TUI Applications with Click and Trogon – Linux Tutorial ]]>
                </title>
                <description>
                    <![CDATA[ Linux and terminal applications are almost synonymous. If you have used applications like grep, cat, sed, and AWK, those are command line interfaces (CLI). And when they work together, they allow you to unleash the power of your computer by mixing an... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/tui-applications-with-click-and-trogon/</link>
                <guid isPermaLink="false">66d8514de86088251dd27bc4</guid>
                
                    <category>
                        <![CDATA[ cli ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Linux ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Jose Vicente Nunez ]]>
                </dc:creator>
                <pubDate>Wed, 17 Jan 2024 17:36:53 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/01/mazinger_vampire.JPG" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Linux and terminal applications are almost synonymous. If you have used applications like grep, cat, sed, and AWK, those are command line interfaces (<a target="_blank" href="https://en.wikipedia.org/wiki/Command-line_interface">CLI</a>). And when they work together, they allow you to unleash the power of your computer by mixing and matching a few commands.</p>
<p>Sometimes the CLI gets too complex – and that's when you can complement it with more exploratory versions of the programs called text user interfaces (<a target="_blank" href="https://en.wikipedia.org/wiki/Text-based_user_interface">TUI</a>).</p>
<p>TUIs like HTOP, Glances, Midnight Commander, and others allow you to mix in the power of the CLI without sacrificing the ease of use.</p>
<p>So what can you do when your Python CLI has too many options and becomes intimidating? Wouldn't be nice if you could have a way to 'self' discover the app, and then once you're familiar with it, perform your tasks quickly using the options supported by the script?</p>
<p>Python has a very <a target="_blank" href="https://github.com/josevnz/rpm_query/blob/main/Writting%20UI%20applications%20that%20can%20query%20the%20RPM%20database%20with%20Python.md">healthy ecosystem of GUI and TUI frameworks</a> that you can use to write nice-looking and intuitive applications. In this tutorial we will talk about Trogon and what you can do to make your application more friendly yet powerful for new and seasoned users alike.</p>
<p>I'll show you two of them that can help you solve the following two problems:</p>
<ol>
<li><p>Avoid becoming overwhelmed and having to use intimidating APIs when writing applications. Will use the <a target="_blank" href="https://palletsprojects.com/p/click/">Click</a> Python package to solve that problem.</p>
</li>
<li><p>Allow discoverability. This is very important when you have an application that supports many options or that you haven't used in a while. That is where <a target="_blank" href="https://github.com/Textualize/trogon">Trogon</a> comes handy.</p>
</li>
</ol>
<p>We will reuse the source code of one of my Open Source applications, <a target="_blank" href="https://github.com/josevnz/rpm_query/tree/main">rpm_query</a> as a base. Rpm_query is a collection of simple applications that can query your system <a target="_blank" href="https://en.wikipedia.org/wiki/RPM_Package_Manager#Local_operations">RPM database</a> from the command line.</p>
<h2 id="heading-what-youll-need-for-this-tutorial">What You'll Need for This Tutorial</h2>
<ol>
<li><p>Linux's distribution, preferably one that uses RPM (Like Fedora or RedHat enterprise Linux)</p>
</li>
<li><p>Python 3.8+</p>
</li>
<li><p>Git</p>
</li>
<li><p>Familiarity with Python virtual environments</p>
</li>
<li><p>An Internet connection so you can download dependencies, using pip.</p>
</li>
</ol>
<p>I strongly suggest that you clone the repository and create a virtual environment so you can follow the tutorial:</p>
<pre><code class="lang-shell">git clone https://github.com/josevnz/CLIWithClickAndTrogon.git
cd CLIWithClickAndTrogon
python3 -m venv ~/virtualenv/CLIWithCLickAndTrogon 
. ~/virtualenv/CLIWithCLickAndTrogon/bin/activate
</code></pre>
<p>If you're all set, let's dive in.</p>
<h2 id="heading-what-a-typical-cli-command-line-interface-looks-like-quick-refresher">What a Typical CLI (Command Line Interface) Looks Like – Quick Refresher</h2>
<p>This script uses a module inside the <a target="_blank" href="https://github.com/josevnz/CLIWithClickAndTrogon/blob/3192bed33056985421feb7dbd40cb1922ad80e6c/reporter/rpm_query.py">reporter</a> Python package to query the RPM database.</p>
<pre><code class="lang-python"><span class="hljs-comment">#!/usr/bin/env python</span>
<span class="hljs-string">"""
# rpmq_simple.py - A simple CLI to query the sizes of RPM on your system
Author: Jose Vicente Nunez
"""</span>
<span class="hljs-keyword">import</span> argparse
<span class="hljs-keyword">import</span> textwrap

<span class="hljs-keyword">from</span> reporter <span class="hljs-keyword">import</span> __is_valid_limit__
<span class="hljs-keyword">from</span> reporter.rpm_query <span class="hljs-keyword">import</span> QueryHelper

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:

    parser = argparse.ArgumentParser(description=textwrap.dedent(__doc__))
    parser.add_argument(
        <span class="hljs-string">"--limit"</span>,
        type=__is_valid_limit__,  <span class="hljs-comment"># Custom limit validator</span>
        action=<span class="hljs-string">"store"</span>,
        default=QueryHelper.MAX_NUMBER_OF_RESULTS,
        help=<span class="hljs-string">"By default results are unlimited but you can cap the results"</span>
    )
    parser.add_argument(
        <span class="hljs-string">"--name"</span>,
        type=str,
        action=<span class="hljs-string">"store"</span>,
        help=<span class="hljs-string">"You can filter by a package name."</span>
    )
    parser.add_argument(
        <span class="hljs-string">"--sort"</span>,
        action=<span class="hljs-string">"store_false"</span>,
        help=<span class="hljs-string">"Sorted results are enabled bu default, but you fan turn it off"</span>
    )
    args = parser.parse_args()

    <span class="hljs-keyword">with</span> QueryHelper(
        name=args.name,
        limit=args.limit,
        sorted_val=args.sort
    ) <span class="hljs-keyword">as</span> rpm_query:
        <span class="hljs-keyword">for</span> package <span class="hljs-keyword">in</span> rpm_query:
            print(<span class="hljs-string">f"<span class="hljs-subst">{package[<span class="hljs-string">'name'</span>]}</span>-<span class="hljs-subst">{package[<span class="hljs-string">'version'</span>]}</span>: <span class="hljs-subst">{package[<span class="hljs-string">'size'</span>]:,<span class="hljs-number">.0</span>f}</span>"</span>)
</code></pre>
<p>Let's install it, in editable mode:</p>
<pre><code class="lang-shell">. ~/virtualenv/CLIWithCLickAndTrogon/bin/activate
pip install --editable .
</code></pre>
<p>And see it in action:</p>
<pre><code class="lang-shell">(CLIWithClickAndTrogon) [josevnz@dmaf5 CLIWithClickAndTrogon]$ rpmq_simple.py --help
usage: rpmq_simple.py [-h] [--limit LIMIT] [--name NAME] [--sort]

# rpmq_simple.py - A simple CLI to query the sizes of RPM on your system Author: Jose Vicente Nunez

options:
  -h, --help     show this help message and exit
  --limit LIMIT  By default results are unlimited but you can cap the results
  --name NAME    You can filter by a package name.
  --sort         Sorted results are enabled bu default, but you fan turn it off
(CLIWithClickAndTrogon) [josevnz@dmaf5 CLIWithClickAndTrogon]$ rpmq_simple.py --name kernel --limit 5
kernel-6.2.11: 0
kernel-6.2.14: 0
kernel-6.2.15: 0
</code></pre>
<p>So it seems than most of the code on the <a target="_blank" href="https://github.com/josevnz/CLIWithClickAndTrogon/blob/main/scripts/rpmq_simple.py">rpmq_simple.py</a> script is boilerplate for the command line interface, using the standard '<a target="_blank" href="https://docs.python.org/3/library/argparse.html">ArgParse</a>' library.</p>
<p>ArgParse is <a target="_blank" href="https://docs.python.org/3/howto/argparse.html#argparse-tutorial">powerful</a>, but it is also intimidating at first, specially when you have to support multiple use cases.</p>
<h2 id="heading-a-new-way-to-process-the-cli-with-click">A New Way to Process the CLI with Click</h2>
<p>The Click framework promises to make it easier to parse out command line arguments. To demonstrate that, let's convert our script from ArgParse to <a target="_blank" href="https://click.palletsprojects.com/en/8.1.x/">Click</a> (they both provide support for options but Click has a few interesting options we will use):</p>
<pre><code class="lang-python"><span class="hljs-comment">#!/usr/bin/env python</span>
<span class="hljs-string">"""
# rpmq_click.py - A simple CLI to query the sizes of RPM on your system
Author: Jose Vicente Nunez
"""</span>
<span class="hljs-keyword">import</span> click

<span class="hljs-keyword">from</span> reporter.rpm_query <span class="hljs-keyword">import</span> QueryHelper


<span class="hljs-meta">@click.command()</span>
<span class="hljs-meta">@click.option('--limit', default=QueryHelper.MAX_NUMBER_OF_RESULTS,</span>
              help=<span class="hljs-string">"By default results are unlimited but you can cap the results"</span>)
<span class="hljs-meta">@click.option('--name', help="You can filter by a package name.")</span>
<span class="hljs-meta">@click.option('--sort', default=True, help="Sorted results are enabled bu default, but you fan turn it off")</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">command</span>(<span class="hljs-params">
        name: str,
        limit: int,
        sort: bool
</span>) -&gt; <span class="hljs-keyword">None</span>:</span>
    <span class="hljs-keyword">with</span> QueryHelper(
            name=name,
            limit=limit,
            sorted_val=sort
    ) <span class="hljs-keyword">as</span> rpm_query:
        <span class="hljs-keyword">for</span> package <span class="hljs-keyword">in</span> rpm_query:
            click.echo(<span class="hljs-string">f"<span class="hljs-subst">{package[<span class="hljs-string">'name'</span>]}</span>-<span class="hljs-subst">{package[<span class="hljs-string">'version'</span>]}</span>: <span class="hljs-subst">{package[<span class="hljs-string">'size'</span>]:,<span class="hljs-number">.0</span>f}</span>"</span>)


<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    command()
</code></pre>
<p>So you will notice to big changes here:</p>
<ol>
<li><p>Most of the boilerplate code from ArgParse is gone, replaced by annotations.</p>
</li>
<li><p>Click works by adding decorators to a new function called 'command', that takes arguments and executes the RPM query.</p>
</li>
</ol>
<p>If you run the new script you will see that it works exactly as before:</p>
<pre><code class="lang-shell">(CLIWithClickAndTrogon) [josevnz@dmaf5 CLIWithClickAndTrogon]$ rpmq_click.py --help
Usage: rpmq_click.py [OPTIONS]

Options:
  --limit INTEGER  By default results are unlimited but you can cap the
                   results
  --name TEXT      You can filter by a package name.
  --sort BOOLEAN   Sorted results are enabled bu default, but you fan turn it
                   off
  --help           Show this message and exit.
(CLIWithClickAndTrogon) [josevnz@dmaf5 CLIWithClickAndTrogon]$ rpmq_click.py --name kernel --limit 5
kernel-6.2.11: 0
kernel-6.2.14: 0
kernel-6.2.15: 0
</code></pre>
<p>So what did we gain? Our code is slightly simpler but also is now supported by Trogon, a new framework we will discuss soon.</p>
<h2 id="heading-how-to-use-setuptools-and-click">How to Use setuptools and Click</h2>
<p>The Click <a target="_blank" href="https://click.palletsprojects.com/en/8.1.x/setuptools/#setuptools-integration">documentation r</a>ecommends that we should use <a target="_blank" href="https://setuptools.pypa.io/en/latest/setuptools.html">setuptools</a> to create a wrapper for our tool, automatically. So we need to define a function where we handle all the command line options and logic and the wrapper creates a regular script for us on the right place during the package installation. It also points to the right version of Python, among other nice things.</p>
<p>The documentation has the deprecated syntax for setup.py, so we will use the more recent setup.cfg format instead:</p>
<pre><code class="lang-python">[metadata]
name = CLIWithClickAndTrogon
version = <span class="hljs-number">0.0</span><span class="hljs-number">.1</span>
author = Jose Vicente Nunez Zuleta
author-email = kodegeek.com@protonmail.com
license = Apache <span class="hljs-number">2.0</span>
summary = Simple TUI that queries the RPM database
home-page = https://github.com/josevnz/cliwithclickandtrogon
description = Simple TUI that queries the RPM database. A tutorial.
long_description = file: README.md
long_description_content_type = text/markdown

[options]
packages = reporter
setup_requires =
    setuptools
    wheel
    build
    pip
    twine
install_requires =
    importlib; python_version == <span class="hljs-string">"3.9"</span>
    click
scripts =
    scripts/rpmq_simple.py
    scripts/rpmq_click.py
[options.entry_points]
console_scripts =
    rpmq = reporter.scripts:command
</code></pre>
<p>I created a package called '<a target="_blank" href="https://github.com/josevnz/CLIWithClickAndTrogon/tree/main/scripts">scripts</a>' inside the package called 'reporter' with the CLI logic using click.</p>
<p><a target="_blank" href="https://setuptools.pypa.io/en/latest/userguide/entry_point.html">setuptools will generate a script called 'rpmq'</a> for us that behaves exactly as the previous script does – but again, we don't need any boilerplate code to pass arguments to Click:</p>
<pre><code class="lang-shell">CLIWithClickAndTrogon) [josevnz@dmaf5 CLIWithClickAndTrogon]$ pip install --editable .
(CLIWithClickAndTrogon) [josevnz@dmaf5 CLIWithClickAndTrogon]$ rpmq --help
Usage: rpmq [OPTIONS]

Options:
  --limit INTEGER  By default results are unlimited but you can cap the
                   results
  --name TEXT      You can filter by a package name.
  --sort BOOLEAN   Sorted results are enabled bu default, but you fan turn it
                   off
  --help           Show this message and exit.
(CLIWithClickAndTrogon) [josevnz@dmaf5 CLIWithClickAndTrogon]$ rpmq --name kernel --limit 5
kernel-6.2.11: 0
kernel-6.2.14: 0
kernel-6.2.15: 0
</code></pre>
<h2 id="heading-how-to-make-your-cli-discoverable-with-trogon">How to Make Your CLI Discoverable with Trogon</h2>
<p>Let's solve the problem of making your CLI discoverable with Trogon. Besides adding the new <code>trogon</code> library as part of the requirements (<a target="_blank" href="https://github.com/josevnz/CLIWithClickAndTrogon/blob/main/requirements.txt">requirements.txt</a> and <a target="_blank" href="https://github.com/josevnz/CLIWithClickAndTrogon/blob/main/setup.cfg">setup.cfg</a>), we need to add a new decorator to our CLI:</p>
<pre><code class="lang-python"><span class="hljs-comment">#!/usr/bin/env python</span>
<span class="hljs-string">"""
A simple CLI to query the sizes of RPM on your system
Author: Jose Vicente Nunez
"""</span>
<span class="hljs-keyword">import</span> click
<span class="hljs-keyword">from</span> trogon <span class="hljs-keyword">import</span> tui

<span class="hljs-keyword">from</span> reporter.rpm_query <span class="hljs-keyword">import</span> QueryHelper

<span class="hljs-meta">@tui()</span>
<span class="hljs-meta">@click.command()</span>
<span class="hljs-meta">@click.option('--limit', default=QueryHelper.MAX_NUMBER_OF_RESULTS,</span>
              help=<span class="hljs-string">"By default results are unlimited but you can cap the results"</span>)
<span class="hljs-meta">@click.option('--name', help="You can filter by a package name.")</span>
<span class="hljs-meta">@click.option('--sort', default=True, help="Sorted results are enabled bu default, but you fan turn it off")</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">command</span>(<span class="hljs-params">
        name: str,
        limit: int,
        sort: bool
</span>) -&gt; <span class="hljs-keyword">None</span>:</span>
    <span class="hljs-keyword">with</span> QueryHelper(
            name=name,
            limit=limit,
            sorted_val=sort
    ) <span class="hljs-keyword">as</span> rpm_query:
        <span class="hljs-keyword">for</span> package <span class="hljs-keyword">in</span> rpm_query:
            click.echo(<span class="hljs-string">f"<span class="hljs-subst">{package[<span class="hljs-string">'name'</span>]}</span>-<span class="hljs-subst">{package[<span class="hljs-string">'version'</span>]}</span>: <span class="hljs-subst">{package[<span class="hljs-string">'size'</span>]:,<span class="hljs-number">.0</span>f}</span>"</span>)


<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    command()
</code></pre>
<p>Just one annotation, <code>@tui</code>, and a new import.</p>
<p>Time to see it in action:</p>
<pre><code class="lang-shell">(CLIWithClickAndTrogon) [josevnz@dmaf5 CLIWithClickAndTrogon]$ rpmq_trogon.py --help
Usage: rpmq_trogon.py [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  command
  tui      Open Textual TUI.
</code></pre>
<p>Same results, but you'll notice two changes:</p>
<ol>
<li><p>If you want to use the CLI options, you need to prepend 'command' before the switches.</p>
</li>
<li><p>There is a new <code>tui</code> command.</p>
</li>
</ol>
<p>Wait a second...what happened with the other flags? No worries, if you ask for more help for 'command', you will see them there:</p>
<pre><code class="lang-shell">(CLIWithClickAndTrogon) [josevnz@dmaf5 CLIWithClickAndTrogon]$ rpmq_trogon.py command --help
Usage: rpmq_trogon.py command [OPTIONS]

Options:
  --limit INTEGER  By default results are unlimited but you can cap the
                   results
  --name TEXT      You can filter by a package name.
  --sort BOOLEAN   Sorted results are enabled bu default, but you fan turn it
                   off
  --help           Show this message and exit.
</code></pre>
<p>Ah, much better. Let's run the CLI similar to the way we did before:</p>
<pre><code class="lang-shell">(CLIWithClickAndTrogon) [josevnz@dmaf5 CLIWithClickAndTrogon]$ rpmq_trogon.py command --limit 5 --name kernel
kernel-6.2.11: 0
kernel-6.2.14: 0
kernel-6.2.15: 0
</code></pre>
<p>And what about support for setuptools? Just add the import and the annotation to the 'command function':</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> click
<span class="hljs-keyword">from</span> trogon <span class="hljs-keyword">import</span> tui

<span class="hljs-keyword">from</span> reporter.rpm_query <span class="hljs-keyword">import</span> QueryHelper
<span class="hljs-meta">@tui()</span>
<span class="hljs-meta">@click.command()</span>
<span class="hljs-meta">@click.option('--limit', default=QueryHelper.MAX_NUMBER_OF_RESULTS,</span>
              help=<span class="hljs-string">"By default results are unlimited but you can cap the results"</span>)
<span class="hljs-meta">@click.option('--name', help="You can filter by a package name.")</span>
<span class="hljs-meta">@click.option('--sort', default=True, help="Sorted results are enabled bu default, but you fan turn it off")</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">command</span>(<span class="hljs-params">
        name: str,
        limit: int,
        sort: bool
</span>) -&gt; <span class="hljs-keyword">None</span>:</span>
    <span class="hljs-comment"># .... real code goes here</span>
    <span class="hljs-keyword">pass</span>
</code></pre>
<p>Allow me to demonstrate now with TUI mode how auto discoverable mode works:</p>
<p><a target="_blank" href="https://asciinema.org/a/590897"><img src="https://asciinema.org/a/590897.svg" alt="asciicast" width="1288.76999949" height="634.666508" loading="lazy"></a></p>
<p>Nice! We got a TUI where some options are automatically populated for us. This gives us a clear idea how to use the programs without knowing too much about them.</p>
<h2 id="heading-whats-next">What's Next</h2>
<ol>
<li><p>Download the <a target="_blank" href="https://github.com/josevnz/CLIWithClickAndTrogon">source code</a> for this tutorial and start experimenting.</p>
</li>
<li><p>Both <a target="_blank" href="https://click.palletsprojects.com/en/8.1.x/">Click</a> and <a target="_blank" href="https://discord.com/invite/Enf6Z3qhVr">Trogon</a> have great documentation and online support. Take advantage of them.</p>
</li>
<li><p>Click has many more complex examples, feel free to <a target="_blank" href="https://github.com/pallets/click/tree/main/examples">check out their gallery</a>.</p>
</li>
</ol>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Task Manager CLI Tool with Node.js ]]>
                </title>
                <description>
                    <![CDATA[ By Krish Jaiswal Hello everyone 👋 In this tutorial, you'll learn how to make a simple Task Manager CLI (Command Line Interface) tool. This means you can use commands to Create, View, Update, or Delete your todos.  We will be building this CLI tool u... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/nodejs-tutorial-build-a-task-manager-cli-tool/</link>
                <guid isPermaLink="false">66d4601633b83c4378a517f0</guid>
                
                    <category>
                        <![CDATA[ cli ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ MongoDB ]]>
                    </category>
                
                    <category>
                        <![CDATA[ node js ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 29 Aug 2023 14:19:19 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/08/Add-a-heading--1-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Krish Jaiswal</p>
<p>Hello everyone 👋 In this tutorial, you'll learn how to make a simple Task Manager CLI (Command Line Interface) tool. This means you can use commands to Create, View, Update, or Delete your todos. </p>
<p>We will be building this CLI tool using NodeJS. We'll also use MongoDB as the database to store all our to-dos. Finally, we'll use a few helpful packages from npm:</p>
<ul>
<li><strong><a target="_blank" href="https://www.npmjs.com/package/commander">commander</a></strong>: This helps us to build the CLI tool.</li>
<li><strong><a target="_blank" href="https://www.npmjs.com/package/chalk">chalk</a></strong>: This makes messages in the terminal colorful and easy to read.</li>
<li><strong><a target="_blank" href="https://www.npmjs.com/package/inquirer">inquirer</a></strong>: This lets us ask the user for input.</li>
<li><strong><a target="_blank" href="https://www.npmjs.com/package/ora">ora</a></strong>: This makes the terminal show nice spinning animations.</li>
</ul>
<p>Before we dive in, I want you to know that you can find the complete code for this project on GitHub. If you're ever unsure about something in the code, you can always refer to the final version there. </p>
<p>Here is the link to the Repository: <a target="_blank" href="https://github.com/KrishJ4856/task-manager-cli-fcc">Task Manager CLI Tool Repo</a>.</p>
<h2 id="heading-table-of-contents">Table Of Contents:</h2>
<ul>
<li><a class="post-section-overview" href="#heading-project-setup">Project Setup</a>  </li>
<li><a class="post-section-overview" href="#heading-how-to-create-the-packagejson-file">How to create the <code>package.json</code> file</a>  </li>
<li><a class="post-section-overview" href="#heading-how-to-install-dependencies">How to install dependencies</a>  </li>
<li><a class="post-section-overview" href="#heading-how-to-convert-commonjs-modules-to-es-modules">How to convert CommonJS modules to ES modules</a>  </li>
<li><a class="post-section-overview" href="#heading-how-to-create-the-folder-structure">How to create the folder structure</a></li>
<li><a class="post-section-overview" href="#heading-how-to-connect-to-the-database">How to Connect to the Database</a>  </li>
<li><a class="post-section-overview" href="#heading-how-to-obtain-a-mongodb-connection-string">How to obtain a MongoDB connection string</a>  </li>
<li><a class="post-section-overview" href="#heading-code-for-connecting-to-the-database">Code for connecting to the database</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-a-mongoose-model">How to Create a Mongoose Model</a></li>
<li><a class="post-section-overview" href="#working-on-crud-operations">Working on CRUD Operations</a>  </li>
<li><a class="post-section-overview" href="#heading-how-to-create-todos">How to create Todos</a>  </li>
<li><a class="post-section-overview" href="#heading-how-to-read-todos">How to reading Todos</a>  </li>
<li><a class="post-section-overview" href="#heading-how-to-delete-todos">How to deleting Todos</a>  </li>
<li><a class="post-section-overview" href="#heading-how-to-update-todos">How to update Todos</a></li>
<li><a class="post-section-overview" href="#heading-how-to-write-the-cli-entry-point-using-commander">How to Write the CLI Entry Point using Commander</a></li>
<li><a class="post-section-overview" href="#heading-how-to-test-the-cli-tool">How to Test the CLI tool</a></li>
<li><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></li>
</ul>
<h1 id="heading-project-setup">Project Setup</h1>
<p>Welcome to the first section of this handbook! Here, we will be setting up our project. </p>
<p>This involves a few simple steps: creating a new directory, setting up the <code>package.json</code> file, and installing necessary npm packages like chalk, inquirer, commander, and others we'll talk about soon. We'll also organize the project by creating folders.</p>
<p>Before we dive in, let's ensure you have NodeJS installed on your system. You can get the latest LTS version from this website: <a target="_blank" href="https://nodejs.org/en">https://nodejs.org/en</a>. </p>
<p>To check if Node is properly installed, type this command: <code>node --version</code>. If you see a version number, you're all set! If not, you need to troubleshoot the errors.</p>
<p>Once NodeJS is up and running, create a new folder named "todo." You can use your favorite code editor (I prefer Visual Studio Code) or follow these steps in your terminal:</p>
<ol>
<li>Make a new folder: <code>mkdir todo</code></li>
<li>Go inside the folder: <code>cd todo</code></li>
<li>Open it in your code editor: <code>code .</code></li>
</ol>
<h2 id="heading-how-to-create-the-packagejson-file">How to Create the <code>package.json</code> File</h2>
<p>The first and foremost step is setting up the <code>package.json</code> file. But don't worry about doing it manually. You can save time by using this command:</p>
<pre><code class="lang-bash">npm init --yes
</code></pre>
<p>Once this step is done, let's move on to the next step and get all the necessary things for our project.</p>
<h2 id="heading-how-to-install-dependencies">How to Install Dependencies</h2>
<p>To build this project, we'll need some packages. Just run this simple command to get them all:</p>
<pre><code class="lang-bash">npm i commander inquirer chalk ora mongoose nanoid dotenv
</code></pre>
<h2 id="heading-how-to-convert-commonjs-modules-to-es-modules">How to Convert CommonJS Modules to ES Modules</h2>
<p>Before you continue, let's make a little change in the <code>package.json</code> file. Remove this line: <code>"main": "index.js"</code>, and add these two lines instead:</p>
<pre><code class="lang-json"><span class="hljs-string">"exports"</span>: <span class="hljs-string">"./index.js"</span>,
 <span class="hljs-string">"type"</span>: <span class="hljs-string">"module"</span>,
</code></pre>
<p>With these changes, we're converting our project from CommonJS Modules to ES Modules. This means we'll use <code>import</code> instead of <code>require()</code> to bring in modules, and <code>export</code> instead of <code>module.exports</code> to share things between files.</p>
<p>If you want to dive deeper into different types of modules in JavaScript and how they work, check out this tutorial on FreeCodeCamp: <a target="_blank" href="https://www.freecodecamp.org/news/modules-in-javascript/">Modules in JavaScript - CommonJS and ESmodules Explained.</a></p>
<h2 id="heading-how-to-create-the-folder-structure">How to Create the Folder Structure</h2>
<p>Now, let's organize our project by setting up a smart folder structure. This means we'll make folders to neatly hold our JavaScript files. This step's really important. It makes things easy to manage and develops smoothly.</p>
<p>We're creating 3 folders and 2 files in the main folder:</p>
<p><strong>First folder:</strong> <code>commands</code>. Inside this folder, you'll create 4 files. The names of the files and the description of the code they will contain is mentioned below:</p>
<ul>
<li><code>addTask.js</code>: Code for Creating a new todo.</li>
<li><code>deleteTask.js</code>: Code for Deleting a todo.</li>
<li><code>readTask.js</code>: Code for Displaying all the todos.</li>
<li><code>updateTask.js</code>: Code for Updating a todo.</li>
</ul>
<p><strong>Second folder:</strong> <code>db</code>. Inside this folder, add a file named <code>connectDB.js</code>. This file will contain the code for connecting to the MongoDB database and disconnecting when needed.</p>
<p><strong>Third folder:</strong> <code>schema</code>. Inside it, make a file named <code>TodoSchema.js</code>. This file stores the Mongoose Schema and Model. Basically, a blue print for our tasks, that is how our tasks will look.</p>
<p><strong>First file:</strong> <code>.env</code>. Create this file inside the root directory / main folder of the project. This is where you'll put your MongoDB Connection string.</p>
<p><strong>Second file:</strong> Create the <code>index.js</code> file in the root directory itself which will serve as the entry point of our project. It's like the project's front – where everything starts.</p>
<p>Once we are done, your project's folders should look something like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/image-141.png" alt="Image showing the folder structure for the project" width="600" height="400" loading="lazy">
<em>Project folder structure</em></p>
<h1 id="heading-how-to-connect-to-the-database">How to Connect to the Database</h1>
<p>Now that you've successfully set up the project, it's time to dive into the exciting part.</p>
<h2 id="heading-how-to-obtain-a-mongodb-connection-string">How to Obtain a MongoDB Connection String</h2>
<p>To keep track of all our todos, we need a place to store them. That's where MongoDB Atlas comes in. It's like a special service that handles databases for us. The best part? You can start using it for free (no credit card needed). </p>
<p>To connect to it, all you need is something called a connection string. If MongoDB Atlas is new to you, don't worry. Check out this easy-to-follow article: <a target="_blank" href="https://www.freecodecamp.org/news/get-started-with-mongodb-atlas/">MongoDB Atlas Tutorial - How to Get Started</a>. It gives you just enough info to start using Atlas. By the time you're done, you'll know how to get what you need, including the connection string.</p>
<p>Once you have that connection string, create a new thing called an "environment variable." It's like a secret code your project uses. Open the <code>.env</code> file and make a line like this: <code>MONGO_URI=</code>. After the <code>=</code>, put in your connection string.</p>
<p>Remember: Replace <code>&lt;password&gt;</code> with your actual password and <code>&lt;username&gt;</code> with the username of your database admin in the connection string. Also, add <code>todos</code> between <code>/?</code> in the string. When you're done, your <code>.env</code> file should look something like this:</p>
<pre><code class="lang-text">MONGO_URI=mongodb+srv://&lt;username&gt;:&lt;password&gt;@cluster0.k5tmsld.mongodb.net/todos?retryWrites=true&amp;w=majority
</code></pre>
<h2 id="heading-code-for-connecting-to-the-database">Code for Connecting to the Database</h2>
<p>Now, let's dive into the code that connects our tool to the MongoDB database. Open up the <code>./db/connectDB.js</code> file and let's write some code to make this connection happen.</p>
<p>First things first, we need to bring in the <code>dotenv</code> package that we grabbed earlier when we were setting up the project and invoke the <code>config()</code> method on <code>dotenv</code>. This helps us load environment variables from the <code>.env</code> file. Here's how you do it:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> dotenv <span class="hljs-keyword">from</span> <span class="hljs-string">'dotenv'</span>
dotenv.config()
</code></pre>
<p>Next up, we want to import a few more packages that we'll use here. These are <code>mongoose</code>, <code>ora</code>, and <code>chalk</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> mongoose <span class="hljs-keyword">from</span> <span class="hljs-string">'mongoose'</span>
<span class="hljs-keyword">import</span> ora <span class="hljs-keyword">from</span> <span class="hljs-string">'ora'</span>
<span class="hljs-keyword">import</span> chalk <span class="hljs-keyword">from</span> <span class="hljs-string">'chalk'</span>
</code></pre>
<p><strong>Note:</strong> <code>mongoose</code> is an Object Data Modeling (ODM) library for MongoDB. It provides a higher-level abstraction making it easier to do things like adding, reading, updating, and deleting stuff from the MongoDB database.</p>
<p>Now, let's get into the real action. We'll define two functions here: <code>connectDB()</code> and <code>disconnectDB()</code>.</p>
<p>The <code>connectDB()</code> function will contain the code to help connect our NodeJS Application to the M0ngoDB Database using <code>mongoose</code>. It's like a phone call connecting them. If we don't establish a connection first, our app won't be able to interact with the database and perform the various CRUD operations.</p>
<p>The <code>disconnectDB()</code> function does the opposite. It's like hanging up the phone after our app is done talking to the database. If we don't disconnect, it's like keeping the call going even after we're done. </p>
<p>Failing to disconnect from the database after we are done interacting with it could cause resource leaks. These may cause your app to slow down or potentially crash over time.</p>
<p>Let me show you the code for both the functions:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">connectDB</span>(<span class="hljs-params"></span>)</span>{
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> spinner = ora(<span class="hljs-string">'Connecting to the database...'</span>).start()
        <span class="hljs-keyword">await</span> mongoose.connect(process.env.MONGO_URI)
        spinner.stop()
        <span class="hljs-built_in">console</span>.log(chalk.greenBright(<span class="hljs-string">'Successfully connected to database!!!'</span>))   
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-built_in">console</span>.log(chalk.redBright(<span class="hljs-string">'Error: '</span>), error);
        process.exit(<span class="hljs-number">1</span>) 
    }
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">disconnectDB</span>(<span class="hljs-params"></span>)</span>{
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">await</span> mongoose.disconnect()
        <span class="hljs-built_in">console</span>.log(chalk.greenBright(<span class="hljs-string">'Disconnected from the database.'</span>))
    } <span class="hljs-keyword">catch</span>(err) {
        <span class="hljs-built_in">console</span>.log(chalk.redBright(<span class="hljs-string">'Error: '</span>), error);
        process.exit(<span class="hljs-number">1</span>) 
    }
}
</code></pre>
<p>This is a lot of code to digest at one time, so let me explain this for you:</p>
<p>In the <code>connectDB()</code> function, the line <code>mongoose.connect(process.env.MONGO_URI)</code> helps us actually connect to the database using the connection string. </p>
<p>Remember the <code>.env</code> file? We're using its info here. To load the <code>MONGO_URI</code> variable, we use the <code>dotenv</code> package and call the <code>config()</code> function and then we can access it using <code>process.env.MONGO_URI</code>. </p>
<p>Since <code>mongoose.connect()</code> returns a promise, we use the <code>await</code> keyword before it to make sure we proceed only when this returned promise gets resolved.</p>
<p>It is possible to encounter some errors while running this code, so we have wrapped the entire code in a <code>try...catch()</code> block to make sure any errors which pop up are handled properly in the <code>catch()</code> block.</p>
<p>The <code>ora</code> package helps us show a spinner while we connect to the database. Once successfully connected, we stop the spinner and show a happy message in green using <code>chalk</code>.</p>
<p>If you notice, we are doing the same thing in the <code>disconnectDB()</code> function. But, instead of connecting, we disconnect from the database using <code>mongoose.disconnect()</code>. We wrap it in a similar try-catch block, and again we show colorful messages using <code>chalk</code>.</p>
<p>We use <code>export</code> before these functions to let other parts of the project use them. Don't forget to add these two temporary lines at the end of the file for now:</p>
<pre><code class="lang-javascript">connectDB()
disconnectDB()
</code></pre>
<p>Now, you can run the <code>connectDB.js</code> file using the command: <code>node ./db/connectDB.js</code> and expect to see this in the console:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/rec1.gif" alt="GIF showing the output messages shown in the terminal when the connectDB.js file is executed" width="600" height="400" loading="lazy">
<em>Output seen on the terminal when <code>connectDB.js</code> file is executed. It shows how our code successfully connects to the database and disconnects from it showing appropriate console messages when we invoke the <code>connectDB()</code> and <code>disconnectDB()</code> methods.</em></p>
<p>Connecting to the database is a big step, but you're making great progress! Before moving ahead, make sure you remove those 2 lines you added at the end because they were added just to check if our connection and disconnection functions are working as expected.</p>
<h1 id="heading-how-to-create-a-mongoose-model">How to Create a Mongoose Model</h1>
<p>A Mongoose model is like a tool that helps us talk to the database. With it, we can easily do things like add, read, update, and delete tasks. It's like a helpful assistant that understands how to communicate with the database.</p>
<p>To make this model, we need something called a Schema. It basically defines what each task should look like. Think of it as a blueprint or a set of instructions that guides how each task is created, what information it should have, and how that information is organized. It's like setting rules for how our tasks are stored in the database.</p>
<p>We're going to build this Schema in the <code>./schema/TodoSchema.js</code> file. Open it up, and let's dive in. First, we need two special tools: <code>mongoose</code> and <code>nanoid</code>. We'll use <code>nanoid</code> to make short and unique IDs for each task.</p>
<p>Type these lines to import the tools:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> mongoose <span class="hljs-keyword">from</span> <span class="hljs-string">'mongoose'</span>
<span class="hljs-keyword">import</span> {nanoid} <span class="hljs-keyword">from</span> <span class="hljs-string">'nanoid'</span>
</code></pre>
<p>Now, we use the <code>mongoose.Schema()</code> method to create our Schema. Here's the code for it:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> TodoSchema = <span class="hljs-keyword">new</span> mongoose.Schema({
    <span class="hljs-attr">name</span>: {
        <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>,
        <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">trim</span>: <span class="hljs-literal">true</span>
    },
    <span class="hljs-attr">detail</span>: {
        <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>,
        <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">trim</span>: <span class="hljs-literal">true</span>
    },
    <span class="hljs-attr">status</span>: {
        <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>,
        <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">enum</span>: [<span class="hljs-string">'completed'</span>, <span class="hljs-string">'pending'</span>],
        <span class="hljs-attr">default</span>: <span class="hljs-string">'pending'</span>,
        <span class="hljs-attr">trim</span>: <span class="hljs-literal">true</span>
    },
    <span class="hljs-attr">code</span>: {
        <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>,
        <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">default</span>: <span class="hljs-string">'code'</span>,
        <span class="hljs-attr">trim</span>: <span class="hljs-literal">true</span>
    }
}, {<span class="hljs-attr">timestamps</span>: <span class="hljs-literal">true</span>})
</code></pre>
<p>Any task created using this Schema will have the following properties:</p>
<ul>
<li><code>name</code>: This is a short title for the task. The <code>type: String</code> emphasizes that it can only be text (a String). The <code>required: true</code> specifies that we have to provide this when creating a task and the <code>trim: true</code> specifies that any extra spaces at the beginning or at the end of the task's name will be removed before saving it in the database.</li>
<li><code>detail</code>: This is a description of the task. It has exactly the same properties as <code>name</code>.</li>
<li><code>status</code>: This shows if the task is done or not. The <code>enum: ['completed', 'pending']</code> property specifies that it can only be <code>completed</code> or <code>pending</code>. The <code>default: 'pending'</code> property specifies that if you do not set the <code>status</code> property while creating the task, it is assumed to be <code>pending</code>.</li>
<li><code>code</code>: This is a short and unique ID for the task. We are giving it a default value of <code>code</code>. This value is just a placeholder and doesn't have any real significance in terms of identifying the task. Don't worry, we'll change it soon.</li>
<li>The <code>{timestamps: true}</code> is a configuration option that automatically adds timestamp fields like <code>createdAt</code> and <code>updatedAt</code> to the tasks when they are created or modified.</li>
</ul>
<p>We have successfully defined our Schema – but you may wonder if the <code>code</code> property was supposed to be unique for every task. Currently it stores the same value, that is "<strong>code</strong>", for every single task. Don't worry, we'll fix that. Add this code at the end:</p>
<pre><code class="lang-javascript">TodoSchema.pre(<span class="hljs-string">'save'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">next</span>)</span>{
    <span class="hljs-built_in">this</span>.code = nanoid(<span class="hljs-number">10</span>)
    next()
})
</code></pre>
<p>Here, <code>TodoSchema.pre('save', function(){....})</code> helps us defining a pre-save hook/function which runs every time before a task gets saved in the database. </p>
<p>Inside the function, we use <code>nanoid(10)</code> to create a unique, 10 character long ID for the task and put this generated id in the <code>code</code> field of the task (we can actually access any property/field of the task using the <code>this</code> keyword). </p>
<p>The last line of code: <code>next()</code> basically tells the computer that we are done and it can finally save the document now. With this, we generate a unique ID for every single task created using the <code>nanoid</code> package.</p>
<p>Lastly, we'll make a <code>Todos</code> model using this <code>TodoSchema</code> blueprint and export it. This is how:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> Todos = mongoose.model(<span class="hljs-string">'Todos'</span>, TodoSchema)
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Todos
</code></pre>
<p>And there you go! We've built our Schema and Model. Now let's proceed to the next section of this tutorial.</p>
<h1 id="heading-how-to-work-on-crud-operations">How to Work on CRUD Operations</h1>
<p>Congrats for having successfully followed up till here. So far, we have done 3 things:</p>
<ol>
<li>We've set up the project</li>
<li>We've connected to the MongoDB database, and</li>
<li>We've created the Mongoose Model</li>
</ol>
<p>Up next, we will be working on the various CRUD operations like Creating, Reading, Updating, and Deleting the tasks from our database.</p>
<h2 id="heading-how-to-create-todos">How to Create Todos</h2>
<p>Now, let's start with creating tasks in our project. At first, I planned for a simple process where you add one task to the database at a time. This means when you tell the tool to create a task, it asks you once for the task's details—like the name and description—and then it saves the task.</p>
<p>But then I realized, what if someone wants to add many tasks quickly? Doing it one by one isn't cool. There are two issues:</p>
<ol>
<li>If you have, say, 5 tasks in your mind, you'd have to type the create command 5 times—one for each task.</li>
<li>After entering task details, you wait a bit because saving stuff to the database can take time, especially if the internet is slow.</li>
</ol>
<p>These problems aren't fun at all! To fix this, we need a way to add multiple tasks in one go. Here's how we'll do it:</p>
<p>After you put in the task's name and description, we'll ask if you want to add more tasks. If you enter yes, we continue the process from the start (asking you to again enter the name and description of the next task). But if you enter no, the question prompting process will stop and all the entered tasks get saved to the database together. This way, you can create many tasks without the hassle of doing it one by one. It's all about making things smooth for you.</p>
<p>We'll be writing some code in the <code>./commands/addTask.js</code> file. This is where the magic happens. Let's break it down step by step:</p>
<p>First, we import the necessary packages and functions we've created earlier. You can add these lines of code to do that:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> inquirer <span class="hljs-keyword">from</span> <span class="hljs-string">"inquirer"</span>;
<span class="hljs-keyword">import</span> { connectDB, disconnectDB } <span class="hljs-keyword">from</span> <span class="hljs-string">'../db/connectDB.js'</span>
<span class="hljs-keyword">import</span> Todos <span class="hljs-keyword">from</span> <span class="hljs-string">"../schema/TodoSchema.js"</span>;
<span class="hljs-keyword">import</span> ora <span class="hljs-keyword">from</span> <span class="hljs-string">"ora"</span>;
<span class="hljs-keyword">import</span> chalk <span class="hljs-keyword">from</span> <span class="hljs-string">"chalk"</span>;
</code></pre>
<p>Now, we create an asynchronous function called <code>input()</code> to gather the task's name and details from the user. Here's how it goes:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">input</span>(<span class="hljs-params"></span>)</span>{
    <span class="hljs-keyword">const</span> answers = <span class="hljs-keyword">await</span> inquirer.prompt([
        { <span class="hljs-attr">name</span>: <span class="hljs-string">'name'</span>, <span class="hljs-attr">message</span>: <span class="hljs-string">'Enter name of the task:'</span>, <span class="hljs-attr">type</span>: <span class="hljs-string">'input'</span> },
        { <span class="hljs-attr">name</span>: <span class="hljs-string">'detail'</span>, <span class="hljs-attr">message</span>: <span class="hljs-string">'Enter the details of the task:'</span>, <span class="hljs-attr">type</span>: <span class="hljs-string">'input'</span> },
    ])

    <span class="hljs-keyword">return</span> answers
}
</code></pre>
<p>In simple terms, <code>input()</code> uses <code>inquirer</code> to ask the user for the task's name and details. The answers are then returned as an object.</p>
<p>But wait, you might wonder what <code>inquirer.prompt()</code> is doing. It's a method in the <code>inquirer</code> package that asks questions and waits for responses. You provide an array of question objects, each containing details like the message to display to the user and the type of question. The function returns a Promise, so we use <code>await</code> to wait for the user's answers which get's returned as an object.</p>
<p>Here, <code>{ name: 'name', message: 'Enter name of the task:', type: 'input' }</code> is the first question that will be asked to the user. The <code>message</code> property contains the question that will be displayed to the user. In our case, it is: <code>Enter the name of the task</code>. The user will be prompted to input some text (a String), since this question is of <code>type: 'input'</code>. The <code>name: 'name'</code> signifies that the user's answer to this question will be assigned to a property named – <code>name</code> in the answer's object.</p>
<p>The next object is the second question that will be asked to the user. In this case, a message will be displayed in the terminal: <code>Enter the details of the task</code> and the user's response will be assigned to a property called <code>detail</code> in the answer's object.</p>
<p>To see how the above code works, you can add these 2 lines of code at the end of the file:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> output = <span class="hljs-keyword">await</span> input()
<span class="hljs-built_in">console</span>.log(output)
</code></pre>
<p>Now, save the file and run the code using the command: <code>node ./commands/addTask.js</code>. This is what you will see when you run the code:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/image-166.png" alt="Image of the terminal showing what `inquirer.prompt()` function returns as output." width="600" height="400" loading="lazy">
<em>Output seen on terminal when we just invoke the <code>input()</code> method and execute the code. It shows how <code>inquirer.js</code> returns user's answers after the question prompting process.</em></p>
<p>We can now proceed with the rest of the code and you can remove the last 2 lines which you just added.</p>
<p>Now, let's create a function named <code>askQuestions()</code> to gather multiple tasks. This is how it looks:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> askQuestions = <span class="hljs-keyword">async</span>() =&gt; {

    <span class="hljs-keyword">const</span> todoArray = []
    <span class="hljs-keyword">let</span> loop = <span class="hljs-literal">false</span>

    <span class="hljs-keyword">do</span>{
        <span class="hljs-keyword">const</span> userRes = <span class="hljs-keyword">await</span> input()
        todoArray.push(userRes)
        <span class="hljs-keyword">const</span> confirmQ = <span class="hljs-keyword">await</span> inquirer.prompt([{ <span class="hljs-attr">name</span>: <span class="hljs-string">'confirm'</span>, <span class="hljs-attr">message</span>: <span class="hljs-string">'Do you want to add more tasks?'</span>, <span class="hljs-attr">type</span>: <span class="hljs-string">'confirm'</span> }])
        <span class="hljs-keyword">if</span>(confirmQ.confirm){
            loop = <span class="hljs-literal">true</span>
        } <span class="hljs-keyword">else</span> {
            loop = <span class="hljs-literal">false</span>
        }
    } <span class="hljs-keyword">while</span>(loop)

    <span class="hljs-keyword">return</span> todoArray
}
</code></pre>
<p>In <code>askQuestions()</code>, we set up a loop that keeps asking for tasks until the user decides to stop. We gather each task from the user by calling the <code>input()</code> function, and the returned user's response gets pushed to the <code>todoArray</code>. </p>
<p>Then, we ask if the user wants to add more tasks using a confirmation question. If they say yes, we set <code>loop</code> to <code>true</code> and the loop continues – otherwise, <code>loop</code> becomes <code>false</code>, and the loop ends. Finally, we return the array of tasks, that is <code>todoArray</code>.</p>
<p>You can test this by adding these lines of code at the end of the file:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> output = <span class="hljs-keyword">await</span> askQuestions()
<span class="hljs-built_in">console</span>.log(output)
</code></pre>
<p>When you run the file using <code>node ./commands/addTask.js</code>, you'll see a similar result to what you see here:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/image-167.png" alt="Image of the terminal showing the array of tasks/todos returned by `askQuestions()` function" width="600" height="400" loading="lazy">
<em>Output seen on terminal when we invoke the <code>askQuestions()</code> method and execute the code. It shows the array of tasks returned by the method when the user does not want to continue adding more tasks.</em></p>
<p>We are almost there! Before proceeding, do not forget to remove the last 2 lines you added just now. After you've done that, let's move ahead.</p>
<p>Up to this point, we have successfully managed to collect all the tasks the user wants to create. </p>
<p>Now, let's define the last piece of the puzzle: the <code>addTask()</code> function. This function brings everything together and completes the task creation process. Here's the full code:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">addTask</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">try</span> {
        <span class="hljs-comment">// calling askQuestions() to get array of todo's</span>
        <span class="hljs-keyword">const</span> userResponse = <span class="hljs-keyword">await</span> askQuestions()

        <span class="hljs-comment">// connecting to the database</span>
        <span class="hljs-keyword">await</span> connectDB()

        <span class="hljs-comment">// Displaying a spinner with the following text message using ora </span>
        <span class="hljs-keyword">let</span> spinner = ora(<span class="hljs-string">'Creating the todos...'</span>).start()

        <span class="hljs-comment">// looping over every todo in the userResponse array</span>
        <span class="hljs-comment">// and saving each todo in the database</span>
        <span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> i=<span class="hljs-number">0</span>; i&lt;userResponse.length; i++){
            <span class="hljs-keyword">const</span> response = userResponse[i]
            <span class="hljs-keyword">await</span> Todos.create(response)
        }

        <span class="hljs-comment">// Stopping the spinner and displaying the success message</span>
        spinner.stop()
        <span class="hljs-built_in">console</span>.log(
            chalk.greenBright(<span class="hljs-string">'Created the todos!'</span>)
        )

        <span class="hljs-comment">// disconnecting the database</span>
        <span class="hljs-keyword">await</span> disconnectDB()
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-comment">// Error Handling</span>
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Something went wrong, Error: '</span>, error)
        process.exit(<span class="hljs-number">1</span>)
    }
}
</code></pre>
<p>The <code>addTask()</code> function starts by calling the <code>askQuestions()</code> function to gather the array of tasks and assigning it to the <code>userResponse</code> variable. Then, it connects to the database using <code>connectDB()</code>, displays a spinner using <code>ora</code> to show the task creation process, loops through each task in the array, and saves it to the database using <code>Todos.create(response)</code>. </p>
<p>Once all tasks are saved, the spinner stops, a success message is shown, and then it disconnects from the database using <code>disconnectDB()</code>.</p>
<p>The entire code is wrapped in a <code>try...catch</code> block to handle any potential errors gracefully.</p>
<p>With this code, you've completed the task creation process. Nice work! This was probably the most complex piece of code in the entire project. Future operations such as reading, deleting and updating tasks are going to be fairly simple and easy by comparison. With that said, let's move to performing the Read operation.</p>
<h2 id="heading-how-to-read-todos">How to Read Todos</h2>
<p>Now we'll explore how to read tasks from the MongoDB database. The process is straightforward, and I'll guide you through the entire code in the <code>./commands/readTask.js</code> file:</p>
<p>First, let's import the necessary packages and functions at the beginning of the file:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Importing packages and functions</span>
<span class="hljs-keyword">import</span> { connectDB, disconnectDB } <span class="hljs-keyword">from</span> <span class="hljs-string">'../db/connectDB.js'</span>
<span class="hljs-keyword">import</span> Todos <span class="hljs-keyword">from</span> <span class="hljs-string">'../schema/TodoSchema.js'</span>
<span class="hljs-keyword">import</span> chalk <span class="hljs-keyword">from</span> <span class="hljs-string">'chalk'</span>
<span class="hljs-keyword">import</span> ora <span class="hljs-keyword">from</span> <span class="hljs-string">'ora'</span>
</code></pre>
<p>Now, let's define an asynchronous function named <code>readTask()</code> which encapsulates the logic for reading tasks. The whole function is wrapped in a try...catch block for handling any potential errors:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">readTask</span>(<span class="hljs-params"></span>)</span>{
    <span class="hljs-keyword">try</span> {
        <span class="hljs-comment">// connecting to the database</span>
        <span class="hljs-keyword">await</span> connectDB()

        <span class="hljs-comment">// starting the spinner</span>
        <span class="hljs-keyword">const</span> spinner = ora(<span class="hljs-string">'Fetching all todos...'</span>).start()

        <span class="hljs-comment">// fetching all the todos from the database </span>
        <span class="hljs-keyword">const</span> todos = <span class="hljs-keyword">await</span> Todos.find({})

        <span class="hljs-comment">// stopping the spinner</span>
        spinner.stop()

        <span class="hljs-comment">// check if todos exist or not</span>
        <span class="hljs-keyword">if</span>(todos.length === <span class="hljs-number">0</span>){
            <span class="hljs-built_in">console</span>.log(chalk.blueBright(<span class="hljs-string">'You do not have any tasks yet!'</span>))
        } <span class="hljs-keyword">else</span> {
            todos.forEach(<span class="hljs-function"><span class="hljs-params">todo</span> =&gt;</span> {
                <span class="hljs-built_in">console</span>.log(
                    chalk.cyanBright(<span class="hljs-string">'Todo Code: '</span>) + todo.code + <span class="hljs-string">'\n'</span> + 
                    chalk.blueBright(<span class="hljs-string">'Name: '</span>) + todo.name + <span class="hljs-string">'\n'</span> + 
                    chalk.yellowBright(<span class="hljs-string">'Description: '</span>) + todo.detail + <span class="hljs-string">'\n'</span>
                )
            })
        }

        <span class="hljs-comment">// disconnect from the database</span>
        <span class="hljs-keyword">await</span> disconnectDB()
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-comment">// Error Handling</span>
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Something went wrong, Error: '</span>, error)
        process.exit(<span class="hljs-number">1</span>)
    }
}

readTask()
</code></pre>
<p>Now, let's break down the code step by step:</p>
<ol>
<li>We establish a connection to the MongoDB database using <code>await connectDB()</code>.</li>
<li>We start a spinner using <code>ora</code> to indicate that we are fetching all the todos.</li>
<li>We fetch all the todos from the database using <code>Todos.find({})</code>. Once the process is complete, the <code>todos</code> variable will contain either an empty array (if no tasks exists in the database) or an array of tasks.</li>
<li>After fetching is complete, we stop the spinner using <code>spinner.stop()</code>.</li>
<li>We check whether there are any todos by checking if <code>todos.length</code> is equal to 0. If it is, we display a message in blue saying "You do not have any tasks yet!". If there are todos in the array (which means that the length of the array is not equal to 0), we loop through each todo in the array and print its code, name, and description using <code>chalk</code> for color formatting.</li>
<li>Finally, we disconnect from the database using <code>await disconnectDB()</code>.</li>
</ol>
<p>In the last line of code, we call the <code>readTask()</code> function. This is only for testing purposes, and you can remove this line as instructed.</p>
<p>To run the code, use the command: <code>node ./commands/readTask.js</code>. When you execute this, you'll see something similar to the output shown here:</p>
<p>Note: I had created some random tasks before, so when I run the <code>readTask.js</code> file, I get to see this in my terminal:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/Untitled-video---Made-with-Clipchamp--1-.gif" alt="GIF showing the output which is displayed in the terminal as a result of running the `readTask.js` file" width="600" height="400" loading="lazy">
<em>Output seen on terminal when <code>readTask.js</code> file is executed. It shows how the code successfully reads all the tasks from the database and prints it out in the terminal.</em></p>
<p>Before we proceed, don't forget to remove the last line of code in the <code>readTask.js</code> file because we won't be needing it in the future. </p>
<p>With this code, you've successfully implemented the read functionality for your task manager CLI tool. Great job! In the upcoming sections, we'll explore how to delete and update tasks.</p>
<h2 id="heading-how-to-delete-todos">How to Delete Todos</h2>
<p>This section of the tutorial covers the simple process of deleting todos from the database. The logic is straightforward: users enter the Todo code of the todo they want to delete, and we remove that todo from the database. </p>
<p>Let's delve into the code to make this happen in the <code>./commands/deleteTask.js</code> file.</p>
<p>The first step is to import necessary packages and functions at the beginning of the file, including inquirer, <code>Todos</code> model, connectDB(), disconnectDB(), ora, and chalk.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Importing packages and functions</span>
<span class="hljs-keyword">import</span> inquirer <span class="hljs-keyword">from</span> <span class="hljs-string">"inquirer"</span>;
<span class="hljs-keyword">import</span> Todos <span class="hljs-keyword">from</span> <span class="hljs-string">'../schema/TodoSchema.js'</span>
<span class="hljs-keyword">import</span> {connectDB, disconnectDB} <span class="hljs-keyword">from</span> <span class="hljs-string">'../db/connectDB.js'</span>
<span class="hljs-keyword">import</span> ora <span class="hljs-keyword">from</span> <span class="hljs-string">"ora"</span>;
<span class="hljs-keyword">import</span> chalk <span class="hljs-keyword">from</span> <span class="hljs-string">"chalk"</span>;
</code></pre>
<p>Up next, we will define an asynchronous function called <code>getTaskCode()</code>. The role of this function is to prompt the user to enter the code of the todo they want to delete using <code>inquirer</code>. The function then trims the code entered by the user using the <code>trim()</code> method and returns the trimmed code. The trimming process is necessary to remove the leading or trailing whitespace which the code might contain.</p>
<p>Here is the code for the <code>getTaskCode()</code> function:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getTaskCode</span>(<span class="hljs-params"></span>)</span>{
    <span class="hljs-keyword">try</span> {
        <span class="hljs-comment">// Prompting the user to enter the todo code</span>
        <span class="hljs-keyword">const</span> answers = <span class="hljs-keyword">await</span> inquirer.prompt([
            {<span class="hljs-attr">name</span>: <span class="hljs-string">'code'</span>, <span class="hljs-string">'message'</span>: <span class="hljs-string">'Enter the code of the todo: '</span>, <span class="hljs-attr">type</span>: <span class="hljs-string">'input'</span>},
        ])

        <span class="hljs-comment">// Trimming user's response so that the todo code does not contain any starting or trailing white spaces</span>
        answers.code = answers.code.trim()

        <span class="hljs-keyword">return</span> answers
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Something went wrong...\n'</span>, error)
    }
}
</code></pre>
<p>Now we will define the main function named <code>deleteTask()</code>. The entire code is below:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">deleteTask</span>(<span class="hljs-params"></span>)</span>{
    <span class="hljs-keyword">try</span> {
        <span class="hljs-comment">// Obtaining the todo code provided by user</span>
        <span class="hljs-keyword">const</span> userCode = <span class="hljs-keyword">await</span> getTaskCode()

        <span class="hljs-comment">// Connecting to the database</span>
        <span class="hljs-keyword">await</span> connectDB()

        <span class="hljs-comment">// Starting the spinner</span>
        <span class="hljs-keyword">const</span> spinner = ora(<span class="hljs-string">'Finding and Deleting the todo...'</span>).start()

        <span class="hljs-comment">// Deleting the task</span>
        <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> Todos.deleteOne({<span class="hljs-attr">code</span>: userCode.code})

        <span class="hljs-comment">// Stopping the spinner</span>
        spinner.stop()

        <span class="hljs-comment">// Checking the delete operation</span>
        <span class="hljs-keyword">if</span>(response.deletedCount === <span class="hljs-number">0</span>){
            <span class="hljs-built_in">console</span>.log(chalk.redBright(<span class="hljs-string">'Could not find any todo matching the provided name. Deletion failed.'</span>))
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-built_in">console</span>.log(chalk.greenBright(<span class="hljs-string">'Deleted Task Successfully'</span>))
        }

        <span class="hljs-comment">// Disconnecting from the database</span>
        <span class="hljs-keyword">await</span> disconnectDB()
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-comment">// Error Handling</span>
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Something went wrong, Error: '</span>, error)
        process.exit(<span class="hljs-number">1</span>)
    }
}
</code></pre>
<p>Let's break down this code step-by-step:</p>
<ol>
<li>We obtain the response object which includes the todo code entered by the user by calling the <code>getTaskCode()</code> function defined above. We then assign this object to the <code>userCode</code> variable.</li>
<li>We connect to the database using <code>await connectDB()</code>.</li>
<li>We start a spinner using <code>ora</code> to indicate that we're finding and deleting the todo.</li>
<li>We use <code>Todos.deleteOne({ code: userCode.code })</code> to search for and delete the todo with a matching code. The response will indicate if any document was deleted or not.</li>
<li>After the operation is complete, we stop the spinner using <code>spinner.stop()</code>.</li>
<li>We use an if...else condition to check the <code>deletedCount</code> property in the response. If it's 0, we print a message indicating that the task with the provided code wasn't found and deletion failed. If <code>deletedCount</code> is greater than 0, we print a success message.</li>
<li>We disconnect from the database using <code>await disconnectDB()</code>.</li>
</ol>
<p>If I call the function: <code>deleteTask()</code> and then proceed to run the code using <code>node /commands/deleteTask.js</code> command, I get to see this in my console:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/Untitled-video---Made-with-Clipchamp--1---1-.gif" alt="GIF showing the output which is displayed in the terminal as a result of running the `deleteTask.js` file" width="600" height="400" loading="lazy">
<em>Output seen on terminal when<code>deleteTask.js</code> file is executed. It shows how the code successfully deletes a single task from the database.</em></p>
<p>As you can see in the above GIF, the code will prompt you to enter a Todo code for the task you want to delete. Upon deletion, you'll receive a confirmation message in the console. When we read all our tasks after the deletion process, we don't get to see the deleted task. This implies that our code is successful in doing what it is supposed to do!</p>
<h2 id="heading-how-to-update-todos">How to Update Todos</h2>
<p>In this section, we will be looking at the code to update a specific todo. Updating a todo is a bit more involved compared to the previous operations. The process unfolds as follows:</p>
<ol>
<li>Prompt the user to input the code of the todo to be updated.</li>
<li>Connect to the database.</li>
<li>Find the task whose code property matches the user's input.</li>
<li>If the task doesn't exist, display a message indicating the failure to find a matching todo.</li>
<li>If the task exists, prompt the user to update the <code>name</code>, <code>description</code> and <code>status</code> of the task. </li>
<li>If the user sets the status property of a task to "completed," then that task is deleted. If set to "pending," the task's name and description are updated in the database.</li>
<li>Display a success message in the console after the update operation.</li>
</ol>
<p>Let's start coding! The first thing you need to do is to import all the packages and functions we will need to perform this job.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Importing packages and functions</span>
<span class="hljs-keyword">import</span> {connectDB, disconnectDB} <span class="hljs-keyword">from</span> <span class="hljs-string">'../db/connectDB.js'</span>
<span class="hljs-keyword">import</span> { getTaskCode } <span class="hljs-keyword">from</span> <span class="hljs-string">'./deleteTask.js'</span>
<span class="hljs-keyword">import</span> inquirer <span class="hljs-keyword">from</span> <span class="hljs-string">'inquirer'</span>
<span class="hljs-keyword">import</span> Todos <span class="hljs-keyword">from</span> <span class="hljs-string">'../schema/TodoSchema.js'</span>
<span class="hljs-keyword">import</span> ora <span class="hljs-keyword">from</span> <span class="hljs-string">'ora'</span>
<span class="hljs-keyword">import</span> chalk <span class="hljs-keyword">from</span> <span class="hljs-string">'chalk'</span>
</code></pre>
<p>Before we start working on our <code>updateTask()</code> function, we will create a small function in the same file named <code>askUpdateQ()</code>. The role of this function is to prompt the user to enter the updated values of the task like the task name, description, and status. At the end, this function will return the response object. </p>
<p>Here is the code for it:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">askUpdateQ</span>(<span class="hljs-params">todo</span>)</span>{
    <span class="hljs-keyword">try</span> {
        <span class="hljs-comment">// Prompting the user to update the todo data</span>
        <span class="hljs-keyword">const</span> update = <span class="hljs-keyword">await</span> inquirer.prompt([
            {<span class="hljs-attr">name</span>: <span class="hljs-string">'name'</span>, <span class="hljs-attr">message</span>: <span class="hljs-string">'Update the name?'</span>, <span class="hljs-attr">type</span>: <span class="hljs-string">'input'</span>, <span class="hljs-attr">default</span>: todo.name},
            {<span class="hljs-attr">name</span>: <span class="hljs-string">'detail'</span>, <span class="hljs-attr">message</span>: <span class="hljs-string">'Update the Description?'</span>, <span class="hljs-attr">type</span>: <span class="hljs-string">'input'</span>, <span class="hljs-attr">default</span>: todo.detail},
            {<span class="hljs-attr">name</span>: <span class="hljs-string">'status'</span>, <span class="hljs-attr">message</span>: <span class="hljs-string">'Update the status'</span>, <span class="hljs-attr">type</span>: <span class="hljs-string">'list'</span>, <span class="hljs-attr">choices</span>: [<span class="hljs-string">'pending'</span>, <span class="hljs-string">'completed'</span>], <span class="hljs-attr">default</span>: todo.status}
        ])

        <span class="hljs-keyword">return</span> update
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Something went wrong... \n'</span>, error)
    }
}
</code></pre>
<p>Two things are to be noted here:</p>
<ol>
<li><code>todo</code> is the original task object (the task which the user wants to update). This will be passed to the <code>askUpdateQ()</code> function by the <code>updateTask()</code> function.</li>
<li>Each question object within the array passed to <code>inquirer.prompt()</code> contains a default property set to the original values of the task. This ensures that if the user skips a question, the default value remains unchanged.</li>
</ol>
<p>With that said, now let's look at the code for the <code>updateTask()</code> function:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">updateTask</span>(<span class="hljs-params"></span>)</span>{
    <span class="hljs-keyword">try</span> {
        <span class="hljs-comment">// Obtaining the task code entered by user by calling getTaskCode() method</span>
        <span class="hljs-keyword">const</span> userCode = <span class="hljs-keyword">await</span> getTaskCode()

        <span class="hljs-comment">// Connecting to the database</span>
        <span class="hljs-keyword">await</span> connectDB()

        <span class="hljs-comment">// Starting the spinner</span>
        <span class="hljs-keyword">const</span> spinner = ora(<span class="hljs-string">'Finding the todo...'</span>).start()

        <span class="hljs-comment">// Finding the todo which the user wants to update</span>
        <span class="hljs-keyword">const</span> todo = <span class="hljs-keyword">await</span> Todos.findOne({<span class="hljs-attr">code</span>: userCode.code})

        <span class="hljs-comment">// Stopping the spinner</span>
        spinner.stop()

        <span class="hljs-comment">// Checking if the todo exists or not</span>
        <span class="hljs-keyword">if</span>(!todo){
            <span class="hljs-built_in">console</span>.log(chalk.redBright(<span class="hljs-string">'Could not find a Todo with the code you provided.'</span>))
        } <span class="hljs-keyword">else</span>{
            <span class="hljs-built_in">console</span>.log(chalk.blueBright(<span class="hljs-string">'Type the updated properties. Press Enter if you don\'t want to update the data.'</span>))

            <span class="hljs-comment">// Get the user's response of the updated data by calling askUpdateQ() method</span>
            <span class="hljs-keyword">const</span> update = <span class="hljs-keyword">await</span> askUpdateQ(todo)

            <span class="hljs-comment">// If user marked status as completed, we delete the todo else we update the data</span>
            <span class="hljs-keyword">if</span>(update.status === <span class="hljs-string">'completed'</span>){
                <span class="hljs-comment">// Changing spinner text and starting it again</span>
                spinner.text = <span class="hljs-string">'Deleting the todo...'</span>
                spinner.start()

                <span class="hljs-comment">// Deleting the todo</span>
                <span class="hljs-keyword">await</span> Todos.deleteOne({<span class="hljs-attr">_id</span> : todo._id})

                <span class="hljs-comment">// Stopping the spinner and display the success message</span>
                spinner.stop()
                <span class="hljs-built_in">console</span>.log(chalk.greenBright(<span class="hljs-string">'Deleted the todo.'</span>))
            } <span class="hljs-keyword">else</span> {
                <span class="hljs-comment">// Update the todo</span>
                spinner.text = <span class="hljs-string">'Updating the todo'</span>
                spinner.start()
                <span class="hljs-keyword">await</span> Todos.updateOne({<span class="hljs-attr">_id</span>: todo._id}, update, {<span class="hljs-attr">runValidators</span>: <span class="hljs-literal">true</span>})
                spinner.stop()
                <span class="hljs-built_in">console</span>.log(chalk.greenBright(<span class="hljs-string">'Updated the todo.'</span>))
            }
        }
        <span class="hljs-comment">// Disconnecting from the database</span>
        <span class="hljs-keyword">await</span> disconnectDB()
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-comment">// Error Handling</span>
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Something went wrong, Error: '</span>, error)
        process.exit(<span class="hljs-number">1</span>)
    }
}
</code></pre>
<p>Here's a breakdown of the above code:</p>
<ol>
<li>Obtain the code of the task which the user wants to update. For this, we are utilizing the <code>getTaskCode()</code> function defined in the <code>./commands/deleteTask.js</code> file. We simply call the function and assign the returned response object to the <code>userCode</code> variable.</li>
<li>Connect to the database using <code>await connectDB()</code>.</li>
<li>Start a spinner to indicate that the code is finding the todo.</li>
<li>Use <code>Todos.findOne({ code: userCode.code })</code> to find the task the user wants to update and assign it to the <code>todo</code> variable. We are doing this because we will need the original values of the task.</li>
<li>Stop the spinner.</li>
<li>If no matching task is found, display a message using <code>chalk</code> indicating that the task wasn't found.</li>
<li>If the task is found, prompt the user to input updated properties by calling the <code>askUpdateQ()</code> function and pass the <code>todo</code> object (original task) in the function. Assign the returned object to <code>update</code> variable.</li>
<li>If the user marks the status as "completed," the task is deleted from the database using <code>deleteOne()</code>. If marked as "pending," the task's name and description are updated using <code>updateOne()</code>.   </li>
</ol>
<p><code>updateOne()</code> method takes in 3 parameters – Query Object, Update Object, and the Options object. Here, <code>{_id: todo._id}</code> is the Query Object. Mongoose searches the entire collection for a task whose <code>id</code> property matches with <code>todo_.id</code>. On finding the task, it replaces the task with the update object, that is <code>update</code> in our case. The third parameter, <code>{ runValidators: true }</code>, ensures that Mongoose validates the <code>update</code> object against the schema's rules before executing it. If the validation fails, the update will be rejected, and you'll receive an error. If the validation is successful, the document will be updated successfully in the database.  </p>
<p>Both in case of the Delete and Update Operation, we change the text of the spinner using <code>spinner.text</code> and start it before performing the operation and once the operation is completed, we stop the spinner.</p>
<ol start="9">
<li>Display appropriate success messages in the console based on the operation performed.</li>
<li>Disconnect from the database using <code>await disconnectDB()</code>.</li>
</ol>
<p>If I call the <code>updateTask()</code> function and run the code using the command: <code>node ./commands/updateTask.js</code>, I get to see something like this in my console:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/08/update.gif" alt="GIF showing the output which is displayed in the terminal as a result of running the `updateTask.js` file" width="600" height="400" loading="lazy">
<em>Output seen on terminal when <code>updateTask.js</code> file is executed. It shows how the code successfully fetches the original task and successfully replaces it with the updated values provided by the user.</em></p>
<p>With this, you've successfully implemented all CRUD operations. Now, let's use the <code>commander</code> library to bring everything together and create a fully functional CLI tool.</p>
<h1 id="heading-how-to-write-the-cli-entry-point-using-commander">How to Write the CLI Entry Point using Commander</h1>
<p>In the final stages of our project, we're going to leverage the power of the <code>commander</code> library to craft a user-friendly CLI interface. With <code>commander</code>, we can neatly define different commands – such as read, add, update, and delete – in an organized and intuitive manner.</p>
<p>Our code will reside in the <code>index.js</code> file, which serves as the entry point of our application. Below is the complete code:</p>
<pre><code class="lang-javascript"><span class="hljs-meta">#!/usr/bin/env node</span>

<span class="hljs-comment">// Importing the required functions for each command</span>
<span class="hljs-keyword">import</span> addTask <span class="hljs-keyword">from</span> <span class="hljs-string">'./commands/addTask.js'</span>
<span class="hljs-keyword">import</span> deleteTask <span class="hljs-keyword">from</span> <span class="hljs-string">'./commands/deleteTask.js'</span>
<span class="hljs-keyword">import</span> readTask <span class="hljs-keyword">from</span> <span class="hljs-string">'./commands/readTask.js'</span>
<span class="hljs-keyword">import</span> updateTask <span class="hljs-keyword">from</span> <span class="hljs-string">'./commands/updateTask.js'</span>

<span class="hljs-comment">// Importing the Command class from Commander.js library</span>
<span class="hljs-keyword">import</span> { Command } <span class="hljs-keyword">from</span> <span class="hljs-string">'commander'</span>

<span class="hljs-comment">// Creating an instance of the Command class</span>
<span class="hljs-keyword">const</span> program = <span class="hljs-keyword">new</span> Command()

<span class="hljs-comment">// Setting the name and description of the CLI tool</span>
program
.name(<span class="hljs-string">'todo'</span>)
.description(<span class="hljs-string">'Your terminal task manager!'</span>)
.version(<span class="hljs-string">'1.0.0'</span>)

<span class="hljs-comment">// Defining a command called 'add'</span>
program
.command(<span class="hljs-string">'add'</span>)
.description(<span class="hljs-string">'Create a new todo.'</span>)
.action(addTask)

<span class="hljs-comment">// Defining a command called 'read'</span>
program
.command(<span class="hljs-string">'read'</span>)
.description(<span class="hljs-string">'Reads all the todos.'</span>)
.action(readTask)

<span class="hljs-comment">// Defining a command called 'update'</span>
program
.command(<span class="hljs-string">'update'</span>)
.description(<span class="hljs-string">'Updates a todo.'</span>)
.action(updateTask)

<span class="hljs-comment">// Defining a command called 'delete'</span>
program
.command(<span class="hljs-string">'delete'</span>)
.description(<span class="hljs-string">'Deletes a todo.'</span>)
.action(deleteTask)

<span class="hljs-comment">// Parsing the command-line arguments and executing the corresponding actions</span>
program.parse()
</code></pre>
<ol>
<li>The very first line, <code>#!/usr/bin/env node</code>, is a "shebang." It informs the system to execute the script using the Node.js interpreter. This enables us to run the script directly from the command line without explicitly typing <code>node</code> before the script filename.</li>
<li>Next, we import all the required functions that contain the logic for each command.</li>
<li>The line <code>import { Command } from 'commander'</code> imports the <code>Command</code> class from the Commander.js library. The subsequent line, <code>const program = new Command()</code>, creates an instance of the <code>Command</code> class. This instance is essential for defining and managing commands for our CLI tool.</li>
<li>We then set the CLI tool's information. The <code>.name()</code>, <code>.description()</code>, and <code>.version()</code> methods set the name, description, and version of our CLI tool. These details are displayed when users invoke the tool with specific flags such as <code>--help</code> or <code>--version</code>.</li>
<li>Next, we define the various commands. Each <code>program.command()</code> block defines a command for our CLI tool. Within each block, the command's name is set using a string argument (for example, <code>'add'</code>, <code>'read'</code>). The <code>.description()</code> method provides the command's description, while the <code>.action()</code> method associates a function (for example, <code>addTask</code>, <code>readTask</code>) with a specific command. When a user enters a command in the terminal, this associated function is executed.</li>
<li>The <code>program.parse()</code> line is essential for parsing the command-line arguments provided by the user. Based on the command entered, Commander.js will execute the associated action function.</li>
</ol>
<h1 id="heading-how-to-test-the-cli-tool">How to Test the CLI Tool</h1>
<p>We're now nearly at the finish line of creating our Task Manager CLI tool! Before we can install and utilize the tool, we just need to make a small adjustment to the <code>package.json</code> file. Simply add the following entry to the JSON file:</p>
<pre><code class="lang-json"><span class="hljs-string">"bin"</span>: {
  <span class="hljs-attr">"todo"</span>: <span class="hljs-string">"index.js"</span>
}
</code></pre>
<p>If you've ever built a CLI tool and wish for users to access it from the command line in a manner similar to commands like <code>node</code> or <code>npm</code>, then the "bin" property comes into play.</p>
<p>The "bin" property in the <code>package.json</code> file enables you to specify commands that become globally accessible once your package is installed. In simpler terms, it lets you create shortcuts for running specific scripts or functions from the command line. </p>
<p>The provided code instructs Node.js to execute the script defined in <code>index.js</code> whenever someone enters <code>todo</code> in the terminal. In essence, this transforms your script into a globally accessible command-line tool.</p>
<p>The final step before you can start using the tool is to install it globally on your system! Run the following command to do so:</p>
<pre><code class="lang-text">npm i -g .
</code></pre>
<h1 id="heading-conclusion">Conclusion</h1>
<p>Congratulations if you've followed along this far! You're now fully equipped to begin using your Task Manager CLI tool.</p>
<p>Here are the commands you can use to operate the tool:</p>
<ul>
<li><code>todo add</code>  – Create a new task</li>
<li><code>todo read</code> – Read all your pending tasks</li>
<li><code>todo update</code> – Update a specific task</li>
<li><code>todo delete</code> – Delete a task</li>
</ul>
<p>You can also use these 2 options using the tool:</p>
<ul>
<li><code>todo --version</code> or <code>todo -V</code> – To know the version number of this tool</li>
<li><code>todo --help</code> or <code>todo -h</code> – To display help for command</li>
</ul>
<p>And that wraps up this handbook. I hope you've found it enjoyable and informative. </p>
<p>I encourage you to share your learning journey on Twitter and LinkedIn using the hashtag #LearnInPublic. Also, be sure to follow freeCodeCamp for more informative coding tutorials.</p>
<p>If you are facing any issues while following along with this tutorial, you can always refer to the entire code available on GitHub - <a target="_blank" href="https://github.com/KrishJ4856/task-manager-cli-fcc">Task Manager CLI GitHub Repo</a>. You can also connect with me on Twitter (X) - <a target="_blank" href="https://twitter.com/Krish4856">@Krish4856</a>. My DM's are open!</p>
<p>See you next time! 👋 ❤️ ✨</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ CLI Shells – A Brief History of Human-Computer Interfaces ]]>
                </title>
                <description>
                    <![CDATA[ By Chidiadi Anyanwu A computer is basically a piece of electronic circuitry that performs tasks as instructed by its users. But for a human to interact with this hardware, they must really know and understand how it works. The person must also know t... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/shells-a-history-of-human-computer-interfaces/</link>
                <guid isPermaLink="false">66d45dd7868774922c884fcd</guid>
                
                    <category>
                        <![CDATA[ cli ]]>
                    </category>
                
                    <category>
                        <![CDATA[ command line ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Computer Science ]]>
                    </category>
                
                    <category>
                        <![CDATA[ shell ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chidiadi Anyanwu ]]>
                </dc:creator>
                <pubDate>Tue, 18 Jul 2023 16:45:23 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/07/Evernote-cli-geeknote.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Chidiadi Anyanwu</p>
<p>A computer is basically a piece of electronic circuitry that performs tasks as instructed by its users.</p>
<p>But for a human to interact with this hardware, they must really know and understand how it works. The person must also know the order in which to give the computer various tasks to produce a meaningful result.</p>
<p>But most of us don't know these things. What happened?</p>
<p>In this article, we're going to look at:</p>
<ul>
<li><p>The history of early computing</p>
</li>
<li><p>How operating systems were developed</p>
</li>
<li><p>Modern operating systems</p>
</li>
<li><p>The development of kernels and shells</p>
</li>
<li><p>The history of shells</p>
</li>
<li><p>Why CLI tools are still important</p>
</li>
</ul>
<h2 id="heading-the-early-days-of-computers">The Early Days of Computers</h2>
<p>In the 1800's, computers were mostly used to work on large amounts of numerical data. They were basically programmable calculators the size of small factories.</p>
<p>The data they worked on were also very physical. They were manually fed as punched cards into the machine's card readers. The computers were then programmed by physically rewiring cables, plugboards, and switches to determine what operations would be carried out on the input data. Some of the computers even processed logic through partially mechanical means.</p>
<p>Plugboards allowed you write (or wire) a program and store it so when you needed that program, you'd remove the current plugboard and install a new one. The output of the programs were printed out by line printers, saved on tapes, or punched on cards.</p>
<p>These programming and memory technologies were used in many forms of technological designs.</p>
<p>For example, the Enigma machine which was used to scramble letters and encipher top secret military and diplomatic communication had a plugboard you would rewire to change settings.</p>
<p>The IBM International Daily Dial Attendance Recorder also counted and recorded staff attendance on punched cards. These were more special-purpose equipment.</p>
<h2 id="heading-early-operating-systems">Early Operating Systems</h2>
<p>The first-ever speech synthesis was done in 1962 on an IBM 704, a computer without an operating system.</p>
<p>At the time of the first computers, nobody ever thought of operating systems. People wrote their programs in machine language (or wired them on plugboards) and ran them. There was no way to run two programs at the same time or perform error detection/correction. Your program ran until it either crashed or completed.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/IBM_Electronic_Data_Processing_Machine_-_GPN-2000-001881-IBM-704.jpg" alt="Picture of a man and a woman operating the IBM 704 in 1957." width="600" height="400" loading="lazy"></p>
<p><em>IBM 704 computer with two operators in 1957</em></p>
<p>Initially, computers didn't ship with any software. Later on, IBM included FORTRAN and COBOL compilers in their mainframes. Then came 'resident monitors,' the precursor to operating systems. They literally got the name because they were software that resided in the company's memory and monitored tasks. They basically performed tasks like cleaning up after programs and helping to sequence jobs on the computers.</p>
<p>The first operating systems were made by computer owners who were tired of under-utilizing their computers. They didn't want to have to wait for a program to complete before they manually loaded in another set of data and programs.</p>
<p>One of these early operating systems was the Input/Output System of General Motors and North American Aviation (GM-NAA I/O). Its main aim was to execute the next program automatically after the current one was finished (batch processing). It was created in 1956, and is known as the first-ever working operating system.</p>
<p>IBM later modified one of its customer's operating systems and distributed it as IBSYS. Then, IBM thought that it wasn't good for different computers to have different (machine language) instruction sets, so they stopped all production and started a new line called System/360.</p>
<p>With this, they aimed to build just one operating system for all their computers. It didn't quite go as planned, so they ended up with a family of operating systems, OS/360.</p>
<p>OS/360 included features like time-sharing, error detection/correction, and device management, which are all features implemented in modern operating systems. The OS/360 was introduced in 1964 and was in use until 1977.</p>
<p>Early time-sharing technologies enabled multiple people to access the same mainframe from their different terminals at once. The first computer terminals were teletypewriters (or teletypes). Teletypes were developed around the 1830's but weren't used as computer terminals until the 1970's.</p>
<h2 id="heading-modern-operating-systems">Modern Operating Systems</h2>
<p>The MULTiplexed Information and Computing Service (MULTICS) operating system was initially developed for General Electric's 645 mainframe, and later Honeywell's mainframes when they bought over GE's computer division.</p>
<p>It was jointly developed in 1964 by GE, MIT, and Bell Labs, mainly as a successor to the Compatible Time-Sharing System (CTSS).</p>
<p>It came with a lot of new ideas to the computing world like access control, dynamic linking, support for ASCII, and dynamic reconfiguration.</p>
<p>Dynamic reconfiguration allowed the operators to remove and add memories and CPUs without logging out or disrupting users. With the advent of the Compatible Time-Sharing System (CTSS), computers were looked at as a utility.</p>
<p>The idea was that computers were too big and expensive and could be used by only one person at a time. But what if that expensive hardware could be used more efficiently by allowing more than one person to use them at the same time? That way, the computer could become a public utility, accessed from home with terminals.</p>
<p>With a public utility, you need to be able to carry out maintenance on different components without disrupting the service. That was the usefulness of dynamic reconfiguration.</p>
<p>That idea of computers becoming a public utility seems to have lived on with the use of servers, and now, cloud computing.</p>
<p>At a point, MULTICS didn't work out and the companies went their separate ways. Ken Thompson, one of the programmers, later described MULTICS as 'over-designed,' 'overbuilt,' and close to unusable. In an interview with Peter Seibel, he said that the things he liked enough to take from it were the hierarchical filesystem and the shell.</p>
<p>He later went on to create the UNIX operating system with Dennis Ritchie.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/Timeline-Computers.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-the-development-of-kernels-and-shells">The Development of Kernels and Shells</h2>
<p>One of the major features of the UNIX operating system was the splitting of the operating system into shell and kernel. The OS kernel was then meant to handle all the system calls, while the shell was used to access the system's services without exposing the inner workings of the operating system.</p>
<p>This concept was first introduced by French computer scientist Louis Pouzin in 1965. He also coined the term shell. This is how he described it in a document titled, <a target="_blank" href="https://people.csail.mit.edu/saltzer/Multics/Multics-Documents/MDN/MDN-4.pdf">The SHELL: A Global Tool for Calling and Chaining Procedures in the System</a>.</p>
<blockquote>
<p>We may envision a common procedure called automatically by the supervisor whenever a user types in some message at his console . . . we shall refer to that procedure as the "SHELL".</p>
</blockquote>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/Louis-Pouzin-Shell-Document-Definition.png" alt="Louis Pouzin Shell Document Definitionpng" width="600" height="400" loading="lazy"></p>
<p><em>Louis Pouzin's Document on Shells</em></p>
<p>According to the document, when the user types in a command, the supervisor initiates a new stack to save the command, then calls the shell with the command as an argument.</p>
<p>He also envisioned a future where people created their own shells and used them without having to meddle with the underlying structure of the shell.</p>
<blockquote>
<p>"An important facility is that the SHELL being itself a common procedure may be replaced by a private one supplied by the user. On that way, not only a particular procedure can be replaced on user's choice, but all conventions about typing commands may be tailor-made to user's wishes just by providing his own SHELL."</p>
</blockquote>
<p>This idea was first implemented in MULTICS, and is one of the only things Ken Thompson admitted to liking enough to take from the project.</p>
<p>Now, in modern operating systems, the shell is the software that lets you communicate with the operating system, and access the OS services which are things like program execution, file management, and I/O management. It could be a command-line shell (CLI) or it could be a graphical user interface (GUI).</p>
<p>The kernel is the part that handles all the system calls, manages computer resources like memory, CPU, and storage, and handles timesharing between processes.</p>
<h2 id="heading-history-of-cli-shells">History of CLI Shells</h2>
<p>Shells as we know them now really began with UNIX, and one of the first was the Thompson shell.</p>
<h3 id="heading-thompson-shell-1971">Thompson Shell (1971)</h3>
<p>Ken Thompson, of course, went on to create his own shell. It was a simple command interpreter, not designed for scripting. It introduced the &lt; &gt; symbols for redirecting the input or output of a command, and the | symbol for piping.</p>
<h3 id="heading-c-shell-1978">C Shell (1978)</h3>
<p>The C Shell (or csh) was created by Bill Joy and enabled scripting. The objective was to create a command language that looked more like the C programming language.</p>
<h3 id="heading-bourne-shell-1979">Bourne Shell (1979)</h3>
<p>Stephen Bourne started work on the Bourne Shell in 1976 as a replacement for the Thompson Shell. It was intended as a scripting language. One of the major goals was to enable using scripts as filters.</p>
<h3 id="heading-korn-shell-1983">Korn Shell (1983)</h3>
<p>Developed by David Korn, the Korn Shell was based on source code from the Bourne Shell, and attempted to integrate the features of C Shell and Bourne Shell. It is <a target="_blank" href="https://en.wikipedia.org/wiki/POSIX">POSIX.2</a> compliant.</p>
<h3 id="heading-bourne-again-shell-aka-bash-1989">Bourne Again SHell aka BASH (1989)</h3>
<p>BASH was written by Brian Fox in 1989 and released under the GNU license as a free and open-source version of the Bourne Shell.</p>
<p>It was one of the first programs Linus Torvalds ported to Linux and is the default shell for most Linux distributions. It was built for scripting and is <a target="_blank" href="https://en.wikipedia.org/wiki/POSIX">POSIX</a> compliant.</p>
<h3 id="heading-z-shell-1990">Z Shell (1990)</h3>
<p>The Z Shell (or Zsh) was created by Paul Falstad as an extended and improved Bourne Shell. The name was derived from the name of a teaching assistant at Paul's university named Zhong Shao.</p>
<p>It also enables scripting and is aesthetically superior to Bash. It supports plugins and extensions, auto-completion, and some other globing capabilities that are unavailable in Bash.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/07/Timeline-Shells-1.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>There are many other popular shells used today, but I'm not going to talk about them.</p>
<p>All these shells mentioned did not apply to Windows (until recently). Windows had two command-line shells: Command Prompt (cmd) and PowerShell. The PowerShell was created as an extension to Command Prompt. It supports cmdlets, scripting, piping, and many more features.</p>
<p>Then in 2019, that changed with Windows Terminal, a new terminal emulator for Windows that can run Bash on what is called Windows Subsystem for Linux (WSL). It also supports Command Prompt, PowerShell, and Azure Cloud shell out-of-the-box.</p>
<h2 id="heading-why-are-these-cli-tools-important-when-we-have-graphical-user-interfaces-guis">Why Are These CLI Tools Important When We Have Graphical User Interfaces (GUIs)?</h2>
<p>You may wonder why we would take the pains to go through this whole evolution and yet still love to use CLI tools and shells. What's so interesting about typing commands into terminals?</p>
<p>The first and most compelling reason is that CLI tools are lightweight because they're text-based. In cases where you have servers and other devices where you need to optimize the use of resources, it's not very wise to use up most of the resources to run GUI interfaces.</p>
<p>Using the CLI can also provide you with a great deal of flexibility and speed that you won't get with a GUI. For example, you may want to search for all the images in a folder with 1000 files and rename them in a certain order. Doing that with your GUI will be time-consuming and maybe frustrating. With the CLI, you can just type a few commands.</p>
<p>Scripts are another important feature. Sometimes, there are tasks you want to carry out multiple times and you don't want to have to navigate through menus all the time or even type commands multiple times. You can write scripts that you run to automatically carry out the tasks repetitively.</p>
<p>In cases where you have to access devices remotely, like web servers or network devices, the CLI is also the most favoured method. Also, for some tasks, it's just easier to do them with a few commands than using your GUI—updating your apps on Linux for example.</p>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>We've really come a long way from purely mechanical computers to purely electronic computers. The way we interact with computers has changed over the decades, but command-line interfaces aren't going anywhere soon.</p>
<p>Thanks for reading. I hope you enjoyed this article. You can <a target="_blank" href="https://www.linkedin.com/in/chidiadi-anyanwu">connect with me on LinkedIn.</a></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Contact Book Application in Python using Rich, Typer, and TinyDB ]]>
                </title>
                <description>
                    <![CDATA[ In this Python tutorial, we'll learn how to build a terminal application (CLI app) to manage our contact book.  We'll use Typer for building the CLI app, Rich for a colorized terminal output, and TinyDB for the database. Get your tools ready We'll be... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-contact-book-application-in-python-using-rich-typer-and-tinydb/</link>
                <guid isPermaLink="false">66ba0e77228e16bed602a892</guid>
                
                    <category>
                        <![CDATA[ cli ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ashutosh Krishna ]]>
                </dc:creator>
                <pubDate>Thu, 17 Mar 2022 00:15:43 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/03/contact-book.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this Python tutorial, we'll learn how to build a terminal application (CLI app) to manage our contact book. </p>
<p>We'll use Typer for building the CLI app, Rich for a colorized terminal output, and TinyDB for the database.</p>
<h1 id="heading-get-your-tools-ready"><strong>Get your tools ready</strong></h1>
<p>We'll be using a few external libraries in this project. Let's learn more about them and install them one by one.</p>
<p>But before we install them, let's create a virtual environment and activate it.</p>
<p>We are going to create a virtual environment using <code>virtualenv</code>. Python now ships with a pre-installed <code>virtualenv</code> library. So, to create a virtual environment, you can use the below command:</p>
<pre><code class="lang-bash">$ python -m venv env
</code></pre>
<p>The above command will create a virtual environment named <code>env</code>. Now, we need to activate the environment using the command:</p>
<pre><code class="lang-bash">$ . env/Scripts/activate
</code></pre>
<p>To verify if the environment has been activated or not, you can see <code>(env)</code> in your terminal. Now, we can install the libraries.</p>
<ol>
    <li><a href="https://rich.readthedocs.io/en/latest/"><strong>Rich</strong></a>: Rich is a Python library for writing <em>rich</em> text (with color and style) to the terminal, and for displaying advanced content such as tables, markdown, and syntax highlighted code.<br>
    To install Rich, use the command:
    <pre><code class="language-bash">$ pip install Rich
</code></pre>
    </li>
    <li><a href="https://typer.tiangolo.com/"><strong>Typer</strong></a>: Typer is a library for building CLI applications.<br>
    To install Typer, use the command:
    <pre><code class="language-bash">$ pip install Typer
</code></pre>
    </li>
    <li><a href="https://tinydb.readthedocs.io/"><strong>TinyDB</strong></a>: TinyDB is a document-oriented database written in pure Python with no external dependencies.<br>
    To install TinyDB, use the command:
    <pre><code class="language-bash">$ pip install TinyDB</code></pre>
    </li>
</ol>

<h1 id="heading-features-of-the-contact-book"><strong>Features of the Contact Book</strong></h1>
<p>Our Contact Book application will be a terminal-based application. Similar to a Todo application, we can perform the following operations on it:</p>
<ol>
<li><strong>Add (or Create)</strong>: You can add a new contact in the contact book.</li>
<li><strong>Show (or Read)</strong>: You can see all your contacts saved in the contact book.</li>
<li><strong>Edit (or Update)</strong>: You can edit the contacts saved in the contact book.</li>
<li><strong>Remove (or Delete)</strong>: You can delete the contacts saved in the contact book.  </li>
</ol>
<h1 id="heading-how-to-create-a-contact-model"><strong>How to Create a Contact Model</strong></h1>
<p>First of all, we'll create a custom class or a model for our Contact. Think of all the fields that contact should have. </p>
<p>I can think of these fields – name and contact number. If you can think of more, you can add them to your model. We'll be moving forward with these two for now.</p>
<p>Create a directory called <code>contact_book</code>. Inside that, create a Python file called <code>model.py</code>. Then add the following content in the file:</p>
<pre><code class="lang-py"><span class="hljs-keyword">import</span> datetime

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Contact</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span> (<span class="hljs-params">self, name, contact_number, position=None, date_created=None, date_updated=None</span>):</span>
        self.name = name
        self.contact_number = contact_number
        self.position = position
        self.date_created = date_created <span class="hljs-keyword">if</span> date_created <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">None</span> <span class="hljs-keyword">else</span> datetime.datetime.now().isoformat()
        self.date_updated = date_updated <span class="hljs-keyword">if</span> date_updated <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">None</span> <span class="hljs-keyword">else</span> datetime.datetime.now().isoformat()

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__repr__</span> (<span class="hljs-params">self</span>) -&gt; str:</span>
        <span class="hljs-keyword">return</span> <span class="hljs-string">f"(<span class="hljs-subst">{self.name}</span>, <span class="hljs-subst">{self.contact_number}</span>, <span class="hljs-subst">{self.position}</span>, <span class="hljs-subst">{self.date_created}</span>, <span class="hljs-subst">{self.date_updated}</span>)"</span>
</code></pre>
<p>We have created a class called Contact which takes two mandatory parameters, <code>name</code> and <code>contact_number</code>. </p>
<p>Apart from these two, it also takes three optional parameters: <code>position</code>, <code>date_created</code> and <strong><code>date_updated</code></strong>. If these three optional parameters are not passed, they default to the current index and the current times, respectively.</p>
<p>Further, we have defined the <code>__repr__</code> method that returns the object in a more readable way.</p>
<h1 id="heading-how-to-create-a-database-using-tinydb"><strong>How to Create a Database using TinyDB</strong></h1>
<p>Now, let's set up TinyDB and create a database. If you're new to TinyDB, make sure you check out <a target="_blank" href="https://ireadblog.com/posts/109/getting-started-with-tinydb">this tutorial</a>. </p>
<p>Inside the <code>contact_book</code> directory, create a <code>__init__.py</code> file and add the following content there:</p>
<pre><code class="lang-py"><span class="hljs-keyword">from</span> tinydb <span class="hljs-keyword">import</span> TinyDB, Query

db = TinyDB(<span class="hljs-string">'contact-book.json'</span>)
db.default_table_name = <span class="hljs-string">'contact-book'</span>
ContactQuery = Query()
</code></pre>
<p>We have created an instance of the <em>TinyDB</em> class and passed the filename to it. This will create a JSON file <strong><code>contact-book.json</code></strong> where our data will be stored. To retrieve data from this database, we'll require an instance of the <em>Query</em> class from the <strong><code>tinydb</code></strong> library.</p>
<p>Now, let's define the different functions which we'll be using to interact with the database. Inside the <code>contact_book</code> directory, create a <code>database.py</code> file and add the following content there:</p>
<pre><code class="lang-py"><span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> List
<span class="hljs-keyword">import</span> datetime
<span class="hljs-keyword">from</span> contact_book.model <span class="hljs-keyword">import</span> Contact
<span class="hljs-keyword">from</span> contact_book <span class="hljs-keyword">import</span> db, ContactQuery

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create</span>(<span class="hljs-params">contact: Contact</span>) -&gt; <span class="hljs-keyword">None</span>:</span>
    contact.position = len(db)+<span class="hljs-number">1</span>
    new_contact = {
        <span class="hljs-string">'name'</span>: contact.name,
        <span class="hljs-string">'contact_number'</span>: contact.contact_number,
        <span class="hljs-string">'position'</span>: contact.position,
        <span class="hljs-string">'date_created'</span>: contact.date_created,
        <span class="hljs-string">'date_updated'</span>: contact.date_updated
    }
    db.insert(new_contact)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">read</span>() -&gt; List[Contact]:</span>
    results = db.all()
    contacts = []
    <span class="hljs-keyword">for</span> result <span class="hljs-keyword">in</span> results:
        new_contact = Contact(result[<span class="hljs-string">'name'</span>], result[<span class="hljs-string">'contact_number'</span>], result[<span class="hljs-string">'position'</span>],
                              result[<span class="hljs-string">'date_created'</span>], result[<span class="hljs-string">'date_updated'</span>])
        contacts.append(new_contact)
    <span class="hljs-keyword">return</span> contacts

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">update</span>(<span class="hljs-params">position: int, name: str, contact_number: str</span>) -&gt; <span class="hljs-keyword">None</span>:</span>
    <span class="hljs-keyword">if</span> name <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">None</span> <span class="hljs-keyword">and</span> contact_number <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">None</span>:
        db.update({<span class="hljs-string">'name'</span>: name, <span class="hljs-string">'contact_number'</span>: contact_number},
                  ContactQuery.position == position)
    <span class="hljs-keyword">elif</span> name <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">None</span>:
        db.update({<span class="hljs-string">'name'</span>: name}, ContactQuery.position == position)

    <span class="hljs-keyword">elif</span> contact_number <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">None</span>:
        db.update({<span class="hljs-string">'contact_number'</span>: contact_number},
                  ContactQuery.position == position)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">delete</span>(<span class="hljs-params">position</span>) -&gt; <span class="hljs-keyword">None</span>:</span>
    count = len(db)
    db.remove(ContactQuery.position == position)
    <span class="hljs-keyword">for</span> pos <span class="hljs-keyword">in</span> range(position+<span class="hljs-number">1</span>, count):
        change_position(pos, pos<span class="hljs-number">-1</span>)

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">change_position</span>(<span class="hljs-params">old_position: int, new_position: int</span>) -&gt; <span class="hljs-keyword">None</span>:</span>
    db.update({<span class="hljs-string">'position'</span>: new_position},
              ContactQuery.position == old_position)
</code></pre>
<p>We have defined four different functions – <code>create()</code>, <code>read()</code>, <code>update()</code> and <code>delete()</code> for each of the operations mentioned above. We are using the <code>position</code> attribute to identify particular contact. The <code>change_position()</code> function is responsible for maintaining the position of the contact whenever a contact is deleted.</p>
<h1 id="heading-how-to-create-a-cli-using-typer"><strong>How to Create a CLI using Typer</strong></h1>
<p>Now let's create our CLI using Typer. Outside the <code>contact_book</code> directory, create a <strong><code>main.py</code></strong> file and add the following content.</p>
<pre><code class="lang-py"><span class="hljs-keyword">import</span> typer

app = typer.Typer()

<span class="hljs-meta">@app.command(short_help='adds a contact')</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add</span>(<span class="hljs-params">name: str, contact_number: str</span>):</span>
    typer.echo(<span class="hljs-string">f"Adding <span class="hljs-subst">{name}</span>, <span class="hljs-subst">{contact_number}</span>"</span>)

<span class="hljs-meta">@app.command(short_help='shows all contacts')</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">show</span>():</span>
    typer.echo(<span class="hljs-string">f"All Contacts"</span>)

<span class="hljs-meta">@app.command(short_help='edits a contact')</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">edit</span>(<span class="hljs-params">position: int, name: str = None, contact_number: str = None</span>):</span>
    typer.echo(<span class="hljs-string">f"Editing <span class="hljs-subst">{position}</span>"</span>)

<span class="hljs-meta">@app.command(short_help='removes a contact')</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">remove</span>(<span class="hljs-params">position: int</span>):</span>
    typer.echo(<span class="hljs-string">f"Removing <span class="hljs-subst">{position}</span>"</span>)

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">" __main__"</span>:
    app()
</code></pre>
<p>First of all, we create an instance of the <em>Typer</em> class from the <code>typer</code> library. Then we create four separate functions for the four operations that we discussed above. We bind each of the functions with a command using the <code>@app.command()</code> decorator. We also add <strong><code>short_help</code></strong> just to help the user with the commands.</p>
<p>To add a contact, we require the <code>name</code> and <code>contact_number</code> parameters. To show the contacts, we need nothing. To edit the contact, we definitely need the <strong><code>position</code></strong> whereas the <code>name</code> and <code>contact_number</code> parameters are optional. To remove the contact, we just need the <code>position</code>.</p>
<p>For now, we are not doing any operation inside the methods. We are just printing using the <code>echo</code> method in the <code>typer</code> class. In the main method, we just need to call the <code>app()</code> object.</p>
<p>If you run the application, you will get a similar output:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/03/screenshot-2022-03-04-203615_k9tltn.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h1 id="heading-how-to-style-the-terminal-using-rich"><strong>How to Style the Terminal using Rich</strong></h1>
<p>We want to display the contacts in a beautiful-looking table layout with different colors. Rich can help us with this. If you're new to Rich, make sure you check out <a target="_blank" href="https://ireadblog.com/posts/108/getting-rich-with-python">this tutorial</a>.</p>
<p>Now let's modify the <strong><code>show()</code></strong> function in <code>main.py</code> as it is responsible for printing the contacts on the terminal.</p>
<pre><code class="lang-py"><span class="hljs-keyword">from</span> rich.console <span class="hljs-keyword">import</span> Console
<span class="hljs-keyword">from</span> rich.table <span class="hljs-keyword">import</span> Table

console = Console()

<span class="hljs-meta">@app.command(short_help='shows all contacts')</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">show</span>():</span>
    contacts = [(<span class="hljs-string">"Ashutosh Krishna"</span>, <span class="hljs-string">"+91 1234554321"</span>),
                (<span class="hljs-string">"Bobby Kumar"</span>, <span class="hljs-string">"+91 9876556789"</span>)]

    console.print(<span class="hljs-string">"[bold magenta]Contact Book[/bold magenta]"</span>, <span class="hljs-string">"📕"</span>)

    <span class="hljs-keyword">if</span> len(contacts) == <span class="hljs-number">0</span>:
        console.print(<span class="hljs-string">"[bold red]No contacts to show[/bold red]"</span>)
    <span class="hljs-keyword">else</span>:
        table = Table(show_header=<span class="hljs-literal">True</span>, header_style=<span class="hljs-string">"bold blue"</span>, show_lines=<span class="hljs-literal">True</span>)
        table.add_column(<span class="hljs-string">"#"</span>, style=<span class="hljs-string">"dim"</span>, width=<span class="hljs-number">3</span>, justify=<span class="hljs-string">"center"</span>)
        table.add_column(<span class="hljs-string">"Name"</span>, min_width=<span class="hljs-number">20</span>, justify=<span class="hljs-string">"center"</span>)
        table.add_column(<span class="hljs-string">"Contact Number"</span>, min_width=<span class="hljs-number">12</span>, justify=<span class="hljs-string">"center"</span>)

        <span class="hljs-keyword">for</span> idx, contact <span class="hljs-keyword">in</span> enumerate(contacts, start=<span class="hljs-number">1</span>):
            table.add_row(str(idx), <span class="hljs-string">f'[cyan]<span class="hljs-subst">{contact[<span class="hljs-number">0</span>]}</span>[/cyan]'</span>, <span class="hljs-string">f'[green]<span class="hljs-subst">{contact[<span class="hljs-number">1</span>]}</span>[/green]'</span>)
        console.print(table)
</code></pre>
<p>We have first created an instance of the <em>Console</em> class. Inside the <code>show()</code> method, we have a dummy list of contacts for now. Using the <code>console</code> object, we print the heading in a bold magenta color. </p>
<p>Next, we create a table and add the columns. Now we iterate over the contacts and put them in the table as separate rows with different colors. Then at the end, we print the table.</p>
<h1 id="heading-how-to-connect-database-operations-with-typer-commands"><strong>How to Connect Database Operations with Typer Commands</strong></h1>
<p>Now let's do the final step which is connecting the database operations with the commands. That is, when we run a command, it should interact with the database appropriately.</p>
<pre><code class="lang-py"><span class="hljs-keyword">import</span> typer
<span class="hljs-keyword">from</span> rich.console <span class="hljs-keyword">import</span> Console
<span class="hljs-keyword">from</span> rich.table <span class="hljs-keyword">import</span> Table
<span class="hljs-keyword">from</span> contact_book.model <span class="hljs-keyword">import</span> Contact
<span class="hljs-keyword">from</span> contact_book.database <span class="hljs-keyword">import</span> create, read, update, delete

app = typer.Typer()
console = Console()

<span class="hljs-meta">@app.command(short_help='adds a contact')</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add</span>(<span class="hljs-params">name: str, contact_number: str</span>):</span>
    typer.echo(<span class="hljs-string">f"Adding <span class="hljs-subst">{name}</span>, <span class="hljs-subst">{contact_number}</span>"</span>)
    contact = Contact(name, contact_number)
    create(contact)
    show()

<span class="hljs-meta">@app.command(short_help='shows all contacts')</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">show</span>():</span>
    contacts = read()

    console.print(<span class="hljs-string">"[bold magenta]Contact Book[/bold magenta]"</span>, <span class="hljs-string">"📕"</span>)

    <span class="hljs-keyword">if</span> len(contacts) == <span class="hljs-number">0</span>:
        console.print(<span class="hljs-string">"[bold red]No contacts to show[/bold red]"</span>)
    <span class="hljs-keyword">else</span>:
        table = Table(show_header=<span class="hljs-literal">True</span>,
                      header_style=<span class="hljs-string">"bold blue"</span>, show_lines=<span class="hljs-literal">True</span>)
        table.add_column(<span class="hljs-string">"#"</span>, style=<span class="hljs-string">"dim"</span>, width=<span class="hljs-number">3</span>, justify=<span class="hljs-string">"center"</span>)
        table.add_column(<span class="hljs-string">"Name"</span>, min_width=<span class="hljs-number">20</span>, justify=<span class="hljs-string">"center"</span>)
        table.add_column(<span class="hljs-string">"Contact Number"</span>, min_width=<span class="hljs-number">12</span>, justify=<span class="hljs-string">"center"</span>)

        <span class="hljs-keyword">for</span> idx, contact <span class="hljs-keyword">in</span> enumerate(contacts, start=<span class="hljs-number">1</span>):
            table.add_row(str(
                idx), <span class="hljs-string">f'[cyan]<span class="hljs-subst">{contact.name}</span>[/cyan]'</span>, <span class="hljs-string">f'[green]<span class="hljs-subst">{contact.contact_number}</span>[/green]'</span>)
        console.print(table)

<span class="hljs-meta">@app.command(short_help='edits a contact')</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">edit</span>(<span class="hljs-params">position: int, name: str = None, contact_number: str = None</span>):</span>
    typer.echo(<span class="hljs-string">f"Editing <span class="hljs-subst">{position}</span>"</span>)
    update(position, name, contact_number)
    show()

<span class="hljs-meta">@app.command(short_help='removes a contact')</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">remove</span>(<span class="hljs-params">position: int</span>):</span>
    typer.echo(<span class="hljs-string">f"Removing <span class="hljs-subst">{position}</span>"</span>)
    delete(position)
    show()

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">" __main__"</span>:
    app()
</code></pre>
<p>In the above code, we have used <code>create()</code>, <code>read()</code>, <code>update()</code> and <code>delete()</code> that we created previously.</p>
<h1 id="heading-app-demo"><strong>App Demo</strong></h1>
<p>Here's the demo of the final application:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/KnRG7cMbccE" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<h1 id="heading-conclusion"><strong>Conclusion</strong></h1>
<p>Congratulations! Now you should have a fully functioning CLI app. Go ahead and try different commands to modify your own contact book.</p>
<p>The code is also available on <a target="_blank" href="https://github.com/ashutoshkrris/Contact-Book">GitHub</a>.</p>
<p><a target="_blank" href="https://github.com/ashutoshkrris/Contact-Book">Embedded content</a></p>
<p>If you enjoyed this tutorial, please share it with your friends and subscribe to my newsletter.</p>
<p><a target="_blank" href="https://www.getrevue.co/profile/ashutoshkrris">Embedded content</a></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Delete a Disk or Storage Device in Windows Using the GUI and the CLI ]]>
                </title>
                <description>
                    <![CDATA[ Deleting or formatting a disk or storage device is a common task for most computer users.  If you use the Windows operating system, you would normally do that using the GUI Disk Management application which comes built-in with any Windows operating s... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-format-storage-disk-in-windows-gui-cli/</link>
                <guid isPermaLink="false">66b902df3eae8584882959b7</guid>
                
                    <category>
                        <![CDATA[ cli ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Windows ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Md. Fahim Bin Amin ]]>
                </dc:creator>
                <pubDate>Thu, 20 Jan 2022 20:20:58 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/01/Artboard-1-2.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Deleting or formatting a disk or storage device is a common task for most computer users. </p>
<p>If you use the Windows operating system, you would normally do that using the GUI Disk Management application which comes built-in with any Windows operating system.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/2022-01-20_18-50.png" alt="Image" width="600" height="400" loading="lazy">
<em>Disk Management (GUI)</em></p>
<p>Sometimes, you might have difficulties in deleting a disk using the GUI application, so you'll need to do it using the CLI (Command Prompt) instead. </p>
<p>In this article, I will introduce you to the process of deleting a disk of any kind of storage device using the GUI method and the CLI method directly from the Windows operating system itself. I will be using a 32GB Pendrive of mine as a guinea pig. </p>
<p><strong>Important note:</strong> make sure first that you have copied all of your data to another drive/storage before deleting a disk or storage device.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/IMG_20220120_185541.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>My guinea pig</em></p>
<h2 id="heading-how-to-format-or-delete-a-disk-using-the-gui-graphical-user-interface-method">How to Format or Delete a Disk Using the GUI (Graphical User Interface) Method</h2>
<p>First, open Disk Management. You can do that in multiple ways, but I am going to show you two ways here.</p>
<p>Click on the search icon in the taskbar and type <code>Disk Management</code>. Click on  <code>Create and format hard disk partitions</code>.  </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/2022-01-20_19-07.png" alt="Image" width="600" height="400" loading="lazy">
<em>Search result for Disk Management</em></p>
<p>Alternatively, you can open the control panel and search for <code>Disk Management</code>. You will get <code>Create and format hard disk partitions</code> under the <code>Windows Tools</code>. Click on that.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/2022-01-20_19-36.png" alt="Image" width="600" height="400" loading="lazy">
<em>Search result for Disk Management in the Control Panel</em></p>
<p>The <code>Disk Management</code> tool will open and show all of the storage devices along with their partitions. Here the 3rd option – <strong>Disk 2</strong> – is our guinea pig (32GB Pendrive).</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/2022-01-20_19-33.png" alt="Image" width="600" height="400" loading="lazy">
<em>Our guinea pig in the Disk Management</em></p>
<p>Suppose you want to delete the first partition of <strong>Disk 2</strong>. Then you need to follow these steps:</p>
<p>First, right click on the partition you want to delete. Then click on <code>Delete Volume...</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/Screenshot--81--1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Click on Delete Volume...</em></p>
<p>Make sure that you have copied everything from the entire disk/storage earlier. Then if you have done that already, simply click on <code>Yes</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/2022-01-20_19-43.png" alt="Image" width="600" height="400" loading="lazy">
<em>Prompt windows before deleting a partition or storage</em></p>
<p>Another prompt window might appear saying that it is currently in use. Make sure that no other tasks or applications are using the drive/partition. Then simply click on <code>Yes</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/2022-01-20_19-47.png" alt="Image" width="600" height="400" loading="lazy">
<em>Another prompt</em></p>
<p>Now you'll se that the partition has become unallocated.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/2022-01-20_19-49.png" alt="Image" width="600" height="400" loading="lazy">
<em>After deleting a partition of Disk 2</em></p>
<p>In this way, you can delete any partition. Later you can create a new partition from it or extend the partition with other existing partitions. </p>
<p>To delete a storage/disk completely, you have to delete all of the partitions it has manually, like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/2022-01-20_20-55_1.png" alt="Image" width="600" height="400" loading="lazy">
<em>After deleting all of the partitions of Disk 2</em></p>
<p>After that, you will see that the entire storage/disk has become unallocated. Then if you want to create a new partition, simply right click on the <strong>Unallocated</strong> box and click on <code>New Simple Volume...</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/Screenshot--85-.png" alt="Image" width="600" height="400" loading="lazy">
<em>Creating a new volume</em></p>
<p>Click <code>Next</code> on the wizard.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/2022-01-20_20-58.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Select the size of the volume you want and click on <code>Next</code>. If you want the whole volume size, then simply keep the box as it was.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/2022-01-20_20-59.png" alt="Image" width="600" height="400" loading="lazy">
<em>Volume size</em></p>
<p>Select the drive letter you want and again click on the <code>Next</code> button.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/2022-01-20_21-00.png" alt="Image" width="600" height="400" loading="lazy">
<em>Selecting the Drive Letter</em></p>
<p>Select the file system and volume label. If you do not want to mess up anything here, keep the allocation unit size as <code>Default</code>. Also, if you want a quick format then keep the box beside <code>Perform a quick format</code> checked. Click on the <code>Next</code> button.</p>
<p><strong>Some additional information regarding the File System</strong>: If you want to work on Windows, Linux, and Mac simultaneously, then select FAT32. Just keep in mind that FAT32 does not support more than 4GB in a single file. </p>
<p>To work on Windows and Linux simultaneously while avoiding the 4GB per file limit, NTFS is a good choice.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/2022-01-20_21-02.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Click on <code>Finish</code> and it is done.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/2022-01-20_21-03.png" alt="Image" width="600" height="400" loading="lazy">
<em>Finishing the task</em></p>
<p>Now you've seen how to delete disk/storage via the GUI. Now I will discuss the most exciting part – the <strong>CLI</strong> way!</p>
<h2 id="heading-how-to-delete-a-diskstorage-using-the-cli-command-line-interface-method">How to Delete a Disk/Storage Using the CLI (Command Line Interface) Method</h2>
<p>First, open the Command Prompt or the <strong>CMD</strong>. You can do that by searching the name like in the image below. <strong>Make sure to click on <code>Run as administrator</code>.</strong></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/2022-01-20_19-55.png" alt="Image" width="600" height="400" loading="lazy">
<em>Opening the CMD</em></p>
<p>Alternatively you can open the CMD using the Run window. Simply press the <code>Win</code> + <code>R</code> keys. Type <code>cmd</code> and press the Enter key.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/2022-01-20_19-57.png" alt="Image" width="600" height="400" loading="lazy">
<em>Open the CMD using Run</em></p>
<p>After opening the CMD, we need to open the DiskPart. According to Google, here's a definition of the Diskpart:</p>
<blockquote>
<p>The diskpart command interpreter helps you manage your computer's drives (disks, partitions, volumes, or virtual hard disks).</p>
</blockquote>
<p>To open DiskPart, type the command <code>diskpart</code> and hit Enter on your keyboard.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/2022-01-20_19-59.png" alt="Image" width="600" height="400" loading="lazy">
<em>Open the DiskPart</em></p>
<p>Now, we need to check the available disks. For that, type the command <code>list disk</code>. It will show all of the drives we have in our computer.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/2022-01-20_20-03.png" alt="Image" width="600" height="400" loading="lazy">
<em>list disk</em></p>
<p>You can see that I have 3 disks or storage devices in my workstation right now. Those are classified as <code>Disk 0</code>, <code>Disk 1</code> and <code>Disk 2</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/2022-01-20_20-05.png" alt="Image" width="600" height="400" loading="lazy">
<em>Disk Status</em></p>
<p>You can also see the other stats like the <strong>Status</strong> of the disks, the <strong>Size</strong> of the disks, how much <strong>free space</strong> they have, whether the disk has become <strong>Dynamic</strong> or not, and the partition table (for my case, it is GPT or GUID Partition Table, stated as <code>Gpt</code> ) also. </p>
<p>Keep in mind that if your disk is on MBR, then you won't face any problems in the following process. If your disk is on GPT like me, then you'll get an error – but fear not! I'll show how you can solve the error and complete the rest of the task as well. </p>
<p>Here, as I am still working with my 32GB Pendrive as my guinea pig, <code>Disk 2</code> is my target Disk. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/2022-01-20_20-09.png" alt="Image" width="600" height="400" loading="lazy">
<em>Target Disk</em></p>
<p>So I will simply select the target disk (The disk which I want to delete).</p>
<p>To select the disk, you need to type the command <code>select disk 2</code>. Then simply hit the Enter key. </p>
<p>Here you need to type the disk number that you want to delete. Like, if your target disk is <code>3</code>, then you need to use the command <code>select disk 3</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/2022-01-20_20-11.png" alt="Image" width="600" height="400" loading="lazy">
<em>Selecting the target disk</em></p>
<p>The target disk has been selected!</p>
<p>Now, you need to delete the entire disk. For that, simply type the command <code>clean</code>. This will delete all the partitions the target disk has.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/Screenshot--82-.png" alt="Image" width="600" height="400" loading="lazy">
<em>Cleaning the disk</em></p>
<p>You will get the confirmation that DiskPart has successfully cleaned the target disk.</p>
<p>Remember that we can't use the drive yet. </p>
<p>To create a primary partition, type the command <code>create partition primary</code> and hit Enter.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/2022-01-20_20-23.png" alt="Image" width="600" height="400" loading="lazy">
<em>Creating primary partition</em></p>
<p>We will get a confirmation that it has succeeded in creating the specified partition.</p>
<p>Now we need to make the partition active. First, we need to select the partition. As we have created only one partition, then we have only <code>Partition 1</code>. So I will select the 1st Partition using the command <code>select partition 1</code> and hit Enter.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/2022-01-20_20-27.png" alt="Image" width="600" height="400" loading="lazy">
<em>Selecting the partition</em></p>
<p>Now we need to make the partition active. For that, type the command <code>active</code> and press Enter. You will receive the marked error only if your disk is also on a GPT partition table like me.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/2022-01-20_20-31.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>This happens because the <code>active</code> command is not applicable for GPT disks. We can simply convert the entire disk to MBR, and later apply the <code>active</code> command.</p>
<p>If your disk is on MBR, then you won't get the error message. You can skip the next few steps if you do not get the error message in this step.</p>
<p>To convert the disk to MBR, first of all we need to clean (delete all the partitions) the disk first. So type <code>clean</code> and press Enter.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/2022-01-20_20-33.png" alt="Image" width="600" height="400" loading="lazy">
<em>Clean command</em></p>
<p>Now, to convert the partition table from the MBR to the GPT, type <code>convert MBR</code> and press Enter.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/2022-01-20_20-36.png" alt="Image" width="600" height="400" loading="lazy">
<em>Converting to MBR</em></p>
<p>To create a primary partition, enter the command and hit Enter <code>create partition primary</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/2022-01-20_20-38.png" alt="Image" width="600" height="400" loading="lazy">
<em>Creating primary partition</em></p>
<p>Now select the partition by entering the command <code>select partition 1</code> and press Enter.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/2022-01-20_20-39.png" alt="Image" width="600" height="400" loading="lazy">
<em>Selecting the partition</em></p>
<p>Now you need to make the partition active. Simply type <code>active</code> and press Enter.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/2022-01-20_20-41.png" alt="Image" width="600" height="400" loading="lazy">
<em>Making the partition active</em></p>
<p>Alright <strong>this is where you'll pick back up</strong> if you didn't get the error.</p>
<p>Now we need to format the partition. Suppose I want the <strong>NTFS</strong> file system in formatting the disk. I can also add a label here.</p>
<p>Type the command <code>format fs=NTFS label=my_guinea_pig quick</code> and hit Enter . Here the <code>fs</code> indicates the file system, and <code>quick</code> indicates that I want the quick formatting here. </p>
<p>You can add any label you want, but make sure not to leave any space between multiple words in the label name (you can use the underscore if you want). </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/Screenshot--83-.png" alt="Image" width="600" height="400" loading="lazy">
<em>Quick formatting the disk</em></p>
<p>Now we're done, and we can close the <strong>CMD</strong> as well. Simply type <code>exit</code> and hit enter.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/Screenshot--84-.png" alt="Image" width="600" height="400" loading="lazy">
<em>Exiting the CMD</em></p>
<p>Now, if I check the USB drive using my file explorer, I will see that my drive is formatted exactly the way I wanted.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/2022-01-20_20-46.png" alt="Image" width="600" height="400" loading="lazy">
<em>Task has been finished</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/2022-01-20_20-47.png" alt="Image" width="600" height="400" loading="lazy">
<em>Disk Properties</em></p>
<p>Now you know how to delete and reformat disk/storage via the CLI. </p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Thanks for reading the entire article. If it helps you then you can also check out other articles of mine at <a target="_blank" href="https://www.freecodecamp.org/news/author/fahimbinamin/">freeCodeCamp</a>.</p>
<p>If you want to get in touch with me, then you can do so using <a target="_blank" href="https://twitter.com/Fahim_FBA">Twitter</a>, <a target="_blank" href="https://www.linkedin.com/in/fahimfba/">LinkedIn</a>, and <a target="_blank" href="https://github.com/FahimFBA">GitHub</a>. </p>
<p>You can also <a target="_blank" href="https://www.youtube.com/@FahimAmin?sub_confirmation=1">SUBSCRIBE to my YouTube channel</a> (Code With FahimFBA) if you want to learn various kinds of programming languages with a lot of practical examples regularly.</p>
<p>If you want to check out my highlights, then you can do so at my <a target="_blank" href="https://www.polywork.com/fahimbinamin">Polywork timeline</a>.</p>
<p>You can also <a target="_blank" href="https://fahimbinamin.com/">visit my website</a> to learn more about me and what I'm working on.</p>
<p>Thanks a bunch!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Angular Command-line Interface Explained ]]>
                </title>
                <description>
                    <![CDATA[ Angular is closely associated with its command-line interface (CLI). The CLI streamlines generation of the Angular file system. It deals with most of the configuration behind the scenes so developers can start coding. The CLI also has a low learning ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/angular-command-line-interface-explained/</link>
                <guid isPermaLink="false">66c344a5f9d371e3aae26824</guid>
                
                    <category>
                        <![CDATA[ Angular ]]>
                    </category>
                
                    <category>
                        <![CDATA[ cli ]]>
                    </category>
                
                    <category>
                        <![CDATA[ command line ]]>
                    </category>
                
                    <category>
                        <![CDATA[ toothbrush ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Sat, 01 Feb 2020 00:00:00 +0000</pubDate>
                <media:content url="https://cdn-media-2.freecodecamp.org/w1280/5f9c9d07740569d1a4ca3582.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Angular is closely associated with its command-line interface (CLI). The CLI streamlines generation of the Angular file system. It deals with most of the configuration behind the scenes so developers can start coding. The CLI also has a low learning curve recommendable for any newcomer wanting to jump right in. Heck, even experienced Angular developers rely on the CLI!</p>
<h2 id="heading-installation">Installation</h2>
<p>The Angular CLI requires <a target="_blank" href="https://nodejs.org/en/">Node.js and Node Packet Manager (NPM)</a>. You can check for these programs with the terminal command: <code>node -v; npm -v</code>. Once installed, open a terminal and install the Angular CLI with this command: <code>npm install -g @angular/cli</code>. This can executed from anywhere on your system. The CLI is configured for global use with the <code>-g</code> flag.</p>
<p>Verify the CLI is there with the command: <code>ng -v</code>. This outputs several lines of information. One of these lines state the version of the installed CLI.</p>
<p>Recognize that <code>ng</code> is the basic building block of the CLI. All your commands will begin with <code>ng</code>. Time to take a look at four of the most common commands prefixed with <code>ng</code>.</p>
<h2 id="heading-key-commands">Key Commands</h2>
<ul>
<li>ng new</li>
<li>ng serve</li>
<li>ng generate</li>
<li>ng build</li>
<li>ng update</li>
</ul>
<p>The key terms for each of these are quite telling. Together, they comprise what you will need to hit the ground running with Angular. Of course, there are many more. All commands are outlined in the <a target="_blank" href="https://github.com/angular/angular-cli/wiki#additional-commands">CLI’s GitHub Documentation<sup>1</sup></a>. You will likely find that the commands listed above will cover the necessary bases.</p>
<h3 id="heading-ng-new">ng new</h3>
<p><code>ng new</code> creates a <em>new</em> Angular file system. This is a surreal process. Please navigate to a file location desirable for <em>new</em> application generation. Type this command as follows, replacing <code>[name-of-app]</code> with whatever you want: <code>ng new [name-of-app]</code>.</p>
<p>A file system under the folder <code>[name-of-app]</code> should appear. Feel free to explore what lies within. Try to not make any changes yet. All of what you need to run your first Angular application comes packaged together in this generated file system.</p>
<h3 id="heading-ng-serve">ng serve</h3>
<p>To get the application running, the <code>ng serve</code> command must execute within the <code>[name-of-app]</code> folder. Anywhere within the folder will do. The Angular CLI must recognize that it is within an environment generated with <code>ng new</code>. It will run provided this one condition. Go ahead and try it: <code>ng serve</code>.</p>
<p>The application runs on port 4200 by default. You can view the Angular application by navigating to <code>localhost:4200</code> in any web browser. Angular works across all browsers. Unless you are using an old version of Internet Explorer, the application will pop up. It displays the official Angular logo alongside a list of helpful links.</p>
<p>Ok, the application runs. It hopefully functions, but you need to know what is going on under the hood. Refer back to the <code>[name-of-app]</code> file system. Navigate <code>[name-of-app] -&gt; src -&gt; app</code>. Therein lies the files responsible for what you saw on <code>localhost:4200</code>.</p>
<h3 id="heading-ng-generate">ng generate</h3>
<p>The <code>.component</code> files define an Angular component including its logic (<code>.ts</code>), style (<code>.css</code>), layout (<code>.html</code>), and testing (<code>.spec.ts</code>). The <code>app.module.ts</code> particularly stands out. Together, these two groups of files work together as <code>component</code> and <code>module</code>. Both <code>component</code> and <code>module</code> are two separate examples of Angular schematics. Schematics classify the different purpose-driven blocks of code <em>generatable</em> with <code>ng generate</code>.</p>
<p>For the sake of this article, understand that a <code>module</code> exports and imports assets to and from an underlying component tree. A <code>component</code> concerns itself with one section of the user interface. That unit’s logic, style, layout, and testing stays encapsulated within the various <code>.component</code> files.</p>
<p>As for <code>ng generate</code>, this command can generate skeletons for each of the available <a target="_blank" href="https://github.com/angular/angular-cli/wiki/generate#available-schematics">Angular schematics<sup>2</sup></a>. Navigate to <code>[name-of-app -&gt; src -&gt; app]</code>. Try generating a new <code>component</code> by executing: <code>ng generate component [name-of-component]</code>. Replace <code>[name-of-component]</code> with whatever you would like. A new file <code>[name-of-component]</code> will pop up along with its necessary <code>component</code> files.</p>
<p>You can see that <code>ng generate</code>expedites Angular’s <a target="_blank" href="https://en.wikipedia.org/wiki/Boilerplate_code">boilerplate code</a>. <code>ng generate</code> also wires things up. Schematics created within context of an Angular file system connect with the system’s root module. In this case, that would be <code>app.module.ts</code> file inside <code>[name-of-app -&gt; src -&gt; app]</code>.</p>
<h3 id="heading-ng-build">ng build</h3>
<p>Angular is a front end tool. The CLI performs its operations on behalf of the front end. <code>ng serve</code> takes care of the back end server setup. This keeps development entirely focused on the front end. That said, connecting your own back end to the Angular application must also be possible.</p>
<p><code>ng build</code> fulfills this need. Before trying it out inside of the file system. Navigate to <code>[name-of-app] -&gt; angular.json</code>. Look for this single line of code: <code>"outputPath": "dist/my-app"</code>.</p>
<p>This one line of configuration determines where <code>ng build</code> dumps its results. The results being the entire Angular application compiled into one folder <code>dist/my-app</code>. Inside of that folder, there exists <code>index.html</code>. The whole Angular application can run with <code>index.html</code>. No <code>ng serve</code> is necessary from here. With this file, you can easily wire up your back end.</p>
<p>Give it a go: <code>ng build</code>. Again, this must execute within the Angular file system. Based of the key value of <code>“outputPath:”</code> in <code>angular.json</code>. A file will generate wherein the original application is fully compiled. If you kept <code>“outputPath:”</code> the same, the compiled application will be in: <code>[name-of-app] -&gt; dist -&gt; [name-of-app]</code>.</p>
<h3 id="heading-ng-update">ng update</h3>
<p>In angular cli ng update do automatic updation on all the angular and npm packages to latest versions.</p>
<p>Here is the syntax and options can be used with <code>ng update</code>.</p>
<p><code>ng update [package]</code></p>
<h3 id="heading-options">Options</h3>
<ul>
<li>dry-run <code>--dry-run (alias: -d)</code><br>Run through without making any changes.</li>
<li>force <code>--force</code><br>If false, will error out if installed packages are incompatible with the update.</li>
<li>all <code>--all</code><br>Whether to update all packages in package.json.</li>
<li>next <code>--next</code><br>Use the largest version, including beta and RCs.</li>
<li>migrate-only <code>--migrate-only</code><br>Only perform a migration, does not update the installed version.</li>
<li>from <code>--from</code><br>Version from which to migrate from. Only available with a single package being updated, and only on migration only.</li>
<li>to <code>--to</code><br>Version up to which to apply migrations. Only available with a single package being updated, and only on migrations only. Requires from to be specified. Default to the installed version detected.</li>
<li>registry <code>--registry</code><br>The NPM registry to use.</li>
</ul>
<p>These commands cover the basics. Angular’s CLI is an incredible convenience that expedites application generation, configuration, and expansion. It does all this while maintaining flexibility, allowing the developer to make necessary changes.</p>
<p>Please check out those links on <code>localhost:4200</code> if you have not already. Do not forget to run <code>ng serve</code> before opening it up. With a better understanding of the CLI, you are now ready to learn more about what is generated by its most essential commands.</p>
<h2 id="heading-more-information">More information:</h2>
<ul>
<li><a target="_blank" href="https://www.freecodecamp.org/news/the-best-angular-examples/">The Best Angular Examples</a></li>
<li><a target="_blank" href="https://www.freecodecamp.org/news/best-angular-tutorial-angularjs/">The Best Angular and AngularJS Tutorials</a></li>
<li><a target="_blank" href="https://www.freecodecamp.org/news/how-to-validate-angular-reactive-forms/">How to Validate Angular Reactive Forms</a></li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Helpful terminal tips ]]>
                </title>
                <description>
                    <![CDATA[ By Leonardo Faria A while ago I started a thread on Twitter with a few terminal tips. There are lots of command line interfaces in NPM and they can be very handy in our daily work.  Here they are. If you like them, you can follow me on twitter ]]>
                </description>
                <link>https://www.freecodecamp.org/news/terminal-tips-tweets/</link>
                <guid isPermaLink="false">66d8517e8acc348be2a441b4</guid>
                
                    <category>
                        <![CDATA[ cli ]]>
                    </category>
                
                    <category>
                        <![CDATA[ npm ]]>
                    </category>
                
                    <category>
                        <![CDATA[ terminal ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Twitter ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 20 Jan 2020 14:43:26 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/01/twitter.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Leonardo Faria</p>
<p>A while ago I started a <a target="_blank" href="https://twitter.com/leozera/status/1090639374109138946">thread on Twitter</a> with a few terminal tips. There are lots of command line interfaces in NPM and they can be very handy in our daily work. </p>
<p>Here they are. If you like them, you can <a target="_blank" href="https://twitter.com/leozera">follow me</a> on twitter for more tips :)</p>
<div class="embed-wrapper">
        <blockquote class="twitter-tweet">
          <a href="https://twitter.com/leozera/status/1090639457118609408"></a>
        </blockquote>
        <script defer="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></div>
<div class="embed-wrapper">
        <blockquote class="twitter-tweet">
          <a href="https://twitter.com/leozera/status/1090639526840549382"></a>
        </blockquote>
        <script defer="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></div>
<div class="embed-wrapper">
        <blockquote class="twitter-tweet">
          <a href="https://twitter.com/leozera/status/1090639627822628865"></a>
        </blockquote>
        <script defer="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></div>
<div class="embed-wrapper">
        <blockquote class="twitter-tweet">
          <a href="https://twitter.com/leozera/status/1090639691135610881"></a>
        </blockquote>
        <script defer="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></div>
<div class="embed-wrapper">
        <blockquote class="twitter-tweet">
          <a href="https://twitter.com/leozera/status/1090639798354665472"></a>
        </blockquote>
        <script defer="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></div>
<div class="embed-wrapper">
        <blockquote class="twitter-tweet">
          <a href="https://twitter.com/leozera/status/1090639885931753475"></a>
        </blockquote>
        <script defer="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></div>
<div class="embed-wrapper">
        <blockquote class="twitter-tweet">
          <a href="https://twitter.com/leozera/status/1090640016970117120"></a>
        </blockquote>
        <script defer="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></div>
<div class="embed-wrapper">
        <blockquote class="twitter-tweet">
          <a href="https://twitter.com/leozera/status/1093949292040019968"></a>
        </blockquote>
        <script defer="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></div>
<div class="embed-wrapper">
        <blockquote class="twitter-tweet">
          <a href="https://twitter.com/leozera/status/1180601156889804801"></a>
        </blockquote>
        <script defer="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></div>
<div class="embed-wrapper">
        <blockquote class="twitter-tweet">
          <a href="https://twitter.com/leozera/status/1215393238720188416"></a>
        </blockquote>
        <script defer="" src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></div>
<p>Photo credit: <a target="_blank" href="https://unsplash.com/photos/HAIPJ8PyeL8">Unsplash</a> </p>
<p><em>Also posted on <a target="_blank" href="http://bit.ly/3arn5Fl">my blog</a>. If you like this content, follow me on <a target="_blank" href="https://twitter.com/leozera">Twitter</a> and <a target="_blank" href="https://github.com/leonardofaria">GitHub</a>.</em></p>
<p>By the way - Thinkific is <a target="_blank" href="https://bit.ly/thnk-senior-front-end-eng">hiring</a> <a target="_blank" href="https://bit.ly/thnk-senior-full-stack-eng">for</a> <a target="_blank" href="https://www.thinkific.com/careers/">several positions</a> if you are interested.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
