<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Cris Mihalache</title>
    <description>The latest articles on DEV Community by Cris Mihalache (@f3rnox).</description>
    <link>https://dev.to/f3rnox</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3864439%2Fc7316899-0147-4001-9123-96a6db351b94.png</url>
      <title>DEV Community: Cris Mihalache</title>
      <link>https://dev.to/f3rnox</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/f3rnox"/>
    <language>en</language>
    <item>
      <title>Productivity Perfected: How I Wrapped Up the Super Time Tracker UI</title>
      <dc:creator>Cris Mihalache</dc:creator>
      <pubDate>Fri, 29 May 2026 02:32:54 +0000</pubDate>
      <link>https://dev.to/f3rnox/productivity-perfected-how-i-wrapped-up-the-super-time-tracker-ui-kkh</link>
      <guid>https://dev.to/f3rnox/productivity-perfected-how-i-wrapped-up-the-super-time-tracker-ui-kkh</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/github-2026-05-21"&gt;GitHub Finish-Up-A-Thon Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Hey there! I'm thrilled to share what I've been working on: the "Super Time Tracker UI." It's a Next.js application designed to make tracking your time not just efficient, but genuinely helpful for boosting your productivity. I poured a lot of effort into making it intuitive and powerful, leveraging modern web tech for a really smooth experience.&lt;/p&gt;

&lt;p&gt;Here’s a peek at what it can do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Time Tracking:&lt;/strong&gt; Effortlessly check in and out of tasks, with easy ways to edit and manage all your entries.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Tagging and Organization:&lt;/strong&gt; Keep everything organized with tags, making it simple to search and analyze your time.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Detailed Reporting:&lt;/strong&gt; Dive deep into your productivity with comprehensive reports and analytics based on your tracked time.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;User Authentication:&lt;/strong&gt; A secure system to get you started and keep your data safe.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Customizable Settings:&lt;/strong&gt; Personalize everything from display preferences and data management (like backup/restore and export) to focus goals, notifications, and even a Pomodoro timer.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Cloud Synchronization:&lt;/strong&gt; Built-in cloud integration means your data is always there, across all your devices, and safely backed up.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Command Palette:&lt;/strong&gt; Need to do something fast? Our central command palette gives you quick access to just about everything.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Pomodoro Timer:&lt;/strong&gt; A handy built-in Pomodoro timer to help you stay focused and manage your work breaks.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Notes and Templates:&lt;/strong&gt; Add detailed notes to your entries and create reusable templates for those tasks you do all the time.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Offline Support:&lt;/strong&gt; Life happens! You can still track your time even when you're not connected to the internet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Under the hood, the app has dedicated API routes for all its data magic (&lt;code&gt;app/api&lt;/code&gt;), a rich collection of beautiful, reusable UI components (&lt;code&gt;components&lt;/code&gt;), and a solid library of utility functions (&lt;code&gt;lib&lt;/code&gt;) that handle everything from data crunching to making sure your UI preferences are just right.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://npmjs.org/packages/super-time-tracker-ui" rel="noopener noreferrer"&gt;npm&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/f3rnox/super-time-tracker-ui" rel="noopener noreferrer"&gt;Github&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn80ca4rlpr7xkt3shrc0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn80ca4rlpr7xkt3shrc0.png" alt="Tracker page" width="799" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpksc70cy79xn50g9cf4w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpksc70cy79xn50g9cf4w.png" alt="Reports page" width="800" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Comeback Story
&lt;/h2&gt;

&lt;p&gt;This project's journey, especially during the GitHub Finish-Up-A-Thon Challenge, has been incredibly rewarding. The "Super Time Tracker UI" had a really strong foundation, but it needed that extra push—that final polish and those crucial features—to truly feel complete.&lt;/p&gt;

&lt;p&gt;My main focus for this "comeback" was on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Making it Feel Great to Use:&lt;/strong&gt; I spent a lot of time improving existing UI components like &lt;code&gt;entry-list-item.tsx&lt;/code&gt;, &lt;code&gt;check-in-form.tsx&lt;/code&gt;, and &lt;code&gt;active-entry-panel.tsx&lt;/code&gt;. The goal was to make them more intuitive, responsive, and visually appealing, ensuring a consistent and delightful experience throughout the app.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Smarter Data Management:&lt;/strong&gt; The cloud synchronization feature (&lt;code&gt;cloud-sync-provider.tsx&lt;/code&gt;, &lt;code&gt;cloud-sync-settings-view.tsx&lt;/code&gt;) got a major overhaul to make it even more reliable and speedy. I also built out robust backup and restore functionalities (&lt;code&gt;backup-restore-setting.tsx&lt;/code&gt;), plus comprehensive data export options (&lt;code&gt;build_csv_export.ts&lt;/code&gt;, &lt;code&gt;build_json_export.ts&lt;/code&gt;, &lt;code&gt;build_markdown_export.ts&lt;/code&gt;) so you have complete control over your data.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Brand New Productivity Tools:&lt;/strong&gt; I added a bunch of new features designed to supercharge your productivity. This included expanding the reporting capabilities in &lt;code&gt;reporting/page.tsx&lt;/code&gt; with even more detailed analytics, implementing a clever &lt;code&gt;notification-rules-runner.tsx&lt;/code&gt; for customizable reminders, and making the Pomodoro timer in &lt;code&gt;pomodoro-view.tsx&lt;/code&gt; even more integrated and useful.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Boosting Performance and Stability:&lt;/strong&gt; A big chunk of effort went into finding and fixing anything that slowed things down, especially in areas with lots of data like &lt;code&gt;entry-list.tsx&lt;/code&gt; and &lt;code&gt;reporting/page.tsx&lt;/code&gt;. I squashed a lot of bugs and made stability improvements across the board, all to ensure a smooth and reliable experience.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Clean Code for the Future:&lt;/strong&gt; I also focused on the overall health of the code, refactoring complex logic in the &lt;code&gt;lib/&lt;/code&gt; utilities and making sure we stuck to best practices. This makes the application much easier to maintain and build upon down the road.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All these efforts transformed the "Super Time Tracker UI" from a promising prototype into a fully-fledged, feature-rich application that's ready to be a part of your daily routine.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Experience with GitHub Copilot
&lt;/h2&gt;

&lt;p&gt;Honestly, GitHub Copilot was like having an expert co-pilot sitting right next to me throughout this whole challenge. Its ability to grasp what I was trying to do and instantly suggest relevant code really sped up development in so many areas.&lt;/p&gt;

&lt;p&gt;Here's how Copilot became my secret weapon:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Quick Starts:&lt;/strong&gt; Whenever I needed a new component (&lt;code&gt;components/*.tsx&lt;/code&gt;), API route (&lt;code&gt;app/api/**/*.ts&lt;/code&gt;), or utility function (&lt;code&gt;lib/*.ts&lt;/code&gt;), Copilot was there, quickly dishing out the initial boilerplate. It saved me so much time on those repetitive setup tasks.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Anticipating My Needs:&lt;/strong&gt; While I was busy polishing features or building new ones, Copilot's intelligent code completion was incredible. It suggested function calls, variable names, and logical structures, often knowing exactly what I was about to type next, especially with data manipulation and UI logic.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Help with Tidying Up:&lt;/strong&gt; When I was refactoring more complex parts of the application, Copilot would chime in with suggestions for cleaner code, more efficient algorithms, and better variable names. It genuinely helped me write better, more maintainable code.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;A Personal Tutor:&lt;/strong&gt; If I was dabbling with a new pattern or integrating a library I wasn't super familiar with, Copilot was like an instant reference. It gave me helpful examples and usage patterns, significantly flattening the learning curve.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Catching Potential Issues:&lt;/strong&gt; By suggesting common patterns and best practices, Copilot helped me write more robust code, which I'm sure prevented some bugs from even appearing. And when issues did pop up, its context-aware suggestions often nudged me towards the right fix.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Even Testing (if I had asked!):&lt;/strong&gt; While I didn't explicitly ask it to generate tests this time, I know Copilot's knack for creating test cases based on existing code would have been incredibly useful for ensuring everything new and modified was rock-solid.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Essentially, Copilot felt like an extension of my own thinking, offering lightning-fast prototyping and smart assistance that let me focus on the bigger picture—the architecture, the user experience, and ultimately, bringing the "Super Time Tracker UI" to its complete and polished state right on schedule for the challenge.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>githubchallenge</category>
    </item>
    <item>
      <title>I shipped super-time-tracker-ui, a local web UI for my CLI time sheets</title>
      <dc:creator>Cris Mihalache</dc:creator>
      <pubDate>Thu, 28 May 2026 19:37:42 +0000</pubDate>
      <link>https://dev.to/f3rnox/i-shipped-super-time-tracker-ui-a-local-web-ui-for-my-cli-time-sheets-574a</link>
      <guid>https://dev.to/f3rnox/i-shipped-super-time-tracker-ui-a-local-web-ui-for-my-cli-time-sheets-574a</guid>
      <description>&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;If you live in the terminal but still wish your time sheets had a real interface, I built the thing for you. It's called &lt;a href="http://npmjs.org/package/super-time-tracker-ui" rel="noopener noreferrer"&gt;super-time-tracker-ui&lt;/a&gt; and it's now on npm.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; super-time-tracker-ui
