Lead Software Engineer at Amplience
I've always loved the idea of websites that respond differently to curl requests versus browser visits. You know, those sites where running curl example.com gives you a beautiful ASCII art terminal output instead of just HTML. It's a nice touch that shows attention to detail and respect for terminal users.
So I decided to add this to my own site. Here's how I built a system that detects curl requests and serves up a colorful terminal-style business card.
When someone runs curl https://zdrenka.com, instead of getting raw HTML, they should see:
The key is detecting when a request comes from curl versus a browser. Different hosting platforms handle this differently:
{
"rewrites": [
{
"source": "/",
"destination": "/curl.txt",
"has": [
{
"type": "header",
"key": "user-agent",
"value": "(?i).*(curl|wget|httpie|lynx|links|elinks).*"
}
]
},
{
"source": "/",
"destination": "/index.html"
}
]
}/ /curl.txt 200 User-Agent: *curl*
/ /curl.txt 200 User-Agent: *wget*
/ /curl.txt 200 User-Agent: *httpie*
/ /curl.txt 200 User-Agent: *lynx*
# Default to index.html for browsers
/ /index.html 200RewriteEngine On
RewriteCond %{HTTP_USER_AGENT} ^(curl|wget|httpie|lynx|links|elinks).*$ [NC]
RewriteRule ^/?$ /curl.txt [L]
RewriteCond %{HTTP_USER_AGENT} !^(curl|wget|httpie|lynx|links|elinks).*$ [NC]
RewriteRule ^/?$ /index.html [L]The logic is simple: if the User-Agent header contains "curl", "wget", "httpie", or similar tools, serve the plain text version. Otherwise, serve the normal HTML.
As a last resort, the build also generates a curl-detector.html — a JavaScript fallback that sniffs navigator.userAgent client-side and redirects accordingly, for cases where server-side routing isn't available.
I created a Node.js script (lib/generateCurlPage.js) that generates the curl-friendly output with ANSI colors:
// ANSI color codes matching website theme
const colors = {
reset: '\x1b[0m',
green: '\x1b[32m',
brightGreen: '\x1b[92m',
red: '\x1b[31m',
brightRed: '\x1b[91m', // ASCII art
cyan: '\x1b[36m',
brightCyan: '\x1b[96m', // Field labels
yellow: '\x1b[33m',
brightYellow: '\x1b[93m', // Highlighted fields
white: '\x1b[37m',
brightWhite: '\x1b[97m', // Main text
blue: '\x1b[34m', // URLs
brightBlue: '\x1b[94m',
gray: '\x1b[90m'
};The script combines ASCII art with info in a side-by-side layout, just like the screenfetch display on my homepage:
for (let i = 0; i < maxLines; i++) {
const artLine = i < asciiArt.length ? asciiArt[i] : " ".repeat(35);
const infoLine = i < info.length ? info[i] : "";
contentLines.push(`${artLine} ${infoLine}`);
}One thing I wanted to avoid was having to update my role, company, or other info in multiple places. So all profile data lives in a single file — lib/profileData.js — which is shared between the curl generator and the browser homepage:
const profileData = {
roleStartDate: '2018-07-30',
industryStartDate: '2011-07-01',
name: "Karl Lankester-Carthy",
role: "Lead Software Engineer",
company: { name: "Amplience.com", url: "https://amplience.com" },
// ...
};The build uses this to generate both curl.txt and a profile-data.js file that the browser loads. Change one file, both outputs stay in sync.
Everything is wired into the build script, so every time I run npm run build, it generates curl.txt alongside the HTML:
const { generateCurlPage } = require('./lib/generateCurlPage');
// In build function
generateCurlPage(outputDir);The "Time In Role" and "Time In Industry" fields update automatically — no manual edits needed:
function calculateTimeDifference(startDate) {
const from = new Date(startDate);
const now = new Date();
let years = now.getFullYear() - from.getFullYear();
let months = now.getMonth() - from.getMonth();
let days = now.getDate() - from.getDate();
if (days < 0) {
months--;
const prevMonth = new Date(now.getFullYear(), now.getMonth(), 0);
days += prevMonth.getDate();
}
if (months < 0) {
years--;
months += 12;
}
return { years, months, days };
}And at the bottom of the curl output, it automatically appends a link to the latest blog post — pulled from the same posts the build already processes:
contentLines.push(`${colors.brightWhite}${profileData.getLatestBlogMessagePlain()}${colors.reset}`);So if I publish a new post, the curl output advertises it automatically.
Now when someone runs curl https://zdrenka.com, they get a colorful terminal output that looks like a proper business card:
Sure, 99% of visitors will use a browser and never see this. But for the 1% who curl my site (usually other developers), it shows attention to detail and respect for the terminal. Plus, it was a fun little project that taught me about User-Agent detection and ANSI color handling.
It's these small touches that make personal sites interesting. Anyone can throw up a boring HTML page, but adding personality and Easter eggs makes it memorable.
Try it yourself: curl https://zdrenka.com 🎨