You are browsing as a guest. Sign up (or log in) to start making projects!

CLI Calculator

  • 6 Devlogs
  • 14 Total hours

An interactive CLI-based computation tool built with Node.js that supports both command-driven operations and direct mathematical expressions, featuring result memory, advanced math functions, persistent history and a REPL-based interface with a live browser demo powered by xterm.js, deployable as a fully static site with no installation required.

Ship #1

## What did you make?

A CLI calculator built from scratch in Node.js. Not just a basic one, it runs as a full REPL in your terminal, supports both command-driven operations (`add 2 3`, `sin 90`) and free-form math expressions (`2 + 3 * 4`, `sin(90) + 10`), remembers your last result with `ans`, logs every calculation to a history file with timestamps and validates inputs with actual helpful error messages.

Over the last week I also ported the whole thing to run in the browser using xterm.js so you can try it live without installing anything.

---

## What was challenging?

Honestly, most of it at some point.

The expression evaluator was probably the hardest part. Using `Function()` to evaluate raw user input feels powerful but also kind of risky and making it fail gracefully without breaking anything took a while to get right. The `ans` memory system was tricky too because replacing it correctly across both commands and expressions without accidentally mangling other words required careful regex work. The browser port was a whole different kind of hard. I didn't realize how much Node.js was doing for me silently. `readline` alone handles an entire input loop that I had to rebuild character by character in xterm.js.

---

## What are you proud of?

A few things:

- The **validation architecture** — argument counts, type checks, domain-specific errors (division by zero, negative square roots, log of non-positives) all cleanly separated from the actual math logic

- The **`ans` memory system** — being able to chain `pow 2 8` → `add ans 100` → `ans / 2` makes it feel like a real calculator

- The **expression evaluator** — typing `sin(90) + 10` directly and getting `11` feels satisfying every time

- The **history system** — timestamped, persistent, filterable with `history [n]`

- The fact that the browser demo runs the exact same logic as the CLI — `calc.js` was never touched, the port lives in a separate file

This was built step by step over 6 devlogs in about a week. I'm still a beginner and it's not perfect but it genuinely feels like something I made rather than something I just followed a tutorial for.

---

## What should people know so they can test your project?

The easiest way is the live browser demo — no install, just open the link:

🔗 https://cli-calculator-five.vercel.app/

Try these to get a feel for it:

```
help
add 2 3
pow 2 8
add ans 100
sin(90) + 10
2 + 3 * 4
history
```

If you want to run it locally instead:

```bash
git clone https://github.com/AidenHammy/cli-calculator.git
cd cli-calculator
npm install
node src/calc.js
```

  • 6 devlogs
  • 14h
  • 13.37x multiplier
  • 190 Stardust
Try project → See source code →
Open comments for this post

1h 43m 29s logged

Devlog 6 — Taking the CLI to the Browser


Biggest struggle

The hardest part wasn’t writing new features. It was translating existing ones into a completely different environment.

The original CLI felt natural because Node.js gives you everything: file system, stdin/stdout, process control. The browser gives you none of that. Every tool I had been relying on: readline, fs, chalk, console.log even process either doesn’t exist or behaves differently in a browser context.

The trickiest translation was the input loop. readline handles line buffering automatically, you just wait for the user to press Enter and get the full line back. With xterm.js, you get one raw character at a time via onData.

The other catch was the expression evaluator. After porting everything over, sin(90) + 10 was returning "Invalid command or expression" because the Function() scope didn’t know what sin was. The fix was straightforward once I understood the cause, exposing the math functions as local constants inside the Function() string but it was a good reminder that a working feature in one environment isn’t automatically working in another.


What changed in this version

New files

  • src/calc-demo.js:- full browser port of calc.js using xterm.js
  • main.js:- entry point: boots xterm, loads the fit addon, calls initCalc(term)
  • index.html:- static shell with topbar, terminal mount and statusbar
  • style.css:- dark terminal theme built around a monospace font stack and a single green accent

Key substitutions made

Node.js (calc.js) Browser (calc-demo.js) readline xterm.js onData + manual buffer chalk ANSI escape codes fs / history.txt localStorage console.log term.write() via print() process.exit location.reload()

calc.js is untouched. The original CLI still runs exactly as before with node src/calc.js. The browser build is a separate port not a replacement.


Current features

  • Everything from Devlog 5, now running in the browser
  • Live deployment on Vercel. No install, no terminal, just a URL
  • Terminal emulator UI with topbar, statusbar and live clock
  • Full xterm.js input handling: backspace, local echo, line buffering
  • History persisted in localStorage across page reloads
  • Expression evaluator now supports sin(90) + 10, sqrt(144), abs(-5) etc. directly
  • cls clears the xterm screen properly using escape sequences
  • exit reloads the page

Reflection

Devlog 5 ended with “maybe create a simple web version for easier demo access” as a future idea. This devlog is that idea, actually shipped.

What surprised me most was how much invisible work Node.js was doing. You write readline and it just works but behind it is a whole input loop, line buffering, echo management and terminal control that you never think about. Doing it manually in xterm.js made all of that visible which was frustrating at first but genuinely useful to understand.