stt-ui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. The stt-ui command boots a local server and opens your browser to a full time tracking dashboard. No accounts required, no cloud required, no setup wizard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk9ioz7um7icgydzdwgs6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk9ioz7um7icgydzdwgs6.png" alt="super-time-tracker-ui" width="799" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I made it
&lt;/h2&gt;

&lt;p&gt;I have a CLI called &lt;a href="http://npmjs.org/package/super-time-tracker" rel="noopener noreferrer"&gt;super-time-tracker&lt;/a&gt; that stores everything in a plain JSON file at ~/.super-time-tracker/db.json. The CLI is great for quick check ins and check outs, but reviewing a month of work in a terminal is rough. Pie charts in ASCII only get you so far.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F22stsjzgn8t8pe2qbu2j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F22stsjzgn8t8pe2qbu2j.png" alt="super-time-tracker" width="788" height="554"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So instead of bolting a clunky reporting command onto the CLI, I made a web UI that reads the exact same database file. Same data, two front ends. Track from the terminal, review in the browser, and they stay in sync because they're literally pointing at the same file. When the UI loads, it even migrates and dedupes the database on the fly so old data keeps working.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it actually does
&lt;/h2&gt;

&lt;p&gt;The home screen is the tracker. You check in with a description and inline @tags, watch the live timer, and check out when you're done. Entries pile up under the active sheet and you can do real work on them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Edit, move between sheets, split, and merge entries&lt;/li&gt;
&lt;li&gt;Archive things you want out of the way without deleting them&lt;/li&gt;
&lt;li&gt;Bulk move or bulk delete a whole selection at once&lt;/li&gt;
&lt;li&gt;Resume a past entry with one click&lt;/li&gt;
&lt;li&gt;Attach, edit, and delete timestamped notes on any entry&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sheets work like separate timesheets. Create them, rename them, archive them, switch between them with keyboard shortcuts. Tag filtering supports both "match any" and "match all" modes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The reporting dashboard
&lt;/h2&gt;

&lt;p&gt;This is the part the CLI could never do well. There's a proper analytics view with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A tag pie chart so you can see where the hours actually went&lt;/li&gt;
&lt;li&gt;Daily bar charts and weekday breakdowns&lt;/li&gt;
&lt;li&gt;An activity heatmap, the GitHub contributions style grid&lt;/li&gt;
&lt;li&gt;Trend cards that compare periods&lt;/li&gt;
&lt;li&gt;A month in review summary&lt;/li&gt;
&lt;li&gt;Flexible date range picking with handy shortcuts&lt;/li&gt;
&lt;li&gt;Focus goals and gentle nudges&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ki8lmjn418wpzvzsv1j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ki8lmjn418wpzvzsv1j.png" alt="The reporting dashboard" width="800" height="399"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can set a weekly focus target, define your working hours, and pick a focus sheet. If you forget to log anything for a while, the app nudges you. If you're slacking on your target, it lets you know without being annoying about it. It's the accountability buddy that doesn't judge.&lt;/p&gt;

&lt;h2&gt;
  
  
  A built in Pomodoro timer
&lt;/h2&gt;

&lt;p&gt;There's a real Pomodoro view with configurable work and break cycles. It ties into the tracker, so a focus session and a tracked entry can move together. Desktop notifications fire when a phase ends, so you can stop staring at the countdown.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI descriptions, your key, your call
&lt;/h2&gt;

&lt;p&gt;Writing entry descriptions gets boring. The app can generate a concise description for you from your current text and any notes you attached. You bring your own API key and pick the provider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OpenAI&lt;/li&gt;
&lt;li&gt;Claude&lt;/li&gt;
&lt;li&gt;Google AI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keys live in your own preferences and the request goes straight to the provider you chose. Skip this entirely if it's not your thing. Nothing depends on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optional cloud sync
&lt;/h2&gt;

&lt;p&gt;By default everything is local and offline. But if you want your sheets on more than one machine, there's optional Supabase sync. Sign in, then push and pull manually or let it sync automatically. Turn it on only if you want it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The little things that make it pleasant
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A command palette so you can jump anywhere fast&lt;/li&gt;
&lt;li&gt;Keyboard shortcuts everywhere, with customizable quick actions&lt;/li&gt;
&lt;li&gt;Light, dark, and system themes plus a few accent palettes&lt;/li&gt;
&lt;li&gt;Desktop and in app notifications&lt;/li&gt;
&lt;li&gt;It's a PWA, so you can install it and use it offline&lt;/li&gt;
&lt;li&gt;Display knobs for time format, duration format, week start day, compact lists, seconds in the timer, and a live timer in the tab title&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Backup, restore, import, and markdown export for your data
&lt;/h2&gt;

&lt;p&gt;Under the hood&lt;/p&gt;

&lt;p&gt;It's a Next.js 16 app on React 19, styled with Tailwind 4. The npm package ships a standalone build plus the stt-ui launcher. The launcher frees the port if a previous instance is still running, waits for the server to come up, then opens your default browser. You can override the port, the bind host, or tell it to skip opening a browser.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8080 stt-ui
&lt;span class="nv"&gt;STT_UI_HOSTNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.0.0.0 stt-ui
stt-ui &lt;span class="nt"&gt;--no-open&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll need Node 20 or newer.&lt;/p&gt;

&lt;p&gt;Try it&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; super-time-tracker-ui
stt-ui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open &lt;a href="http://127.0.0.1:3000" rel="noopener noreferrer"&gt;http://127.0.0.1:3000&lt;/a&gt; and start tracking. It's MIT licensed and the source lives on GitHub. If you find a bug or want a feature, open an issue. I'd love to hear how you use it.&lt;/p&gt;

&lt;p&gt;Happy tracking.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>productivity</category>
      <category>opensource</category>
      <category>ai</category>
    </item>
    <item>
      <title>A Powerful Node.JS CLI Time Tracker ⏱️🚀</title>
      <dc:creator>Cris Mihalache</dc:creator>
      <pubDate>Thu, 23 Apr 2026 07:33:29 +0000</pubDate>
      <link>https://dev.to/f3rnox/a-powerful-nodejs-cli-time-tracker-33f9</link>
      <guid>https://dev.to/f3rnox/a-powerful-nodejs-cli-time-tracker-33f9</guid>
      <description>&lt;h2&gt;
  
  
  Introducing super-time-tracker: A Node.js CLI Time Tracker (Rebuilt)
&lt;/h2&gt;

&lt;p&gt;After using my own &lt;code&gt;track-time-cli&lt;/code&gt; for a while, I found myself wanting more — more commands, more flexibility, and a cleaner experience overall. So I rebuilt it from the ground up, gave it a new name, and published it as &lt;strong&gt;super-time-tracker&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Introducing &lt;a href="https://npmjs.com/package/super-time-tracker" rel="noopener noreferrer"&gt;&lt;strong&gt;super-time-tracker&lt;/strong&gt;&lt;/a&gt;, a Node.js CLI utility for tracking the time you spend on your projects, with natural language time input, tags, notes, and rich reporting commands.&lt;/p&gt;

&lt;p&gt;It is a Node.js CLI app written in TypeScript. Like its predecessor, it supports natural language for task start and end times (e.g., &lt;code&gt;2 hours and 24 minutes ago&lt;/code&gt; or &lt;code&gt;forty five minutes ago&lt;/code&gt;), and most commands accept flags to render durations in human-readable form or show times as relative.&lt;/p&gt;




&lt;h3&gt;
  
  
  Getting Started
&lt;/h3&gt;

&lt;p&gt;Install it with your package manager of choice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-g&lt;/span&gt; super-time-tracker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The entry point is &lt;strong&gt;&lt;code&gt;stt&lt;/code&gt;&lt;/strong&gt;. By default, running &lt;code&gt;stt&lt;/code&gt; with no arguments shows the currently active time sheet entry.&lt;/p&gt;

