The Audience of One: Why I Replaced My Entire Tech Stack with Bespoke Code
The Audience of One: Why I Replaced My Entire Tech Stack with Bespoke Code
Using standard developer tools is like renting an apartment in a massive, brutalist housing complex. The plumbing generally works and the heating is functional. However, the walls are a color you despise, and management strictly forbids drilling holes. For twenty-five years, I lived in these apartments. I customized my Vim dotfiles until they looked like a ransom note. I wrestled with tmux configurations and nested bash scripts, battling arcane window manager plugins to force generalized software to bend to the idiosyncratic ways my brain processes logic.
It never quite fit.
When you use text editors like VS Code or whatever terminal emulator currently dominates Hacker News, you are fundamentally using software built for everyone. That means the architecture carries the dead weight of millions of edge cases you will never encounter. An editor has to support every language. It must also account for obscure keyboard layouts and remote SSH protocols in existence.
I used to think writing my own window manager or text editor was a sign of madness. It was a multi-year, heroic undertaking reserved for people who didn't have real product deadlines. But the economics of software creation flipped entirely over the last year.
The barrier to entry evaporated. I realized the gap between "I wish my tool did X" and "Here is a tool that does exactly X" had shrunk to a few focused evenings. So, I stopped renting. I started building my own house, brick by hyper-personalized brick.
The Burden of Generalized Software
We accept a massive cognitive overhead when we rely on commercial or heavily adopted open-source tools. Software complexity scales exponentially with the number of users. If you build an app for ten thousand people, you are building ten thousand different apps glued together with configuration files.
Think about the last time you wanted a specific behavior in your workflow. Maybe you wanted your text editor to automatically push to a specific Git branch, but only if the file extension was .md, and you wanted it to trigger a custom script that updates a local SQLite database for your blog.
To achieve this in a standard environment, you have to:
- Learn the plugin API of your editor.
- Write a messy wrapper script.
- Fight with the editor's "sensible defaults" which inevitably conflict with your specific desire.
- Maintain that fragile bridge across software updates.
I got exhausted. I realized I was spending an hour a week shaving yaks just to make my tools marginally less annoying. The breaking point came when I looked at my file manager. It was a bloated beast of a program that I used for exactly three operations.
I didn't need a file manager. I needed a script that did exactly what I wanted, the way my fingers wanted to type it, without any configuration flags.
The "Audience of One" Philosophy
When you build software for an audience of one, the entire engineering paradigm shifts. You experience a profound, almost uncomfortable liberation.
Consider the things you completely eliminate:
- No Settings: If I want the text to be green, I hardcode the hex value. There is no configuration file.
- No Documentation: I know how it works because my brain designed the shortcuts.
- No Edge Cases: I will never run this on a Windows machine. I will never use it with a Dvorak keyboard. I don't need to support those states.
- No Feature Creep: The software is considered "feature complete" the moment it solves my immediate annoyance.
With the assistance of modern LLMs, writing this kind of software is no longer a chore. I treat the AI like an incredibly fast, highly skilled contractor. We pair on the boring boilerplate, and I spend my cognitive energy on the strict, opinionated logic.
Let me show you exactly what this looks like in practice.
Implementation: Replacing the CLI Dashboard
I used to rely on a massive, unwieldy combination of tmux and zsh plugins, held together by shell aliases to manage my daily context switching. It was slow. It broke when I updated Node versions.
I decided to replace it with a bespoke TypeScript daemon. I call it Core. It doesn't have a repository for public consumption. It lives in a folder on my machine, and it assumes everything about my file system.
The Bespoke Workspace Manager
Here is a stripped-down version of how I handle my project context now. Notice the sheer audacity of hardcoding paths. It is beautiful in its selfishness.
import { execSync } from 'child_process';
import { existsSync, readdirSync } from 'fs';
import { join } from 'path';
// Hardcoded assumptions. No environment variables. No config parsing.
const BASE_DIR = '/Users/me/Engineering';
const ACTIVE_PROJECTS = ['bespoke-editor', 'client-dashboard', 'api-gateway'];
interface ProjectStatus {
name: string;
branch: string;
dirty: boolean;
}
class WorkspaceManager {
constructor(private readonly baseDir: string) {
if (!existsSync(this.baseDir)) {
throw new Error(`Critical: Base directory ${this.baseDir} missing. Did you buy a new laptop?`);
}
}
public getStatus(): ProjectStatus[] {
return ACTIVE_PROJECTS.map(project => {
const projectPath = join(this.baseDir, project);
if (!existsSync(projectPath)) {
return { name: project, branch: 'MISSING', dirty: false };
}
const branch = this.runCommand(projectPath, 'git rev-parse --abbrev-ref HEAD');
const status = this.runCommand(projectPath, 'git status --porcelain');
return {
name: project,
branch: branch || 'detached',
dirty: status.length > 0
};
});
}
public bootProject(projectName: string): void {
const projectPath = join(this.baseDir, projectName);
if (!existsSync(projectPath)) return;
console.log(`Booting context for ${projectName}...`);
// I always use exactly these panes in my terminal.
// No generic window layouts. Just exactly what I want.
const commands = [
`cd ${projectPath} && nvim .`,
`cd ${projectPath} && npm run dev`
];
// Execute my custom multiplexer setup (abstracted for brevity)
this.launchCustomMultiplexer(commands);
}
private runCommand(cwd: string, cmd: string): string {
try {
return execSync(cmd, { cwd, encoding: 'utf-8', stdio: 'pipe' }).trim();
} catch {
return '';
}
}
private launchCustomMultiplexer(commands: string[]) {
// In reality, this interfaces directly with my terminal emulator
console.log('Executing:', commands);
}
}
// Execution is instant.
const cli = new WorkspaceManager(BASE_DIR);
console.table(cli.getStatus());Look at this code. If you tried to submit this to a public repository, you would be crucified in the code review. People would complain about hardcoded paths and demand a YAML config, or they would question your lack of Mercurial support.
I don't care. I am the user. I use Git. My projects live in /Users/me/Engineering. By dropping the requirement to satisfy strangers, I wrote a context switcher that executes in 12 milliseconds and does exactly what I need.
The Big Jump: Writing My Own Text Editor
Replacing my shell scripts was the gateway drug. The real test was replacing Vim.
I used Vim for decades. The muscle memory is so deeply ingrained that I accidentally type :wq in Slack messages. But I realized I only used about 5% of Vim's actual capabilities. The other 95% was a complex ecosystem of plugins I barely understood, constantly breaking each other.
I wanted a modal text editor that did exactly four things perfectly:
- Open markdown and TypeScript files.
- Auto-wrap text at 80 characters without breaking URLs.
- Have a built-in interface to an LLM without leaving the buffer.
- Save files automatically on every keystroke.
I didn't use Rust or C. I used Node.js and TypeScript, manipulating raw terminal streams.
Terminal Raw Mode: The Foundation
Building a TUI (Terminal User Interface) in Node is surprisingly straightforward once you understand raw mode. You are essentially hijacking standard input and output, bypassing the OS's normal line-buffering.
import * as readline from 'readline';
import * as fs from 'fs';
// A highly stripped down version of my personal editor's core loop.
class BespokeEditor {
private cursor = { x: 0, y: 0 };
private lines: string[] = [''];
private filename: string;
constructor(filename: string) {
this.filename = filename;
this.init();
}
private init() {
if (fs.existsSync(this.filename)) {
const content = fs.readFileSync(this.filename, 'utf-8');
this.lines = content.split('\n');
}
// The magic happens here: raw mode.
readline.emitKeypressEvents(process.stdin);
process.stdin.setRawMode(true);
process.stdin.on('keypress', (str, key) => this.handleInput(str, key));
this.render();
}
private handleInput(str: string, key: readline.Key) {
// My custom exit sequence: Ctrl+Q
if (key.ctrl && key.name === 'q') {
process.stdout.write('\x1b[2J\x1b[0f'); // Clear screen
process.exit(0);
}
// Arrow keys for navigation
if (key.name === 'up' && this.cursor.y > 0) this.cursor.y--;
if (key.name === 'down' && this.cursor.y < this.lines.length - 1) this.cursor.y++;
if (key.name === 'left' && this.cursor.x > 0) this.cursor.x--;
if (key.name === 'right' && this.cursor.x < this.lines[this.cursor.y].length) this.cursor.x++;
// Typing characters
if (!key.ctrl && !key.meta && str && str.length === 1) {
const line = this.lines[this.cursor.y];
this.lines[this.cursor.y] =
line.slice(0, this.cursor.x) + str + line.slice(this.cursor.x);
this.cursor.x++;
this.autoSave();
}
// Backspace
if (key.name === 'backspace' && this.cursor.x > 0) {
const line = this.lines[this.cursor.y];
this.lines[this.cursor.y] =
line.slice(0, this.cursor.x - 1) + line.slice(this.cursor.x);
this.cursor.x--;
this.autoSave();
}
this.render();
}
private render() {
// Clear screen and move cursor to top left
process.stdout.write('\x1b[2J\x1b[0f');
// Render lines
this.lines.forEach((line, i) => {
process.stdout.write(`${line}\n`);
});
// Move cursor to actual position
process.stdout.write(`\x1b[${this.cursor.y + 1};${this.cursor.x + 1}H`);
}
private autoSave() {
// I don't want to think about saving. It just happens.
fs.writeFileSync(this.filename, this.lines.join('\n'));
}
}
// Boot the editor
const targetFile = process.argv[2] || 'scratch.txt';
new BespokeEditor(targetFile);This is just a prototype framework, but it illustrates the concept. From this foundation, I added a command mode activated by the Escape key, just like Vim.
But here is where the "Audience of One" shines. I wanted an AI integration. I didn't want to write a generic plugin that supports OpenAI and Anthropic, along with local models via a dropdown menu. I just hardcoded an API call to my preferred model. If I press Ctrl+A, the editor grabs the current paragraph, sends it to the API with a hardcoded prompt ("Rewrite this to be more punchy and direct"), and replaces the text inline.
It took me forty-five minutes to build that feature. If I were building it for an open-source project, it would have taken three weeks of debates in the issue tracker covering API key security and retry logic, plus user interface preferences.
The Pragmatics of Maintenance
One of the biggest arguments against building your own tools is maintenance burden. "What happens when an OS update breaks your app?"
I genuinely don't worry about it anymore. Because the codebase is so small, so completely devoid of abstraction layers meant to protect hypothetical users, debugging is trivial. When something breaks, I open the source code, find the hardcoded value that is throwing a fit, change it, and restart the daemon.
It is entirely analogous to a craftsman maintaining their own physical tools. A carpenter doesn't send their chisel back to a massive corporation when it gets dull; they take it to the whetstone themselves.
Software engineers have been conditioned to treat our environment as a fragile ecosystem owned by tech giants. We complain when an update moves a button, or when a feature we liked gets deprecated. But we have the literal raw material to forge our own reality sitting on our hard drives.
The Return to Craft
This shift is fundamentally about ownership.
For a long time, the software industry moved toward massive consolidation. We all used the same platforms and editors, relying on identical cloud providers. We standardized everything in the name of efficiency. And while that makes sense for enterprise production systems, it is a tragedy for the personal computing environment.
The original dream of the personal computer was that the machine became an extension of your specific mind. Educational languages from the 1960s and 70s, like Logo or early implementations of BASIC, were heavily focused on this idea. The goal was to give the user absolute agency over the machine.
By writing my own tools, I feel like I've reclaimed that agency. My operating system is a quiet, intensely personal workshop.
There are sharp edges, certainly. If someone else sat down at my keyboard, they wouldn't know how to open a file. The keybindings make sense only to my neuromuscular system. The error messages (when they exist) are usually sarcastic comments I wrote to myself at 2 AM.
But I don't care. The software wasn't built for them. It was built for me.
Taking the First Step
I am not suggesting you delete your entire IDE tomorrow and try to write a compiler in Assembly. That is a recipe for frustration.
But look at your daily workflow. Find the one tool that annoys you the most. The script that takes too long. The interface that requires three clicks when you wish it required zero.
Open a blank file. Spin up your favorite LLM. Tell it exactly what you want the tool to do, hardcoding every assumption you can possibly think of. Disregard best practices and scalability. Write rapid, personal code without worrying about how dirty it is.
Compile it. Bind it to a hotkey.