The project now has two lives: a proper Node.js CLI tool and a browser demo that looks and feels identical. That separation, keeping calc.js untouched and building the browser port as a parallel file felt like the right architectural call. One doesn’t compromise the other.

It’s also the first time something I built has a real public URL.


0
0
3
Open comments for this post

4h 38m 42s logged

Devlog 5 — Expressions, Memory and a Real “Calculator Feel”


What I learned:

  • How to evaluate user input dynamically using Function() (and why it needs to be handled carefully)
  • How to replace parts of user input using regex.
  • How to structure commands when the number of arguments isn’t always fixed (min/max validation)
  • How small UX details (like colors using chalk) make a big difference in usability
  • How to maintain state across a REPL session
  • How to design a fallback system where unknown commands are treated as expressions

Biggest struggle:

This one was definitely more complex than the previous days.

The hardest part was adding expression evaluation.
Using something like Function() to evaluate user input felt powerful but also risky and confusing at first. Making sure it doesn’t break on invalid input and handling errors properly took some time. Another tricky part was implementing a feature (ans) that lets you reuse the previous result in new calculations. Ensuring it worked consistently across both commands and expressions required careful handling of user input. Replacing it correctly in user input without breaking other words or expressions required using regex carefully.

Also, as the number of commands grew, validation started getting messy. Switching to a min/max argument system helped but it took a bit of thinking to get right. This stage felt less like “just coding” and more like designing how the calculator should behave.


Current features:

  • Full REPL-based interactive CLI

  • Support for both commands and direct expressions:

    add 2 3
    2 + 3 * 4
    
  • ans keyword to reuse the previous result

  • Expanded math operations:

    • Trigonometry (sin, cos, tan)
    • Logarithms (ln, log)
    • Factorial (fact)
    • Rounding (round, ceil, floor)
    • Absolute value (abs)
  • Improved validation with flexible argument handling (min/max support)

  • Persistent history with timestamps

  • Optional history limit (history n)

  • Colored and structured output using chalk

  • Better error handling for invalid commands and expressions


Next steps:

  • Improve expression parsing (support more complex cases safely)
  • Possibly replace Function() with a safer custom parser
  • Add command chaining or multi-step calculations
  • Improve UI further (custom prompt, better formatting)
  • Maybe create a simple web version for easier demo access

Reflection:

This version feels like a big step forward. Before, the calculator was command-based and structured. Now it feels much more natural and flexible, especially with expressions and the ans feature. It’s starting to behave like an actual calculator instead of just a program that runs specific commands.


At the same time, the complexity is definitely increasing. It’s getting harder to manage everything cleanly which makes me realize how important structure and planning are as projects grow. It’s not perfect but it finally feels like something I could actually use. Not just something I built to learn.

0
0
2
Open comments for this post

1h 47m 46s logged

Devlog 4 — Making it Interactive (REPL + UX Improvements)

What I learned:

  • How to use readline to keep the program running and take input again and again
  • The difference between a script that runs once vs something interactive
  • How small UX things (like input formatting and cleaner output) actually matter a lot
  • How to handle messy user input using trim() and split(/\s+/)
  • How to structure code so it doesn’t become a mess when it keeps running in a loop

Biggest struggle:

This part wasn’t “hard” in the usual sense but it was confusing at first.

Before this, my program would just run once and end. Now it stays alive and that changes how everything works. I had to think differently about how commands are handled because now the same logic runs over and over again.

Input handling was also kind of annoying. Even small things like extra spaces could break stuff which forced me to actually deal with it properly instead of ignoring it.

Another thing was output. At some points it felt too cluttered and at others it felt like not enough information was being shown. Finding a balance there took a bit of trial and error.


Current features:

  • REPL system using readline (no need to restart the program every time)

  • Continuous input like a real CLI

  • Support for both commands and symbols (add + +, etc.)

  • Better input handling (spaces don’t break things anymore)

  • Validation for:

    • wrong number of arguments
    • invalid numbers
    • division by zero
    • square root of negative numbers
  • Cleaner output (less unnecessary logs)

  • History system:

    • saved to history.txt
    • can view with history
    • can reset with clear
  • cls to clear the screen

  • exit to quit the program


Next steps:

  • Support actual expressions like 2 + 3 * 4 instead of strict commands
  • Make history a bit smarter (maybe timestamps or search)
  • Reduce repeated logic even more
  • Add vector/matrix operations
  • Add autocomplete for commands
  • Add colored terminal output

Reflection:

This is probably the first version that actually feels like a real program.

The REPL made a huge difference. Before, it felt like I was just testing functions. Now it actually feels like something you can use.

Most of the improvements in this version aren’t big features, but small things that make the experience smoother. Stuff like handling spaces properly or adding aliases doesn’t sound like much but it changes how the program feels.

I’m starting to notice that building something usable is very different from just making something work.

It’s still simple and there’s a lot more I could improve but this is the first time the calculator feels complete in its own way.

1
0
31
Open comments for this post

2h 5m 3s logged

Devlog 3 — Persistence and Control (File-based History + Clear Command)