&lt;p&gt;All data is stored in &lt;code&gt;~/.super-time-tracker/db.json&lt;/code&gt; — a plain JSON file you can version-control in a git repo to back up your entries as they grow.&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;stt --help&lt;/code&gt; to view all supported commands.&lt;/p&gt;




&lt;h3&gt;
  
  
  A Note on Command Aliases
&lt;/h3&gt;

&lt;p&gt;All commands have short aliases. For example, &lt;code&gt;stt list&lt;/code&gt; is the same as &lt;code&gt;stt l&lt;/code&gt;, and &lt;code&gt;stt sheet&lt;/code&gt; is the same as &lt;code&gt;stt s&lt;/code&gt;. I'll use the short forms throughout this post. Check &lt;code&gt;stt --help&lt;/code&gt; for the full list.&lt;/p&gt;




&lt;h3&gt;
  
  
  Timesheets 📚
&lt;/h3&gt;

&lt;p&gt;A task in &lt;strong&gt;super-time-tracker&lt;/strong&gt; is a &lt;em&gt;timesheet entry&lt;/em&gt; that belongs to a &lt;em&gt;timesheet&lt;/em&gt;. Timesheets represent any grouping of tasks — typically a project or a client.&lt;/p&gt;

&lt;p&gt;To view all timesheets, run &lt;code&gt;stt ss&lt;/code&gt;. This command also accepts &lt;code&gt;--today&lt;/code&gt;, &lt;code&gt;--yesterday&lt;/code&gt;, &lt;code&gt;--since &amp;lt;natural language time&amp;gt;&lt;/code&gt;, &lt;code&gt;--filter &amp;lt;substring&amp;gt;&lt;/code&gt;, and &lt;code&gt;--concise&lt;/code&gt; to narrow down the output.&lt;/p&gt;

&lt;h4&gt;
  
  
  Managing Timesheets
&lt;/h4&gt;

&lt;p&gt;To create or switch to a timesheet, run &lt;code&gt;stt s [name]&lt;/code&gt;. Running &lt;code&gt;stt s&lt;/code&gt; with no name displays the currently active sheet. A default sheet named &lt;code&gt;main&lt;/code&gt; is created on first run.&lt;/p&gt;

&lt;p&gt;New in this version:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;stt s --rename &amp;lt;new name&amp;gt;&lt;/code&gt;&lt;/strong&gt; — rename the active sheet in place.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;stt s &amp;lt;name&amp;gt; --delete&lt;/code&gt;&lt;/strong&gt; — delete a sheet by name.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Tracking Tasks ⏱️
&lt;/h3&gt;

&lt;p&gt;To check in to a new entry, run &lt;code&gt;stt i &amp;lt;description&amp;gt;&lt;/code&gt;. If you started working earlier and forgot to check in, use &lt;code&gt;--at&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stt i &lt;span class="nt"&gt;--at&lt;/span&gt; &lt;span class="s1"&gt;'2 hours and 24 minutes ago'&lt;/span&gt; crafting something
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also target a specific sheet with &lt;code&gt;--sheet&lt;/code&gt;, attach tags with &lt;code&gt;--tags&lt;/code&gt;, or add an opening note with &lt;code&gt;--note&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stt i &lt;span class="nt"&gt;--sheet&lt;/span&gt; work &lt;span class="nt"&gt;--tags&lt;/span&gt; @frontend @bugfix fixing the modal
stt i &lt;span class="nt"&gt;--note&lt;/span&gt; &lt;span class="s1"&gt;'starting with a plan'&lt;/span&gt; writing the architecture doc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To end a task, run &lt;code&gt;stt o&lt;/code&gt;, optionally with &lt;code&gt;--at&lt;/code&gt; or &lt;code&gt;--note&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stt o &lt;span class="nt"&gt;--at&lt;/span&gt; &lt;span class="s1"&gt;'5 minutes ago'&lt;/span&gt;
stt o &lt;span class="nt"&gt;--note&lt;/span&gt; &lt;span class="s1"&gt;'wrapped up, tests passing'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Notes
&lt;/h4&gt;

&lt;p&gt;A new addition in this version is the dedicated &lt;strong&gt;&lt;code&gt;note&lt;/code&gt;&lt;/strong&gt; command, which attaches a timestamped note to the active entry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stt n pair with alice on the widget
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also attach notes to specific past entries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stt n &lt;span class="nt"&gt;--sheet&lt;/span&gt; work &lt;span class="nt"&gt;--entry&lt;/span&gt; 32 &lt;span class="nt"&gt;--at&lt;/span&gt; &lt;span class="s1"&gt;'30 minutes ago'&lt;/span&gt; got unblocked
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Resuming Entries
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;stt r&lt;/code&gt; resumes (clones) the most recent entry as a fresh active entry. You can also resume a specific entry by ID, on a different sheet, or with overrides:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stt r &lt;span class="nt"&gt;--entry&lt;/span&gt; 32
stt r &lt;span class="nt"&gt;--at&lt;/span&gt; &lt;span class="s1"&gt;'10 minutes ago'&lt;/span&gt; &lt;span class="nt"&gt;--tags&lt;/span&gt; @foo updated description
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Editing Entries
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;edit&lt;/code&gt; command got a significant upgrade. You can now rewrite an entry's start/end times, update its tags, or delete it by ID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stt e &lt;span class="nt"&gt;--entry&lt;/span&gt; 32 &lt;span class="nt"&gt;--start&lt;/span&gt; &lt;span class="s1"&gt;'10am'&lt;/span&gt; &lt;span class="nt"&gt;--end&lt;/span&gt; &lt;span class="s1"&gt;'11:30am'&lt;/span&gt;
stt e &lt;span class="nt"&gt;--entry&lt;/span&gt; 32 &lt;span class="nt"&gt;--tags&lt;/span&gt; @new @tags
stt e &lt;span class="nt"&gt;--entry&lt;/span&gt; 32 &lt;span class="nt"&gt;--delete&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Viewing Tasks 📃
&lt;/h3&gt;

&lt;h4&gt;
  
  
  List
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;stt l&lt;/code&gt; lists entries from the past 24 hours. It accepts &lt;code&gt;--since&lt;/code&gt;, &lt;code&gt;--all&lt;/code&gt;, &lt;code&gt;--all-sheets&lt;/code&gt;, &lt;code&gt;--filter&lt;/code&gt;, &lt;code&gt;--concise&lt;/code&gt;, and &lt;code&gt;-r&lt;/code&gt; (relative timestamps). For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stt l &lt;span class="nt"&gt;--since&lt;/span&gt; &lt;span class="s1"&gt;'7 days ago'&lt;/span&gt; &lt;span class="nt"&gt;--all-sheets&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Convenience Commands
&lt;/h4&gt;

&lt;p&gt;There are dedicated commands for common time windows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;stt t&lt;/code&gt; — entries from today&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;stt y&lt;/code&gt; — entries from yesterday&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;stt w&lt;/code&gt; — a week summary; supports &lt;code&gt;--total&lt;/code&gt; for a per-day aggregate, plus &lt;code&gt;--since&lt;/code&gt;, &lt;code&gt;--all-sheets&lt;/code&gt;, and &lt;code&gt;--filter&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Breakdown
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;stt b&lt;/code&gt; displays total durations broken down by day, weekday, and hour — useful for spotting patterns in when you work most. It accepts &lt;code&gt;--today&lt;/code&gt;, &lt;code&gt;--yesterday&lt;/code&gt;, &lt;code&gt;--since&lt;/code&gt;, &lt;code&gt;--all-sheets&lt;/code&gt;, and &lt;code&gt;--filter&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Useful Flags
&lt;/h3&gt;

