How I built a developer tool in one week
I am always looking for interesting app ideas that I can build. I don’t exactly remember how I got this particular one, but recently I had the idea for building a tool that helps with the customization of the MacOS terminal. I took some time off my other projects and built betterterminal.com
I usually run into the trap of starting a new, seemingly quick project, and then get caught up in perfecting every possible aspect about it. For this project, I set myself the goal of finishing the product within a week.
In this writeup, I want to recap why I built this, and how I went from idea to finished product within only 7 days.
The idea
Anyone who writes code will need to use the terminal at some point. If you’re not familiar with it, it’s basically built in application in MacOS, which lets you interact with your computer through a command line interface.
It's used for various tasks, such as navigating your computer's file system—similar to using the Finder application, but with commands instead of clicks. And instead of seeing the files and folders change in your finder, you receive text responses with the requested information.
Anyways, lets get back to the idea. The default MacOS terminal is just a black window with white text. While powerful, its appearance is quite basic. Years ago, I watched coding tutorials where the instructor's terminal looked different from mine - it had appealing colors, a nice font, and displayed useful information that my terminal didn’t show.
After finding a tutorial on how to customize my terminal, I followed the necessary steps. It was a pain. I had to navigate through mystical configuration files on my Mac and was constantly worried that I could break something important. I had to convert HEX color codes into a format the terminal could interpret and use complex escape sequences to add extra info to my command line prompt.
In the end, I successfully changed the appearance of my terminal and have been enjoying the benefits ever since. Now again, I don’t know why this idea crossed my mind only now, but I figured that I could probably create an app to simplify that entire process. By handling the complexities in the backend, it would allow users to focus on the visual aspects via a user-friendly graphical interface—an option I would have appreciated back then.
Implementation Plan
I had a rough idea for a product but no clue how to build it, how much effort it would take, or even what features to include. I also didn't want to start another big project that would take up my time for weeks. To figure out if it was a good idea to dive deep into this project, I did the only logical thing: I asked ChatGPT for help.
Question 1: "What customization options are there for the Mac OS terminal?"
Information received: I learned that I could customize visual things like color schemes and fonts, tweak the command prompt, set custom aliases, add functions, and more.
Great, there were quite a few things to adjust, I realized. Now I just needed to know if I could do all this programmatically, or if my idea was a no-go.
Question 2: "Is it possible to configure all these settings through a file? For example, can all of these settings be adjusted through the bashrc file?"
Information received: Yes, it's possible to control some of these aspects through files, but other preferences, like terminal themes, need to be adjusted through the terminal application settings. I also had to think about different shell types that people might use.
That was a bit disappointing because I thought I wouldn't be able to let users customize their color schemes through my app. However, I kept asking more questions, and ChatGPT introduced me to AppleScript, a scripting language by Apple that can execute commands on a Mac, like changing application settings. (To this day, I don’t fully understand AppleScript; it was a lot of trial and error with ChatGPT.)
Question 3: "Would it be possible to create an app with a web interface that lets the user configure his terminal style to his preferences, and then download a file or a code snippet that will apply the settings all at once?"
Information received: Yes, it's possible.
Great, the feasibility was confirmed. With half a plan in place, it was time to make the same mistake as always, and start to code right away. To be fair, I did look up some Youtube videos on the topic first, to confirm that they have a lot of views.
Tech Stack
To get a headstart on this project, I used the popular ShipFast NextJS boilerplate to get started. It includes a lot of starter code around user authentication, payment processing and more. I already built Survser on top of this boilerplate, so I was pretty familiar with the codebase I was about to dive into. Here are the main tools I am making use of:
- NextJS: Serves as the main application framework.
- MongoDB + Mongoose: Manages the database.
- NextAuth: Handles user authentication.
- Stripe: Processes payments.
- Zeptomail: Sends transactional emails.
- AWS S3: Hosts files.
Overall, the tech stack is relatively straightforward. I had some concerns that JavaScript might not be the best choice for an app that needs to modify configurations on a user’s machine, but I decided to go ahead and test it out.
How the code works
I won’t go into too much detail here, but I wanted to give a quick overview of how the code works, and the challenges that I encountered.
On the frontend, we basically have an editor that allows the user to edit three main aspects of their terminals: Theme, aliases and prompt.
Theme
Setting up the theme editor was quite easy, since the user is just able to set background- and text color, as well as text size and font. The font selection is probably the trickiest part, since I need to make sure that they are standard fonts that can work on every Mac. To be quick, I just chose a few ones to get started, and will expand that selection later on.
Aliases
This part was also pretty simple, since the user just needs to specify a command and its corresponding alias. I just had to make sure not to allow whitespace characters in the alias, as I discovered that it results in errors in the terminal.
Prompt
This was the trickiest part. First of all, I needed to specify the selection of dynamic prompt segments, such as username, date, current directory, etc.. Each of these have a special escape sequence code - here’s an example:
{
code: "\\W",
label: "Basename of Working Directory",
description: "The basename of the current working directory."
}
I also added the option for the user to specify prompt segments through free text input.
The trickiest part about this was, that I wanted the user to be able to specify a color for each segment of the prompt, for example:
I also allowed users to add prompt segments through free text input. One of the bigger challenges was enabling users to assign colors to each prompt segment. Terminal configurations don't accept HEX codes directly, so I used “tput” colors. This required creating a manual mapping from HEX to tput, resulting in a more limited color palette.
Access Restriction & Payment
Since I wasn’t planning on releasing this tool for free, I also had to figure out how to handle payment and user authentication. I didn’t want to require users to sign up before being able to play around with the editor. In the end, users can create their configuration, and are redirected to Stripe in order to pay, before they can access the installation instructions for their terminal configuration. Once my app receives the payment webhook, I automatically create an account for the user, and they are able to access the installation instructions.
I intended this tool to be a paid product, so I had to consider how to manage payment and user authentication. I wanted users to experience the editor without signing up first. After configuring their terminal, users are directed to Stripe for payment before they can download the installation instructions. Once payment is confirmed through a webhook, an account is automatically created for the user, giving them access to their configurations.
This overview covers the frontend implementation, which, despite some challenges, wasn't overly complicated. Next, I'll explain how the tool functions on the backend.
Backend Logic
Once a user finalizes their configuration and sends the save request, I store the configuration in the database. Upon receiving payment, I use a reference ID from the Stripe webhook to mark the configuration as paid and begin generating the necessary scripts for installing the configuration on the user's device. Here's what I create:
- AppleScript to set terminal theme: This script instructs the terminal application to create a new theme based on the user's specified colors and fonts. It also makes a backup of the existing theme.
- AppleScript to revert terminal theme: If the user decides to revert, this script restores the previous theme from backup and removes the new theme.
- Shell script for new shell configuration: This script updates the
.bashrc
or.zshprofile
file to include the custom prompt configuration and defines the necessary color variables. It also defines helper functions, likerevert_terminal
, to restore the previous settings. - Main shell script: This script first backs up the files it will modify, then executes the AppleScript, and inserts the content from the shell script into the
.rc
file. Afterwards, it cleans up by deleting itself and any other non-essential files to avoid cluttering the user's hard drive.
Building these scripts with JavaScript was a bit tricky, particularly with correctly escaping characters. Here’s a snippet from the function responsible for constructing the prompt:
const constructPrompt = (config, content) => {
content = addGitFunction(config, content)
content += '# Prompt Configuration\n'
content += `PS1="\\n";\n`
config.prompt.forEach((item, index) => {
let value =
item.type === 'sequence'
? `\\${item.code}`
: item.type === 'git'
? `\\$(parse_git_branch)`
: item.value
let colorCode = `\\[$\{color${index + 1}}\\]`
let promptString = `${colorCode}${item.type === 'git' ? ' ' : ''}${
item.type === 'git' ? value : wrap(item.wrapIn, value)
}`
if (item.addSpace && shouldAddSpace(index, config.prompt)) {
promptString += ' '
}
content += `PS1+="${promptString}";\n`
})
content += `PS1+="\\[$\{reset}\\]";\n`
content += `export PS1;\n`
return content
}
After generating these files, I package them into an archive and upload it to an AWS S3 bucket. Users are then given a shell command on the frontend that allows them to download, unzip, and execute the files in a single action.
Whats next
Building BetterTerminal within a week has been a fun challenge. I had to overcome some technical challenges, but finally created a tool that brings simplicity and efficiency to MacOS terminal customization. It’s definitely a tool that I’ll happily use for myself.
Community Feedback and Next Steps
Since launching, I’ve started sharing BetterTerminal in various online communities and the feedback has been really positive so far. I want to keep increasing the tool's reach to attract more users and hopefully establish a small, steady revenue stream.
Future Directions
Looking forward, I have several ideas for new features that could enhance the usability and appeal of BetterTerminal. The implementation of these features will largely depend on user adoption—I am keen to develop this tool further, but want to ensure that these developments are in direct response to user needs and feedback.
Get in Touch
For those who have questions or need assistance with BetterTerminal, please feel free to reach out via email at pk@pkundr.com. Additionally, if you or someone you know is looking to hire a skilled developer, I am available for new projects. Let's work together to create something amazing.