What I learned:

  • Why CLI programs reset completely every time they are executed
  • How to use the fs module to write and read files in Node.js
  • How appendFileSync can be used to store data incrementally
  • How to read stored data using readFileSync
  • The difference between in-memory state and persistent state
  • How small bugs (like variable naming or extra trims) can break logic in subtle ways
  • Why validation, execution and side effects (like saving history) should stay separated

Biggest struggle:

This part was less chaotic than Day 2 but still frustrating in a different way.

The logic itself wasn’t the problem this time. It was understanding how programs behave over time. I had already built a history system before so seeing it disappear every run was confusing at first.

Then came the file system part. Writing to a file felt simple but reading it back properly and formatting it without breaking things took more effort than expected. Small mistakes like misnaming variables or structuring loops incorrectly kept causing issues.

It wasn’t overwhelming chaos but more like constant small friction that slowed everything down.


Current features:

  • Persistent history system using fs
  • Calculations are stored in history.txt across runs
  • history command displays formatted past calculations
  • clear command wipes stored history clean
  • Improved validation and error messages
  • Clean separation between parsing, validation and execution

Next steps:

  • Convert the calculator into an interactive CLI (REPL-style)
  • Reduce repeated validation logic into reusable helpers
  • Improve overall UX (cleaner outputs and command feedback)

Reflection:

The biggest realization was understanding that programs don’t remember anything unless you explicitly store it. That changed how I think about state completely.

Compared to earlier struggles, this one felt quieter but still tiring. Less confusion, more persistence. Fixing small issues, understanding behavior and slowly making things more stable.

It still isn’t perfect but it finally feels like the calculator has some sense of continuity instead of resetting every time.

Note: The screenshot does not show all features (full validation system).

0
0
5
Open comments for this post

3h 2m 28s logged

Devlog 2 — CLI Calculator Upgrade (Validation + History System)


What I learned:

  • How to structure validation into layers (arity type domain)
  • How CLI arguments actually behave — missing inputs simply don’t exist in the array
  • How to use slice() + map(Number) to normalize input in one step
  • Why separating parsing validation and execution simplifies reasoning
  • How function hoisting allows referencing functions before declaration
  • How to maintain in-memory state using arrays
  • How for loops and forEach help format structured output
  • How small design choices like a numbers array simplify architecture

Biggest struggle:

This part was genuinely exhausting.

I kept getting stuck on when validation should happen and what exactly needed to be validated. I was duplicating checks mixing raw CLI arguments with parsed numbers and constantly second-guessing my logic.

I also misunderstood CLI arguments. I expected missing inputs to appear as undefined which made my validation more complicated than necessary.

The worst part was the flow. Everything felt tangled. Command lookup parsing validation execution like it was happening all at once. Even when the code worked I didn’t fully trust it which made the whole process frustrating.


Current features:

  • Command routing using a function map (commands[cmd])
  • Argument validation based on expected counts
  • Clean input parsing using slice() and map(Number)
  • Domain rules (division by zero negative square roots)
  • Scientific operations: sqrt pow log
  • In-memory history system
  • history command for session logs
  • Improved help command
  • More consistent error handling

Next steps:

  • Persist history to a file
  • Refactor validation into helper functions
  • Improve error messages

Reflection:

Validation was mentally draining. I kept mixing up arity type and domain rules and the code felt like a pile of checks instead of a system. It worked but I didn’t trust it.

The biggest reality check was the history system. I built it saw it work then ran another command and everything was gone. That’s when it clicked. CLI programs don’t persist memory. Every run starts fresh.

That was the most important lesson. Not everything you build “stays” unless you store it. It changed how I think about programs.

By the end the calculator is still simple but my understanding of structure and flow is much stronger. It feels less like guessing and more like knowing what I’m doing.

Note: The screenshot only shows a basic run and does not include all implemented features (validation command handling improvements and history tracking).

0
0
8
Open comments for this post

55m 47s logged

Devlog 1 — Building a CLI Calculator

What I set out to build :

A command-based CLI calculator using Node.js that can take input directly from the terminal and perform operations like addition, subtraction multiplication and division.


What I learned:

  • How process.argv works for handling command-line input

  • Why "5" + "3" results in "53" (strings vs numbers in JavaScript)

  • How to convert inputs properly using Number()

  • The difference between calling a function and referencing it

  • How to implement a command map using commands[cmd]()


Biggest struggle:

Understanding how to structure the command system cleanly without relying on long and messy if/else chains.


Breakthrough moment:

Realizing that I could store functions inside an object and dynamically execute them based on user input. That made the whole system feel much cleaner and scalable.


Current features:

  • Supports add, sub, mul and div commands
  • Handles invalid commands with basic error messaging
  • Uses a command map for cleaner execution logic

Next steps:

  • Add scientific functions like sqrt, pow, and log
  • Improve input validation
  • Potentially add a history system

Reflection:

Started this knowing almost nothing about CLI tools or Node.js input handling and ended up building a working command-based system. It’s simple but it actually feels like the foundation of something bigger.

0
0
28

Delete project?

Are you sure you want to permanently delete this project? This action cannot be undone.

All devlogs, followers, and associated data will be removed.

Followers

Loading…