&lt;p&gt;Nearly every command supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;-h&lt;/code&gt;&lt;/strong&gt; — render durations in human-readable format (e.g., &lt;em&gt;1 hour, 36 minutes&lt;/em&gt; instead of &lt;em&gt;1:36:18&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;-r&lt;/code&gt;&lt;/strong&gt; — show times as relative (e.g., &lt;em&gt;3 hours ago&lt;/em&gt; instead of an absolute timestamp)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Example Workflow
&lt;/h3&gt;

&lt;p&gt;Here's a typical day with &lt;strong&gt;super-time-tracker&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stt s work
stt i &lt;span class="nt"&gt;--at&lt;/span&gt; &lt;span class="s1"&gt;'2 hours and 24 minutes ago'&lt;/span&gt; crafting something
stt n pair with alice on the widget
stt o
stt l &lt;span class="nt"&gt;--since&lt;/span&gt; &lt;span class="s1"&gt;'4 hours ago'&lt;/span&gt;
stt t
stt w
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  What's New vs. track-time-cli
&lt;/h3&gt;

&lt;p&gt;If you used the old &lt;code&gt;track-time-cli&lt;/code&gt; (&lt;code&gt;tt&lt;/code&gt;), here's a quick summary of what changed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Notes&lt;/strong&gt; — new dedicated command for attaching timestamped notes to entries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tags&lt;/strong&gt; — first-class support on &lt;code&gt;stt i&lt;/code&gt;, &lt;code&gt;stt r&lt;/code&gt;, and &lt;code&gt;stt e&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sheet management&lt;/strong&gt; — rename and delete sheets directly from the CLI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;resume&lt;/code&gt; upgrades&lt;/strong&gt; — resume with a time override, tag override, or description override&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;edit&lt;/code&gt; upgrades&lt;/strong&gt; — rewrite start/end times from natural language, update tag sets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;breakdown&lt;/code&gt;&lt;/strong&gt; — improved with weekday and hour-of-day breakdowns in addition to per-day totals&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;sheets&lt;/code&gt;&lt;/strong&gt; — now supports &lt;code&gt;--today&lt;/code&gt;, &lt;code&gt;--yesterday&lt;/code&gt;, &lt;code&gt;--since&lt;/code&gt;, and &lt;code&gt;--filter&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;New command: &lt;strong&gt;&lt;code&gt;stt&lt;/code&gt;&lt;/strong&gt; (instead of &lt;code&gt;tt&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;I haven't covered every flag and option in this post — check out the &lt;a href="https://github.com/f3rnox/super-time-tracker" rel="noopener noreferrer"&gt;README&lt;/a&gt; and the full &lt;code&gt;stt --help&lt;/code&gt; output for the complete picture.&lt;/p&gt;

&lt;p&gt;Any feedback, comments, or suggestions are very welcome. Happy tracking! ⏱️&lt;/p&gt;

</description>
      <category>node</category>
      <category>cli</category>
      <category>productivity</category>
      <category>programming</category>
    </item>
    <item>
      <title>A Powerful Node.JS CLI Time Tracker ⏱️🚀</title>
      <dc:creator>Cris Mihalache</dc:creator>
      <pubDate>Thu, 23 Apr 2026 07:33:28 +0000</pubDate>
      <link>https://dev.to/f3rnox/a-powerful-nodejs-cli-time-tracker-808</link>
      <guid>https://dev.to/f3rnox/a-powerful-nodejs-cli-time-tracker-808</guid>
      <description>&lt;h2&gt;
  
  
  Introducing super-time-tracker: A Node.js CLI Time Tracker (Rebuilt)
&lt;/h2&gt;

&lt;p&gt;After using my own &lt;code&gt;track-time-cli&lt;/code&gt; for a while, I found myself wanting more — more commands, more flexibility, and a cleaner experience overall. So I rebuilt it from the ground up, gave it a new name, and published it as &lt;strong&gt;super-time-tracker&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Introducing &lt;a href="https://npmjs.com/package/super-time-tracker" rel="noopener noreferrer"&gt;&lt;strong&gt;super-time-tracker&lt;/strong&gt;&lt;/a&gt;, a Node.js CLI utility for tracking the time you spend on your projects, with natural language time input, tags, notes, and rich reporting commands.&lt;/p&gt;

&lt;p&gt;It is a Node.js CLI app written in TypeScript. Like its predecessor, it supports natural language for task start and end times (e.g., &lt;code&gt;2 hours and 24 minutes ago&lt;/code&gt; or &lt;code&gt;forty five minutes ago&lt;/code&gt;), and most commands accept flags to render durations in human-readable form or show times as relative.&lt;/p&gt;




&lt;h3&gt;
  
  
  Getting Started
&lt;/h3&gt;

&lt;p&gt;Install it with your package manager of choice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-g&lt;/span&gt; super-time-tracker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The entry point is &lt;strong&gt;&lt;code&gt;stt&lt;/code&gt;&lt;/strong&gt;. By default, running &lt;code&gt;stt&lt;/code&gt; with no arguments shows the currently active time sheet entry.&lt;/p&gt;

&lt;p&gt;All data is stored in &lt;code&gt;~/.super-time-tracker/db.json&lt;/code&gt; — a plain JSON file you can version-control in a git repo to back up your entries as they grow.&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;stt --help&lt;/code&gt; to view all supported commands.&lt;/p&gt;




&lt;h3&gt;
  
  
  A Note on Command Aliases
&lt;/h3&gt;

&lt;p&gt;All commands have short aliases. For example, &lt;code&gt;stt list&lt;/code&gt; is the same as &lt;code&gt;stt l&lt;/code&gt;, and &lt;code&gt;stt sheet&lt;/code&gt; is the same as &lt;code&gt;stt s&lt;/code&gt;. I'll use the short forms throughout this post. Check &lt;code&gt;stt --help&lt;/code&gt; for the full list.&lt;/p&gt;




&lt;h3&gt;
  
  
  Timesheets 📚
&lt;/h3&gt;

&lt;p&gt;A task in &lt;strong&gt;super-time-tracker&lt;/strong&gt; is a &lt;em&gt;timesheet entry&lt;/em&gt; that belongs to a &lt;em&gt;timesheet&lt;/em&gt;. Timesheets represent any grouping of tasks — typically a project or a client.&lt;/p&gt;

&lt;p&gt;To view all timesheets, run &lt;code&gt;stt ss&lt;/code&gt;. This command also accepts &lt;code&gt;--today&lt;/code&gt;, &lt;code&gt;--yesterday&lt;/code&gt;, &lt;code&gt;--since &amp;lt;natural language time&amp;gt;&lt;/code&gt;, &lt;code&gt;--filter &amp;lt;substring&amp;gt;&lt;/code&gt;, and &lt;code&gt;--concise&lt;/code&gt; to narrow down the output.&lt;/p&gt;

&lt;h4&gt;
  
  
  Managing Timesheets
&lt;/h4&gt;

&lt;p&gt;To create or switch to a timesheet, run &lt;code&gt;stt s [name]&lt;/code&gt;. Running &lt;code&gt;stt s&lt;/code&gt; with no name displays the currently active sheet. A default sheet named &lt;code&gt;main&lt;/code&gt; is created on first run.&lt;/p&gt;

&lt;p&gt;New in this version:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;stt s --rename &amp;lt;new name&amp;gt;&lt;/code&gt;&lt;/strong&gt; — rename the active sheet in place.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;stt s &amp;lt;name&amp;gt; --delete&lt;/code&gt;&lt;/strong&gt; — delete a sheet by name.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Tracking Tasks ⏱️
&lt;/h3&gt;

&lt;p&gt;To check in to a new entry, run &lt;code&gt;stt i &amp;lt;description&amp;gt;&lt;/code&gt;. If you started working earlier and forgot to check in, use &lt;code&gt;--at&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stt i &lt;span class="nt"&gt;--at&lt;/span&gt; &lt;span class="s1"&gt;'2 hours and 24 minutes ago'&lt;/span&gt; crafting something
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also target a specific sheet with &lt;code&gt;--sheet&lt;/code&gt;, attach tags with &lt;code&gt;--tags&lt;/code&gt;, or add an opening note with &lt;code&gt;--note&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stt i &lt;span class="nt"&gt;--sheet&lt;/span&gt; work &lt;span class="nt"&gt;--tags&lt;/span&gt; @frontend @bugfix fixing the modal
stt i &lt;span class="nt"&gt;--note&lt;/span&gt; &lt;span class="s1"&gt;'starting with a plan'&lt;/span&gt; writing the architecture doc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To end a task, run &lt;code&gt;stt o&lt;/code&gt;, optionally with &lt;code&gt;--at&lt;/code&gt; or &lt;code&gt;--note&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stt o &lt;span class="nt"&gt;--at&lt;/span&gt; &lt;span class="s1"&gt;'5 minutes ago'&lt;/span&gt;
stt o &lt;span class="nt"&gt;--note&lt;/span&gt; &lt;span class="s1"&gt;'wrapped up, tests passing'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Notes
&lt;/h4&gt;

&lt;p&gt;A new addition in this version is the dedicated &lt;strong&gt;&lt;code&gt;note&lt;/code&gt;&lt;/strong&gt; command, which attaches a timestamped note to the active entry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stt n pair with alice on the widget
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also attach notes to specific past entries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stt n &lt;span class="nt"&gt;--sheet&lt;/span&gt; work &lt;span class="nt"&gt;--entry&lt;/span&gt; 32 &lt;span class="nt"&gt;--at&lt;/span&gt; &lt;span class="s1"&gt;'30 minutes ago'&lt;/span&gt; got unblocked
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Resuming Entries
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;stt r&lt;/code&gt; resumes (clones) the most recent entry as a fresh active entry. You can also resume a specific entry by ID, on a different sheet, or with overrides:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stt r &lt;span class="nt"&gt;--entry&lt;/span&gt; 32
stt r &lt;span class="nt"&gt;--at&lt;/span&gt; &lt;span class="s1"&gt;'10 minutes ago'&lt;/span&gt; &lt;span class="nt"&gt;--tags&lt;/span&gt; @foo updated description
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Editing Entries
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;edit&lt;/code&gt; command got a significant upgrade. You can now rewrite an entry's start/end times, update its tags, or delete it by ID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stt e &lt;span class="nt"&gt;--entry&lt;/span&gt; 32 &lt;span class="nt"&gt;--start&lt;/span&gt; &lt;span class="s1"&gt;'10am'&lt;/span&gt; &lt;span class="nt"&gt;--end&lt;/span&gt; &lt;span class="s1"&gt;'11:30am'&lt;/span&gt;
stt e &lt;span class="nt"&gt;--entry&lt;/span&gt; 32 &lt;span class="nt"&gt;--tags&lt;/span&gt; @new @tags
stt e &lt;span class="nt"&gt;--entry&lt;/span&gt; 32 &lt;span class="nt"&gt;--delete&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Viewing Tasks 📃
&lt;/h3&gt;

&lt;h4&gt;
  
  
  List
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;stt l&lt;/code&gt; lists entries from the past 24 hours. It accepts &lt;code&gt;--since&lt;/code&gt;, &lt;code&gt;--all&lt;/code&gt;, &lt;code&gt;--all-sheets&lt;/code&gt;, &lt;code&gt;--filter&lt;/code&gt;, &lt;code&gt;--concise&lt;/code&gt;, and &lt;code&gt;-r&lt;/code&gt; (relative timestamps). For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stt l &lt;span class="nt"&gt;--since&lt;/span&gt; &lt;span class="s1"&gt;'7 days ago'&lt;/span&gt; &lt;span class="nt"&gt;--all-sheets&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Convenience Commands
&lt;/h4&gt;

&lt;p&gt;There are dedicated commands for common time windows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;stt t&lt;/code&gt; — entries from today&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;stt y&lt;/code&gt; — entries from yesterday&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;stt w&lt;/code&gt; — a week summary; supports &lt;code&gt;--total&lt;/code&gt; for a per-day aggregate, plus &lt;code&gt;--since&lt;/code&gt;, &lt;code&gt;--all-sheets&lt;/code&gt;, and &lt;code&gt;--filter&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Breakdown
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;stt b&lt;/code&gt; displays total durations broken down by day, weekday, and hour — useful for spotting patterns in when you work most. It accepts &lt;code&gt;--today&lt;/code&gt;, &lt;code&gt;--yesterday&lt;/code&gt;, &lt;code&gt;--since&lt;/code&gt;, &lt;code&gt;--all-sheets&lt;/code&gt;, and &lt;code&gt;--filter&lt;/code&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Useful Flags
&lt;/h3&gt;

&lt;p&gt;Nearly every command supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;-h&lt;/code&gt;&lt;/strong&gt; — render durations in human-readable format (e.g., &lt;em&gt;1 hour, 36 minutes&lt;/em&gt; instead of &lt;em&gt;1:36:18&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;-r&lt;/code&gt;&lt;/strong&gt; — show times as relative (e.g., &lt;em&gt;3 hours ago&lt;/em&gt; instead of an absolute timestamp)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Example Workflow
&lt;/h3&gt;

&lt;p&gt;Here's a typical day with &lt;strong&gt;super-time-tracker&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stt s work
stt i &lt;span class="nt"&gt;--at&lt;/span&gt; &lt;span class="s1"&gt;'2 hours and 24 minutes ago'&lt;/span&gt; crafting something
stt n pair with alice on the widget
stt o
stt l &lt;span class="nt"&gt;--since&lt;/span&gt; &lt;span class="s1"&gt;'4 hours ago'&lt;/span&gt;
stt t
stt w
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  What's New vs. track-time-cli
&lt;/h3&gt;

&lt;p&gt;If you used the old &lt;code&gt;track-time-cli&lt;/code&gt; (&lt;code&gt;tt&lt;/code&gt;), here's a quick summary of what changed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Notes&lt;/strong&gt; — new dedicated command for attaching timestamped notes to entries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tags&lt;/strong&gt; — first-class support on &lt;code&gt;stt i&lt;/code&gt;, &lt;code&gt;stt r&lt;/code&gt;, and &lt;code&gt;stt e&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sheet management&lt;/strong&gt; — rename and delete sheets directly from the CLI&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;resume&lt;/code&gt; upgrades&lt;/strong&gt; — resume with a time override, tag override, or description override&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;edit&lt;/code&gt; upgrades&lt;/strong&gt; — rewrite start/end times from natural language, update tag sets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;breakdown&lt;/code&gt;&lt;/strong&gt; — improved with weekday and hour-of-day breakdowns in addition to per-day totals&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;sheets&lt;/code&gt;&lt;/strong&gt; — now supports &lt;code&gt;--today&lt;/code&gt;, &lt;code&gt;--yesterday&lt;/code&gt;, &lt;code&gt;--since&lt;/code&gt;, and &lt;code&gt;--filter&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;New command: &lt;strong&gt;&lt;code&gt;stt&lt;/code&gt;&lt;/strong&gt; (instead of &lt;code&gt;tt&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;I haven't covered every flag and option in this post — check out the &lt;a href="https://github.com/f3rnox/super-time-tracker" rel="noopener noreferrer"&gt;README&lt;/a&gt; and the full &lt;code&gt;stt --help&lt;/code&gt; output for the complete picture.&lt;/p&gt;

&lt;p&gt;Any feedback, comments, or suggestions are very welcome. Happy tracking! ⏱️&lt;/p&gt;

</description>
      <category>node</category>
      <category>cli</category>
      <category>productivity</category>
      <category>programming</category>
    </item>
    <item>
      <title>Using Chat GPT To Generate Datasets 🤖</title>
      <dc:creator>Cris Mihalache</dc:creator>
      <pubDate>Thu, 23 Apr 2026 00:53:30 +0000</pubDate>
      <link>https://dev.to/f3rnox/using-chat-gpt-to-generate-datasets-3kop</link>
      <guid>https://dev.to/f3rnox/using-chat-gpt-to-generate-datasets-3kop</guid>
      <description>&lt;p&gt;Recently I used &lt;a href="https://chatgpt.com/" rel="noopener noreferrer"&gt;Chat GPT&lt;/a&gt; to generate mock data in JSON format.&lt;/p&gt;

&lt;p&gt;This is extremely powerful, as you can describe what real data looks like in your prompt, and &lt;a href="https://chatgpt.com/" rel="noopener noreferrer"&gt;Chat GPT&lt;/a&gt; will generate data with realistic content in fields like descriptions, titles, tags, etc.&lt;/p&gt;

&lt;p&gt;This is a sample of the data &lt;a href="https://chatgpt.com/" rel="noopener noreferrer"&gt;Chat GPT&lt;/a&gt; generated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Implementing OAuth2 for user authentication"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"@feature"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"@security"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-02-22T03:11:03"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-02-22T06:36:18"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how the description, tags, and duration are all realistic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;

&lt;p&gt;To build the documentation website for &lt;a href="https://github.com/f3rno64/track-time-cli" rel="noopener noreferrer"&gt;track-time-cli&lt;/a&gt;, I needed a database with mock data to have realistic command output in the documentation content.&lt;/p&gt;

&lt;p&gt;To get &lt;a href="https://chatgpt.com/" rel="noopener noreferrer"&gt;Chat GPT&lt;/a&gt; to generate them for me, I used the following prompt:&lt;/p&gt;

&lt;p&gt;Generate 5 time sheet entries with realistic software development tasks as descriptions. Use the following schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"writing documentation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"@feature"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"@docs"&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-02-21T18:26:45"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-02-21T19:33:08"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The entries should be sequential in time, of varying durations (between 5 minutes and 5 hours), and have realistic descriptions that fit the software development time sheet theme.&lt;/p&gt;

&lt;p&gt;The output should be in JSON, an array of time sheet entries conforming to the provided schema example.&lt;/p&gt;

&lt;p&gt;Note that I specified the &lt;strong&gt;number of entries&lt;/strong&gt; (5) and the &lt;strong&gt;type of activity&lt;/strong&gt; (&lt;em&gt;realistic software development tasks&lt;/em&gt;) that should be mentioned in the description.&lt;/p&gt;

&lt;p&gt;I defined the output schema by providing an example of a correctly formatted time sheet entry. I also specified that they should be sequential in time and have realistic durations as would be expected for the defined type of activity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Result
&lt;/h3&gt;

&lt;p&gt;This was the output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Refactoring user interface code"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"@feature"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"@frontend"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-02-21T18:26:45"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-02-21T22:39:32"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Optimizing database queries for report generation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"@performance"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"@database"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-02-21T22:39:32"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-02-22T03:11:03"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Implementing OAuth2 for user authentication"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"@feature"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"@security"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-02-22T03:11:03"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-02-22T06:36:18"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Weekly team sync-up meeting"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"@meeting"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"@communication"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-02-22T06:36:18"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-02-22T10:16:24"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Adding localization support for new languages"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"@feature"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"@i18n"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-02-22T10:16:24"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-02-22T11:50:10"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After running this prompt a few times to generate 16 entries, I placed them all in an array within a mock JSON database. The output of running &lt;a href="https://github.com/f3rno64/track-time-cli" rel="noopener noreferrer"&gt;track-time-cli's&lt;/a&gt; list command was the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; 23:50 ▶ 97% ▶ xia ▶ f3rnox ▶ .track-time-cli ▶  main ▶ ✎ ▶ 2? ▶ &lt;span class="nv"&gt;$ &lt;/span&gt;▶ tt l
&lt;span class="k"&gt;*&lt;/span&gt; Showing sheets since 2/21/2024, 12:00:00 AM &lt;span class="o"&gt;[&lt;/span&gt;yesterday]

- Sheet main &lt;span class="o"&gt;(&lt;/span&gt;16 entries&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;1:10:19:16]
    &lt;span class="o"&gt;(&lt;/span&gt;1&lt;span class="o"&gt;)&lt;/span&gt;   &lt;span class="o"&gt;[&lt;/span&gt;9:28:26]  yesterday       -&amp;gt;  14 hours ago    Writing API documentation                           @feature @docs
    &lt;span class="o"&gt;(&lt;/span&gt;2&lt;span class="o"&gt;)&lt;/span&gt;   &lt;span class="o"&gt;[&lt;/span&gt;16:51]    14 hours ago    -&amp;gt;  14 hours ago    Implementing user authentication module             @feature @backend
    &lt;span class="o"&gt;(&lt;/span&gt;3&lt;span class="o"&gt;)&lt;/span&gt;   &lt;span class="o"&gt;[&lt;/span&gt;50:01]    14 hours ago    -&amp;gt;  13 hours ago    Designing database schema &lt;span class="k"&gt;for &lt;/span&gt;new features          @design @database
    &lt;span class="o"&gt;(&lt;/span&gt;4&lt;span class="o"&gt;)&lt;/span&gt;   &lt;span class="o"&gt;[&lt;/span&gt;2:04:11]  13 hours ago    -&amp;gt;  11 hours ago    Improving responsive design &lt;span class="k"&gt;for &lt;/span&gt;mobile devices      @feature @frontend
    &lt;span class="o"&gt;(&lt;/span&gt;5&lt;span class="o"&gt;)&lt;/span&gt;   &lt;span class="o"&gt;[&lt;/span&gt;2:29:48]  11 hours ago    -&amp;gt;  9 hours ago     Conducting user experience research                 @research @ux
    &lt;span class="o"&gt;(&lt;/span&gt;6&lt;span class="o"&gt;)&lt;/span&gt;   &lt;span class="o"&gt;[&lt;/span&gt;1:42:30]  9 hours ago     -&amp;gt;  7 hours ago     Implementing caching &lt;span class="k"&gt;for &lt;/span&gt;improved performance       @performance @backend
    &lt;span class="o"&gt;(&lt;/span&gt;7&lt;span class="o"&gt;)&lt;/span&gt;   &lt;span class="o"&gt;[&lt;/span&gt;9:08]     7 hours ago     -&amp;gt;  7 hours ago     Weekly team retrospective meeting                   @meeting @team
    &lt;span class="o"&gt;(&lt;/span&gt;8&lt;span class="o"&gt;)&lt;/span&gt;   &lt;span class="o"&gt;[&lt;/span&gt;39:52]    7 hours ago     -&amp;gt;  6 hours ago     Refining search algorithm                           @algorithm @backend
    &lt;span class="o"&gt;(&lt;/span&gt;9&lt;span class="o"&gt;)&lt;/span&gt;   &lt;span class="o"&gt;[&lt;/span&gt;2:13:23]  6 hours ago     -&amp;gt;  4 hours ago     Reviewing and merging pull requests                 @review @version-control
    &lt;span class="o"&gt;(&lt;/span&gt;10&lt;span class="o"&gt;)&lt;/span&gt;  &lt;span class="o"&gt;[&lt;/span&gt;1:08:12]  4 hours ago     -&amp;gt;  3 hours ago     Developing new feature &lt;span class="k"&gt;for &lt;/span&gt;customer analytics       @feature @analytics
    &lt;span class="o"&gt;(&lt;/span&gt;11&lt;span class="o"&gt;)&lt;/span&gt;  &lt;span class="o"&gt;[&lt;/span&gt;2:27:32]  3 hours ago     -&amp;gt;  21 minutes ago  Solving compatibility issues &lt;span class="k"&gt;in &lt;/span&gt;cross-platform app  @bugfix @mobile
    &lt;span class="o"&gt;(&lt;/span&gt;12&lt;span class="o"&gt;)&lt;/span&gt;  &lt;span class="o"&gt;[&lt;/span&gt;3:14:14]  21 minutes ago  -&amp;gt;  &lt;span class="k"&gt;in &lt;/span&gt;3 hours      Organizing backlog and prioritizing tasks           @planning @management
    &lt;span class="o"&gt;(&lt;/span&gt;13&lt;span class="o"&gt;)&lt;/span&gt;  &lt;span class="o"&gt;[&lt;/span&gt;3:07:36]  &lt;span class="k"&gt;in &lt;/span&gt;3 hours      -&amp;gt;  &lt;span class="k"&gt;in &lt;/span&gt;6 hours      Upgrading server infrastructure                     @maintenance @devops
    &lt;span class="o"&gt;(&lt;/span&gt;14&lt;span class="o"&gt;)&lt;/span&gt;  &lt;span class="o"&gt;[&lt;/span&gt;1:41:17]  &lt;span class="k"&gt;in &lt;/span&gt;6 hours      -&amp;gt;  &lt;span class="k"&gt;in &lt;/span&gt;8 hours      Creating automated tests &lt;span class="k"&gt;for &lt;/span&gt;new API endpoints      @testing @automation
    &lt;span class="o"&gt;(&lt;/span&gt;15&lt;span class="o"&gt;)&lt;/span&gt;  &lt;span class="o"&gt;[&lt;/span&gt;2:02:29]  &lt;span class="k"&gt;in &lt;/span&gt;8 hours      -&amp;gt;  &lt;span class="k"&gt;in &lt;/span&gt;10 hours     Troubleshooting server downtime issues              @maintenance @devops
    &lt;span class="o"&gt;(&lt;/span&gt;16&lt;span class="o"&gt;)&lt;/span&gt;  &lt;span class="o"&gt;[&lt;/span&gt;43:46]    &lt;span class="k"&gt;in &lt;/span&gt;10 hours     -&amp;gt;  &lt;span class="k"&gt;in &lt;/span&gt;10 hours     Designing user interface &lt;span class="k"&gt;for &lt;/span&gt;the upcoming module    @design @frontend

&lt;span class="k"&gt;*&lt;/span&gt; 0 Sheets not shown. use &lt;span class="nt"&gt;--all&lt;/span&gt; to show
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Conclusions
&lt;/h3&gt;

&lt;p&gt;Clearly &lt;a href="https://chat.openai.com/" rel="noopener noreferrer"&gt;Chat GPT&lt;/a&gt; is a great tool for generating realistic datasets.&lt;/p&gt;

&lt;p&gt;Try it out the next time you need some mock data with realistic textual content! 🚀&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>node</category>
      <category>chatgpt</category>
    </item>
    <item>
      <title>Build a typed CLI from scratch with Commander.js + Zod</title>
      <dc:creator>Cris Mihalache</dc:creator>
      <pubDate>Wed, 22 Apr 2026 23:03:34 +0000</pubDate>
      <link>https://dev.to/f3rnox/build-a-typed-cli-from-scratch-with-commanderjs-zod-4oj6</link>
      <guid>https://dev.to/f3rnox/build-a-typed-cli-from-scratch-with-commanderjs-zod-4oj6</guid>
      <description>&lt;h2&gt;
  
  
  The problem with untyped CLIs
&lt;/h2&gt;

&lt;p&gt;Commander.js is great for parsing arguments, but it hands you everything as &lt;code&gt;string | undefined&lt;/code&gt; — regardless of what type you declared in the option definition. The &lt;code&gt;&amp;lt;number&amp;gt;&lt;/code&gt; in &lt;code&gt;.option("--count &amp;lt;number&amp;gt;")&lt;/code&gt; is cosmetic. What lands in your action handler is raw text from &lt;code&gt;process.argv&lt;/code&gt;, and the coercion, validation, and type-narrowing is entirely on you. Most developers paper over this with &lt;code&gt;parseInt(opts.count as string)&lt;/code&gt; and hope for the best. That works until a user passes &lt;code&gt;"banana"&lt;/code&gt; and your tool crashes with a stack trace instead of a helpful message.&lt;/p&gt;

&lt;p&gt;We already have the right tool for this: Zod. It's everywhere in the Node.js ecosystem for HTTP bodies, env vars, and config files — but rarely applied at the CLI layer. That's the gap this post fills. By the end you'll have a CLI that validates all inputs at the boundary, gives users readable error messages, and is fully typed end-to-end with no casts and no &lt;code&gt;any&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Project scaffold
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;my-cli &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;my-cli
npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
npm &lt;span class="nb"&gt;install &lt;/span&gt;commander zod
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; typescript tsx @types/node
npx tsc &lt;span class="nt"&gt;--init&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three dependencies: &lt;code&gt;commander&lt;/code&gt; for the CLI surface, &lt;code&gt;zod&lt;/code&gt; for validation, &lt;code&gt;tsx&lt;/code&gt; to run TypeScript directly during development without a compile step. Update &lt;code&gt;tsconfig.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"strict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ES2022"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Node16"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"moduleResolution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Node16"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"outDir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dist"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;strict: true&lt;/code&gt; is non-negotiable here — it's exactly what catches the class of bugs we're solving. Wire up &lt;code&gt;bin&lt;/code&gt; and scripts in &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"my-cli"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./dist/index.js"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tsx src/index.ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tsc"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2. Your first typed command
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Command&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;commander&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;program&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;program&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-cli&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--name &amp;lt;string&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Your name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--count &amp;lt;number&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;How many times to greet&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;program&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx tsx src/index.ts &lt;span class="nt"&gt;--name&lt;/span&gt; Alice &lt;span class="nt"&gt;--count&lt;/span&gt; 3
&lt;span class="c"&gt;# { name: 'Alice', count: '3' }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;count&lt;/code&gt; is &lt;code&gt;'3'&lt;/code&gt; — a string. Commander doesn't coerce types. If you do arithmetic with it you get string concatenation. If you type-guard with &lt;code&gt;typeof opts.count === 'number'&lt;/code&gt; it always returns &lt;code&gt;false&lt;/code&gt;. This is the gap Zod fills.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Enter Zod — schema-validate your options
&lt;/h2&gt;

&lt;p&gt;Treat &lt;code&gt;program.opts()&lt;/code&gt; the same way you'd treat an untrusted HTTP request body: validate at the boundary, only work with the parsed output. Define a schema and run opts through it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;OptionsSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Name cannot be empty&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coerce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;positive&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;program&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;OptionsSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Hello, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;!`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;z.coerce.number()&lt;/code&gt; calls &lt;code&gt;Number(value)&lt;/code&gt; before validating, converting Commander's &lt;code&gt;"3"&lt;/code&gt; to &lt;code&gt;3&lt;/code&gt;. If someone passes &lt;code&gt;--count banana&lt;/code&gt;, Zod rejects it cleanly instead of silently producing &lt;code&gt;NaN&lt;/code&gt;. &lt;code&gt;.int().positive()&lt;/code&gt; enforces additional constraints without a single &lt;code&gt;if&lt;/code&gt; statement. &lt;code&gt;.default(1)&lt;/code&gt; makes the option optional — Zod fills it in. After &lt;code&gt;.parse()&lt;/code&gt;, &lt;code&gt;options&lt;/code&gt; has the inferred type &lt;code&gt;{ name: string; count: number }&lt;/code&gt; — no annotation required.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. User-friendly validation errors
&lt;/h2&gt;

&lt;p&gt;Right now a failed parse throws a raw &lt;code&gt;ZodError&lt;/code&gt; JSON blob at the user. Fix that with a small reusable helper using &lt;code&gt;safeParse()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ZodSchema&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;parseOptions&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ZodSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid options:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`  --&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;  &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Run with --help for usage.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before vs after, when &lt;code&gt;--name&lt;/code&gt; is missing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Before
ZodError: [{"code":"too_small","path":["name"],"message":"Name cannot be empty"...}]

# After
Invalid options:

  --name  Name cannot be empty

Run with --help for usage.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This helper is generic — &lt;code&gt;T&lt;/code&gt; is inferred from the schema, so the return type is always exactly right. &lt;code&gt;opts&lt;/code&gt; is &lt;code&gt;unknown&lt;/code&gt;, which correctly models the untrusted input. Replace all direct &lt;code&gt;.parse()&lt;/code&gt; calls with &lt;code&gt;parseOptions(schema, opts)&lt;/code&gt; going forward.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Scaling to subcommands
&lt;/h2&gt;

&lt;p&gt;As your CLI grows, each command gets its own schema and calls the same helper — no copy-pasted validation logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;InitSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;force&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RunSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coerce&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;positive&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;program&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;init&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--dir &amp;lt;path&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Target directory&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--force&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Overwrite existing files&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;InitSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Initializing in &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;program&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;run&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--name &amp;lt;string&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Your name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--count &amp;lt;number&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;How many times to greet&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;action&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;RunSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Hello, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;!`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside the &lt;code&gt;init&lt;/code&gt; action, &lt;code&gt;options&lt;/code&gt; has type &lt;code&gt;{ dir: string; force: boolean }&lt;/code&gt;. Inside &lt;code&gt;run&lt;/code&gt;, it's &lt;code&gt;{ name: string; count: number }&lt;/code&gt;. TypeScript infers these separately from each schema — accessing &lt;code&gt;options.name&lt;/code&gt; in the &lt;code&gt;init&lt;/code&gt; handler is a compile error, not a runtime surprise.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Help menus and &lt;code&gt;.env&lt;/code&gt; support
&lt;/h2&gt;

&lt;p&gt;Commander generates &lt;code&gt;--help&lt;/code&gt; automatically from your &lt;code&gt;.option()&lt;/code&gt; descriptions — you already have it for free. One gotcha: Zod's &lt;code&gt;.default()&lt;/code&gt; values don't appear in Commander's help output. Mirror them manually in the third argument to &lt;code&gt;.option()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--count &amp;lt;number&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;How many times to greet&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For &lt;code&gt;.env&lt;/code&gt; support, merge environment variables into opts before parsing. Zod doesn't care where values originate — it just validates the final object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dotenv/config&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;program&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;run&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;action&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;cliOpts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;merged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CLI_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CLI_COUNT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;cliOpts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// CLI flags win over env vars&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;RunSchema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Env vars as defaults, CLI flags as overrides — one schema validates both.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Publishing to npm
&lt;/h2&gt;

&lt;p&gt;Add a shebang as the very first line of &lt;code&gt;src/index.ts&lt;/code&gt; (before imports):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cp"&gt;#!/usr/bin/env node
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build, make the output executable, test locally with &lt;code&gt;npm link&lt;/code&gt;, then publish:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run build
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x dist/index.js
npm &lt;span class="nb"&gt;link
&lt;/span&gt;my-cli run &lt;span class="nt"&gt;--name&lt;/span&gt; Cris &lt;span class="nt"&gt;--count&lt;/span&gt; 3  &lt;span class="c"&gt;# test it&lt;/span&gt;
npm &lt;span class="nb"&gt;unlink &lt;/span&gt;my-cli
npm publish &lt;span class="nt"&gt;--access&lt;/span&gt; public
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify with &lt;code&gt;npx my-cli run --name Cris --count 3&lt;/code&gt; — if that works, you're done.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;The pattern in one line: &lt;strong&gt;Commander owns the surface, Zod owns the contract.&lt;/strong&gt; Commander parses and routes. Zod coerces, validates, defaults, and types. The &lt;code&gt;parseOptions&lt;/code&gt; helper — 15 lines — is the glue, and it scales to any number of subcommands without modification.&lt;/p&gt;

&lt;p&gt;This isn't CLI-specific either. The same schema-at-boundary approach applies to HTTP bodies, env vars, and config files. Validate everything at every input boundary, work only with validated data inside your logic, and an entire category of runtime bugs stops existing.&lt;/p&gt;

&lt;p&gt;The full source is on GitHub: &lt;em&gt;[link to your repo]&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Next up: adding &lt;code&gt;--json&lt;/code&gt; output mode and stdin piping so your CLI composes cleanly in shell pipelines.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Found this useful? Drop a reaction or leave a comment — feedback helps me prioritise what to cover next in this series.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>node</category>
      <category>typescript</category>
      <category>cli</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Stop Guessing npm Package Names — Let a CLI Do It</title>
      <dc:creator>Cris Mihalache</dc:creator>
      <pubDate>Wed, 22 Apr 2026 22:32:04 +0000</pubDate>
      <link>https://dev.to/f3rnox/stop-guessing-npm-package-names-let-a-cli-do-it-4pe1</link>
      <guid>https://dev.to/f3rnox/stop-guessing-npm-package-names-let-a-cli-do-it-4pe1</guid>
      <description>&lt;p&gt;You've finished building a neat little utility. You open a terminal, type &lt;code&gt;npm publish&lt;/code&gt;, and then… stall. What do you name the thing?&lt;/p&gt;

&lt;p&gt;You try &lt;code&gt;json-stream-parser&lt;/code&gt; — taken. &lt;code&gt;stream-json-parser&lt;/code&gt; — also taken. &lt;code&gt;parse-json-stream&lt;/code&gt; — taken too, and it's a completely different package. You spend 20 minutes bouncing between the npm registry website and your terminal before settling on something you're not even happy with.&lt;/p&gt;

&lt;p&gt;I've been through that loop more times than I'd like to admit, so I built a small tool to short-circuit it: &lt;strong&gt;&lt;a href="https://www.npmjs.com/package/gen-package-name" rel="noopener noreferrer"&gt;gen-package-name&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;gen-package-name&lt;/code&gt; is a CLI utility that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Asks you for a short description of your package (or accepts keywords directly)&lt;/li&gt;
&lt;li&gt;Extracts meaningful keywords from that description&lt;/li&gt;
&lt;li&gt;Generates a set of name candidates using those keywords&lt;/li&gt;
&lt;li&gt;Checks each candidate against the npm registry in real time&lt;/li&gt;
&lt;li&gt;Lets you regenerate until you find something you like&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The whole flow takes about 30 seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;Install globally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; gen-package-name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or just run it once with npx:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx gen-package-name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Interactive mode (the happy path)
&lt;/h2&gt;

&lt;p&gt;Run it with no arguments and it walks you through everything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gen-package-name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll be prompted for a description like &lt;code&gt;"parse json streams efficiently"&lt;/code&gt;. It strips stop words, extracts the signal (&lt;code&gt;parse&lt;/code&gt;, &lt;code&gt;json&lt;/code&gt;, &lt;code&gt;streams&lt;/code&gt;), and generates 5 name candidates using a mix of patterns — &lt;code&gt;adjective-noun&lt;/code&gt;, &lt;code&gt;verb-noun&lt;/code&gt;, &lt;code&gt;noun-type&lt;/code&gt;, and combinations like &lt;code&gt;verb-keyword-cli&lt;/code&gt;. Each candidate gets checked against the registry and marked available or taken.&lt;/p&gt;

&lt;p&gt;If you don't love the batch, you can regenerate as many times as you want. When you pick a name that's already taken, it fetches and prints the existing package's metadata so you know what you're up against.&lt;/p&gt;

&lt;h2&gt;
  
  
  Non-interactive and CI-friendly modes
&lt;/h2&gt;

&lt;p&gt;For scripting and pipelines, there are a few flags worth knowing:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generate names from a description, no prompts:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gen-package-name &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"http request retry logic"&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pass keywords directly, show only available names, output JSON:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gen-package-name &lt;span class="nt"&gt;-k&lt;/span&gt; http,retry,fetch &lt;span class="nt"&gt;-c&lt;/span&gt; 10 &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nt"&gt;-j&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The JSON output is machine-readable, so you can pipe it into other tools or use it in a script that automates your publish workflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick availability check for a specific name:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gen-package-name &lt;span class="nt"&gt;--check&lt;/span&gt; my-package-name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fetch registry metadata for any existing package:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gen-package-name &lt;span class="nt"&gt;--info&lt;/span&gt; chalk &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How the name generation works
&lt;/h2&gt;

&lt;p&gt;The generator builds name candidates by randomly combining words from several pools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Adjectives&lt;/strong&gt; — words like &lt;code&gt;tiny&lt;/code&gt;, &lt;code&gt;fast&lt;/code&gt;, &lt;code&gt;safe&lt;/code&gt;, &lt;code&gt;sharp&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verbs&lt;/strong&gt; — words like &lt;code&gt;parse&lt;/code&gt;, &lt;code&gt;watch&lt;/code&gt;, &lt;code&gt;build&lt;/code&gt;, &lt;code&gt;resolve&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nouns&lt;/strong&gt; — a curated list plus your own keywords injected at the top&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Package type suffixes&lt;/strong&gt; — &lt;code&gt;lib&lt;/code&gt;, &lt;code&gt;cli&lt;/code&gt;, &lt;code&gt;kit&lt;/code&gt;, &lt;code&gt;util&lt;/code&gt;, &lt;code&gt;core&lt;/code&gt;, and so on&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The patterns it uses are the same conventions that popular npm packages already follow: &lt;code&gt;adjective-noun&lt;/code&gt; (&lt;code&gt;tiny-emitter&lt;/code&gt;, &lt;code&gt;fast-glob&lt;/code&gt;), &lt;code&gt;verb-noun&lt;/code&gt; (&lt;code&gt;parse-json&lt;/code&gt;, &lt;code&gt;watch-config&lt;/code&gt;), and &lt;code&gt;noun-type&lt;/code&gt; (&lt;code&gt;router-kit&lt;/code&gt;, &lt;code&gt;cache-lib&lt;/code&gt;). When you supply two or more keywords, it also generates &lt;code&gt;keyword-keyword&lt;/code&gt; pairs — things like &lt;code&gt;http-client&lt;/code&gt; or &lt;code&gt;auth-token&lt;/code&gt; — which tend to be the most memorable names.&lt;/p&gt;

&lt;p&gt;Candidates are validated against npm's naming rules before they're even checked against the registry (lowercase alphanumeric and hyphens, no leading/trailing hyphens, max 214 chars), so you never get back something that would fail at publish time anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full CLI reference
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Usage: gen-package-name [options]

Options:
  -V, --version          print version and exit
  -d, --description      describe the package (keywords extracted automatically)
  -k, --keywords &amp;lt;list&amp;gt;  comma- or space-separated keyword seeds
  -c, --count &amp;lt;n&amp;gt;        number of names to generate (default: 5)
  -a, --available-only   only output names available on npm
  -j, --json             emit machine-readable JSON
  -n, --non-interactive  skip prompts; print results and exit
  --check &amp;lt;name&amp;gt;         check whether a specific name is available
  --info &amp;lt;name&amp;gt;          fetch npm registry metadata for a package
  --no-color             disable colored output
  -h, --help             display help
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Stack
&lt;/h2&gt;

&lt;p&gt;The package is written in TypeScript, targets Node.js ≥ 16, and uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.npmjs.com/package/commander" rel="noopener noreferrer"&gt;Commander.js&lt;/a&gt;&lt;/strong&gt; for argument parsing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.npmjs.com/package/inquirer" rel="noopener noreferrer"&gt;Inquirer&lt;/a&gt;&lt;/strong&gt; for the interactive prompts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.npmjs.com/package/chalk" rel="noopener noreferrer"&gt;chalk&lt;/a&gt;&lt;/strong&gt; for colored output&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.npmjs.com/package/package-json" rel="noopener noreferrer"&gt;package-json&lt;/a&gt;&lt;/strong&gt; to query the npm registry&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No exotic dependencies, no network calls beyond the registry lookups.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;npm:&lt;/strong&gt; &lt;a href="https://www.npmjs.com/package/gen-package-name" rel="noopener noreferrer"&gt;npmjs.com/package/gen-package-name&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/f3rnox/gen-package-name" rel="noopener noreferrer"&gt;github.com/f3rnox/gen-package-name&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hope it saves you at least one naming spiral. 🛠️&lt;/p&gt;

</description>
      <category>node</category>
      <category>npm</category>
      <category>cli</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
