{
  "version": "https://jsonfeed.org/version/1",
  "title": "George Mandis",
  "description": "This is my blog where I write about web development and travel.",
  "home_page_url": "https://george.mand.is",
  "feed_url": "https://george.mand.is/feed.json",
  "favicon": "https://s3-us-west-2.amazonaws.com/george.mand.is/me-sketch-avatar.jpg",  
  "author": {
    "name": "George Mandis"
  },  
  "items": [
    {
      "id": "https://george.mand.is/2026/05/meet-patui-ms-paint-for-the-terminal-with-vim-controls/",
      "url": "https://george.mand.is/2026/05/meet-patui-ms-paint-for-the-terminal-with-vim-controls/",
      "title": "Meet PaTUI: MS Paint for the Terminal, with Vim Controls",
      "content_html": "<p>Every once in a while, a revolutionary product comes along that changeseverything. One is very fortunate if they get to work on even <em>one</em> of these intheir career.</p><p>Today I'm proud to introduce three revolutionary products of this caliber. Thefirst is a state-of-the-art image editor channeling the elegance of MS Paint.The second is an unparalleled user-interface experience continuing thelong-standing, intuitive traditions of Vim. And the third is software leveragingthe blazingly performant, brilliantly designed programming language that isJavaScript.</p><p>So that's three things: an image editor in the class of MS Paint. Anunimaginably intuitive user-interface paradigm rivaling Vim. A software productfinally realizing the raw horsepower and sensible typing decisions ofJavaScript.</p><p><em>MS Paint. Vim. JavaScript.</em></p><p>Are you getting it?</p><p><img src=\"https://georgemandis.s3-us-west-1.amazonaws.com/patui/patui-screenshot-me.png\" alt=\"The PaTUI screen with an image of the author's face loaded\"></p><p>These are not three separate products. This is <em>one</em> product, and we're callingit <strong>PaTUI</strong>.</p><p><img src=\"https://georgemandis.s3-us-west-1.amazonaws.com/patui/patui-splash.png\" alt=\"The PaTUI screen with the word PaTUI drawn in a pixel-art-meets-Comic-Sans style logo\"></p><h2>Welcome to PaTUI</h2><p>Homage to<a href=\"https://www.youtube.com/watch?v=5J-47F8Hrdw\">one of the most iconic product launches of this century</a>aside, what is PaTUI?</p><p>PaTUI is a terminal-based image editor. Load an image (PNG or JPEG, locally orvia URL), and it renders as colored block characters in your terminal. Thenpaint on it in <s>visual</s> &quot;paint&quot; mode, erase, fill, type text or apply retrofilters. When your masterpiece is complete you can export your work as a JPEG,PNG or ANSI art. In all exports, WYSIWYG.</p><p>No, we don't dare impose the dogma of higher-fidelities on your artistic vision.What are we, Photoshop? <em>Please</em>.</p><p>This isn't <s>an Arby's</s> Photoshop. This is <em>PaTUI</em>.</p><h2>&quot;Zoom, Enhance&quot;</h2><p>Each pixel in the image is represented by block characters wrapped with ANSIescape codes to render colors. We're using &quot;True&quot; color with RGB escape codes(e.g. <code>echo -e &quot;\\e[48;2;255;0;255m \\e[0m&quot;</code>) so the vibrance of your originalimage always shines through.</p><p>And, because the original image really is kept around in memory, you can use the&quot;zoom and enhance&quot; feature to zero in on as much or little detail as you need.</p><p><img src=\"https://georgemandis.s3-us-west-1.amazonaws.com/patui/patui-zoom.png\" alt=\"The PaTUI screen with the zoom feature engaged on the loaded image\"></p><h2>What pixels want: Vim-based controls</h2><p>It has Vim-style modal controls (<code>i</code> for paint mode, <code>hjkl</code> to move, <code>dd</code> todelete a &quot;row&quot; of pixels, <code>yy</code> to yank, <code>u</code> to undo), a 16-color palette you canselect with <code>!@#$%^&amp;*()</code>, an extended CSS-compatible color palette you canaccess with <code>:set color &lt;cornflowerblue|salmon|rebeccapurple,etc&gt;</code>, and commandslike <code>:w mona-lisa.png</code> and <code>:wq</code>.</p><p>Now you can draw shapes and touch up pixels with the most intuitive controlsever invented in computing: arbitrary keys with a generous amount of <code>Shift</code>thrown in.</p><p>You can always use <code>:help</code> for a more exhaustive list.</p><p><img src=\"https://georgemandis.s3-us-west-1.amazonaws.com/patui/patui-help-screen.png\" alt=\"The PaTUI help screen shown when you type \"></p><p><strong>Vim motions on pixels.</strong> <code>5j</code> moves down 5 rows. <code>dd</code> clears a row. <code>yy</code> yanksit, <code>p</code> pastes. <code>W</code> jumps to the next color boundary. <code>dG</code> deletes from cursorto bottom. If you know Vim, you already know how to navigate. If you don't knowVim, well, you're going to learn!</p><p><strong>Retro palette filters.</strong> <code>:palette gameboy</code> limits your image to the originalGame Boy's four shades of green. <code>:palette cga</code> gives you the CGA palette.<code>:dither</code> applies Floyd-Steinberg error diffusion dithering. Combine them:<code>:palette gameboy</code> then <code>:dither</code> and suddenly your photo looks like it belongson a 1989 handheld.</p><p><strong>Find-and-replace for colors.</strong> <code>:%s/blue/red/g</code> replaces all blue pixels withred. <code>:%s/~blue/red/g</code> does a fuzzy match -- anything in the blue family. TheVim regex muscle memory just... works here.</p><p><strong>Text rasterization.</strong> Press <code>t</code> to enter text mode and type charactersdirectly onto the image in the current foreground color. Font size scales withbrush size. It's exactly as janky and charming as it sounds.</p><p><strong>Export to ANSI art.</strong> <code>:w painting.ans</code> exports your work as ANSI escapecodes. <code>:wc</code> copies the ANSI art to your clipboard. Paste it into a terminal andit renders in color. Paste it into Slack and confuse your coworkers.</p><h2>Wh...Why? Why the terminal? Why any of this?</h2><p>There's something satisfying about creative tools that work in environmentsdesigned for text. The terminal gives you a grid of cells, each of which candisplay a colored block character. That's your canvas. Each cell is a pixel. Theconstraints are the point.</p><p>It's also just funny. The idea of bringing Vim motions to pixel art, of typing<code>:wq</code> to save a painting, of having a tool sidebar in a terminal -- it's absurdin a way that makes me happy to work on it.</p><h2>How it works</h2><p>PaTUI is a <a href=\"https://bun.sh\">Bun</a> app built with<a href=\"https://github.com/vadimdemedes/ink\">Ink</a>, which is React for terminal UIs.Image loading and manipulation use <a href=\"https://sharp.pixelplumbing.com/\">sharp</a>.State management is <a href=\"https://github.com/pmndrs/zustand\">zustand</a>.</p><p>The rendering pipeline: load an image with sharp, downscale it to fit theterminal viewport (accounting for the 2:1 aspect ratio of terminal characters),map each pixel to a 256-color ANSI escape code, and render it as a grid of <code>▀</code>(upper half block) characters. Each character encodes two vertical pixels usingforeground and background colors.</p><p>Edits modify the source image buffer. Undo/redo is a stack of image snapshots.Filters (grayscale, palette limiting, dithering) are applied at render time andincluded in exports.</p><h2>How do you pronounce PaTUI?</h2><p>It sounds like &quot;patooey,&quot; because that's what your images will look like.</p><h2>Try It</h2><pre><code class=\"language-bash\"># Homebrew (macOS / Linux)brew install georgemandis/tap/patui# Or from sourcebun install &amp;&amp; bun src/index.tsx mona.png</code></pre><p>Is it practical? Absolutely not. Is it fun? Easily the most fun 5 minutes you'llprocrastinate with today.</p><p>Built during my time at the <a href=\"https://www.recurse.com/\">Recurse Center</a> on alark. View the source and open a PR if you think you can help improve it.</p><ul><li><a href=\"https://github.com/georgemandis/patui\">github.com/georgemandis/patui</a></li></ul>",
      "date_published": "2026-05-06T00:00:00.000Z"
    },
    {
      "id": "https://george.mand.is/2026/04/soft-launching-little-irons/",
      "url": "https://george.mand.is/2026/04/soft-launching-little-irons/",
      "title": "Soft-launching Little Irons",
      "content_html": "<p>I built a tool to help people keep track of their job applications. In an instance of domain-name-driven application development, the project is called Little Irons (<a href=\"https://littleirons.com\">littleirons.com</a>)—a tool for helping you keep track of &quot;all your little irons in the fire.&quot;</p><p><img src=\"https://littleirons.com/icons-logo-nobg.png?__frsh_c=11e1bff9746d5f6719ccb0dfa3b55f4c7767244f\" alt=\"The logo for Little Irons—a job-search tool for tracking all your 'little irons' in the fire.\"></p><p>It's free, it's early, and I would love feedback: <a href=\"https://littleirons.com\">https://littleirons.com</a></p><p>The inspiration was an ancient Google Sheet I'd used forever as part of my own job + opportunity explorations. I wanted to see if I could run with this as the base and add nicer features on top inspired by software I enjoy (like Linear) and some thoughtful affordances and &quot;AI flourishes&quot; sprinkled on top, including:</p><ul><li>A browser extension to save job descriptions from any site for opportunities you are exploring.</li><li>An AI-powered parser that can extract all the important info (title, company, pay, location, job description, etc.) from the posting itself—all you have to do is provide the URL.</li><li>Company research with citations, salary data relative to role + industry, and personalized interview preparation based on the role, company, and your experience (if you've uploaded a resume or CV).</li><li>An ICS calendar feed you can subscribe to in any software (Google, Apple, Microsoft) that shows upcoming steps for any opportunities you've created events for.</li><li>An email assistant you can forward emails to—whether it's opportunities you're interested in or followups for jobs you've applied to. It will smartly know whether to add it as a new job you're &quot;exploring,&quot; update the status of an opportunity in motion—even a tragic rejection—or proactively &quot;schedule&quot; a new event in the sequence, like a final call with the hiring manager or an on-site.</li></ul><p>The heart of the tool is a kanban view of all the job applications you have in motion, with the columns aligning to an overall &quot;status&quot; for that particular opportunity. The furthest left column labeled &quot;Exploring&quot; is a place to put any jobs you're interested in applying to. The &quot;Applied&quot; column to the right is, hopefully, self-explanatory. Any columns further to the right are for when you move forward with the process.</p><p><img src=\"https://s3.us-west-2.amazonaws.com/george.mand.is/little-irons/kanban-board.png\" alt=\"Little Irons kanban board showing job applications organized by pipeline stage\"></p><p>In my experience there is often enough a distinction between the initial screening and the rest of the interview process that it's worth ratifying these as proper top-level statuses. The interview process itself though can take many forms and even multiple phases, from one-on-one conversations with stakeholders to panel-style interviews. This is where the individual &quot;events&quot; associated with an opportunity come into play.</p><p>Click any job to open its detail panel without leaving the board. You get the full picture: status, salary, location, relevant skill tags, and collapsible sections for upcoming events, contacts at the company, the full job description, your notes, and attached documents. The &quot;Next Actions&quot; section surfaces what you need to do next so nothing falls through the cracks.</p><p><img src=\"https://s3.us-west-2.amazonaws.com/george.mand.is/little-irons/job-detail-panel.png\" alt=\"Little Irons job detail side panel showing a Systems Administrator position at City Tech Services\"></p><p>Adding or editing a job happens through a single modal—fill in the basics and expand the Contacts and Events sections to track who you're talking to and what's scheduled. The Job Description tab stores the full posting text for easy reference later.</p><p><img src=\"https://s3.us-west-2.amazonaws.com/george.mand.is/little-irons/edit-job-modal.png\" alt=\"Little Irons edit job modal with fields for title, URL, salary, status, events, and contacts\"></p><p>Beyond the board, there are a few other ways to look at your search. The Calendar gives you a monthly overview of your activity—color-coded dots mark application dates, follow-ups, and scheduled events like interviews. Click any date to see what happened that day. You can also generate an iCalendar feed to sync everything into Google Calendar, Outlook, or Apple Calendar.</p><p><img src=\"https://s3.us-west-2.amazonaws.com/george.mand.is/little-irons/calendar-view.png\" alt=\"Little Irons calendar view for April 2026 showing application dates and follow-ups\"></p><p>The Stats page turns your job search into numbers. Top-line metrics show total jobs tracked, applications sent, response rate, and offer rate. A pipeline funnel breaks down how many jobs sit at each stage, and an outcomes section tallies your offers, rejections, withdrawals, and ghostings. The activity chart tracks your momentum over the last 30 days—helpful for keeping yourself accountable during a long search.</p><p><img src=\"https://s3.us-west-2.amazonaws.com/george.mand.is/little-irons/stats-dashboard.png\" alt=\"Little Irons stats dashboard showing pipeline metrics, response rates, outcomes, and activity chart\"></p><p>The Timeline presents your job search as a chronological story—every status change appears as an entry on an alternating feed with color-coded dots. It's a useful way to look back and see how active you've been or spot patterns in your pipeline.</p><p><img src=\"https://s3.us-west-2.amazonaws.com/george.mand.is/little-irons/timeline-view.png\" alt=\"Little Irons timeline view showing a chronological feed of job search events\"></p><p>And for people who prefer spreadsheets, the List view shows everything in a sortable table. Click any column header to sort, filter by status, or select multiple jobs for bulk actions.</p><p><img src=\"https://s3.us-west-2.amazonaws.com/george.mand.is/little-irons/list-view.png\" alt=\"Little Irons list view showing a sortable table of all tracked jobs with a status filter dropdown\"></p><p>The UI is admittedly a bit rough around the edges—something I plan on improving as I get more users to test with. Press Cmd/Ctrl+K to open the Linear-inspired command palette to quickly search jobs, jump between views, change statuses, or create new entries without reaching for the mouse.</p><p><img src=\"https://s3.us-west-2.amazonaws.com/george.mand.is/little-irons/command-palette.png\" alt=\"Little Irons command palette showing quick actions for searching jobs, navigating views, and filtering\"></p><p>To be clear: this is not intended to be an automated bulk-application tool. It's a simple, focused place to try and organize a real job search.</p><p>The site is completely free—all features, including the AI ones—and all you need to join is a GitHub account. If you find it useful and want to support ongoing development, I have a <a href=\"https://github.com/sponsors/georgemandis\">GitHub Sponsors page</a>.</p><p>Give it a shot and let me know what you think!</p>",
      "date_published": "2026-04-15T00:00:00.000Z"
    },
    {
      "id": "https://george.mand.is/2025/09/more-dynamic-cronjobs/",
      "url": "https://george.mand.is/2025/09/more-dynamic-cronjobs/",
      "title": "More dynamic cronjobs",
      "content_html": "<p>I remember learning about cronjobs in the early 2000s. I could tell the computer to go <em>do</em> something, on a recurring basis, forever, even when I wasn't there. They felt like magic!</p><p>We didn't have <a href=\"https://crontab.guru\">Crontab.guru</a> or AI to ask for figuring out some of the more complex specifications. Just the <a href=\"https://man.openbsd.org/crontab.5\">man pages</a> and good old-fashioned trial and error—mostly error in my case.</p><p>But while you could do fun, complex specifications of recurring intervals, you couldn't quite specify something quite as dynamic as &quot;run this script every Tuesday at 7am <em>unless it's the last Tuesday</em> of the month...&quot;</p><p>Or at least, you couldn't strictly through the crontab specification syntax. But I had a recent, mildly embarrassing epiphany that it's not hard at all to add arbitrary checks to your crontab to account for more complex and dynamic scenarios.</p><p>Want to run a script every Tuesday of the month at 7am <em>except</em> for the last Tuesday? That's easy—set up your crontab to run every Tuesday at 7am, but add a little check to make sure the <em>next</em> week is still part of the same month:</p><pre><code class=\"language-shell\">0 7 * * Tue [ &quot;$(date -v+7d '+%m')&quot; = &quot;$(date '+%m')&quot; ] &amp;&amp; /path/to/your_command</code></pre><p>If it's not part of the same month, that means we're on the <em>last</em> Tuesday for the month and the script won't run.</p><p><strong>Note:</strong> <em>The <code>-v</code> flag is for the macOS/BSD flavors of <code>date</code>. On Linux you'd want to use <code>-d +7 days</code> instead.</em></p><p>This really has nothing to do with cronjobs at all and everything to do with the <a href=\"https://www.unix.com/man_page/posix/1p/test/\">POSIX &quot;test&quot; command</a> which is the thing we're using with those square brackets. I'm used to seeing and utilizing them in shell scripts, but for whatever reason I never thought to reach for that tool here in the crontab.</p><p>You could just as easily rewrite it like this, skipping the bracket shorthand, which is probably easier to read:</p><pre><code class=\"language-shell\">0 7 * * Tue test &quot;$(date -v+7d '+%m')&quot; = &quot;$(date '+%m')&quot; &amp;&amp; /path/to/your_command</code></pre><p>It never crossed my mind until recently to add slightly more complex checks at the crontab level.</p><h3>Other clever cronjob things you can do:</h3><h4>Holiday-only cronjobs</h4><p>Maybe fetch a list of all the US Holidays for a given year and store them in a handy <code>HOLIDAYS.txt</code> file somewhere:</p><pre><code class=\"language-shell\">curl -s https://date.nager.at/api/v3/PublicHolidays/2025/US | jq -r '.[].date' &gt; HOLIDAYS.txt</code></pre><p>Now you can update your cronjob to run every Tuesday at 7am <em>except</em> on Holidays:</p><pre><code class=\"language-shell\">0 7 * * Tue ! grep -qx &quot;$(date +%F)&quot; HOLIDAYS.txt &amp;&amp; /path/to/your_command</code></pre><p>Or inversely, maybe run a holiday-only script that checks once a day</p><pre><code class=\"language-shell\">@daily grep -qx &quot;$(date +%F)&quot; HOLIDAYS.txt &amp;&amp; /path/to/your_special_holiday_command</code></pre><h4>Only run on sunny days</h4><p>The <a href=\"https://weather.gov\">National Weather Service</a> makes all kinds of fun data available (if you can find it...). How about a script that runs every hour, but only when the weather is clear?</p><pre><code class=\"language-shell\">@hourly curl -s &quot;https://api.weather.gov/gridpoints/TOP/32,81/forecast/hourly&quot; | jq -r '.properties.periods[0].shortForecast' | grep -qi clear &amp;&amp; /path/to/your_command</code></pre><p>Or maybe when the weather is cloudy?</p><pre><code class=\"language-shell\">@hourly curl -s &quot;https://api.weather.gov/gridpoints/TOP/32,81/forecast/hourly&quot; | jq -r '.properties.periods[0].shortForecast' | grep -qi cloudy &amp;&amp; /path/to/your_command</code></pre><h4>Only run when there's something newsworthy</h4><p>Or maybe we get in line with every-other-startup I'm aware of and throw AI at the problem, only running our script when the LLM gods have decided there is something newsworthy:</p><pre><code class=\"language-shell\">@hourly curl -s &quot;https://news.google.com/rss?hl=en-US&amp;gl=US&amp;ceid=US:en&quot; | llm --system &quot;Reply strictly 'yes' or 'no'. Does anything in the news today suggest it is a good reason to run a script that I only want to send when the world is on fire and crazy and terrible things are happening?&quot;  | tr -d '[:space:]' | tr '[:upper:]' '[:lower:]' | grep -qx yes &amp;&amp; /path/to/oh_no</code></pre>",
      "date_published": "2025-09-21T00:00:00.000Z"
    },
    {
      "id": "https://george.mand.is/2025/07/building-a-go-link-toy-with-deno/",
      "url": "https://george.mand.is/2025/07/building-a-go-link-toy-with-deno/",
      "title": "Building a Go Link Toy with Deno",
      "content_html": "<p><img src=\"https://georgemandis.s3-us-west-1.amazonaws.com/go-links-window.png\" alt=\"Screenshot of my Go Links Web Manager tool\"></p><p>For some reason I've been thinking about <a href=\"https://meta.wikimedia.org/wiki/Go_links\">go links</a>, which are really just a fancy word for link shorteners. They're useful at larger companies for providing static, easy-to-remember URLs that can take employees directly to pages for benefits explanations, requesting time-off, internal documentation and all sorts of things.</p><p>Some companies go the extra mile and make these links available on aesthetically pleasing URLs that only work when you're plugged into the corporate network. Something like:</p><ul><li><a href=\"http://go/payroll\">http://go/payroll</a></li><li><a href=\"http://go/holidays\">http://go/holidays</a></li><li><a href=\"http://go/metrics\">http://go/metrics</a></li><li><a href=\"http://go/evilmoonbaseplans\">http://go/evilmoonbaseplans</a></li></ul><p>If you work at a company utilizing go links, there's a chance these links might even work for you! If that last link works, you might want to consider finding a new job.</p><h2>Projects &gt; Solutions</h2><p>I wanted a simple personal go link system I could run on my machine, mostly for fun. I decided I wanted to make my own go link implementation just for me, local only to my network and perhaps even just my particular machine.</p><p>You might be thinking <em>&quot;Why not just bookmark the links in your browser?&quot;</em> to which I would say <em>&quot;Hey, I'm looking for a Sunday afternoon project, not a solution!&quot;</em></p><p>Other requirements:</p><ul><li><strong><code>go/whatever</code></strong> URLs, because they feel fancy</li><li>A cute little web-interface I can navigate to for managing the URLs, shortcodes</li><li>A CLI-driven component to the tool so I could manage and even access the links from the shell by invoking <code>golinks [shortcode]</code></li><li>Some basic stat tracking every time I use one of these, just because</li></ul><p>If you want to cut to the chase and play with the toy I built, check out the repo:</p><ul><li><a href=\"https://github.com/georgemandis/golinks\">github.com/georgemandis/golinks</a></li></ul><p>I also used this as an opportunity to continue exploring <a href=\"https://deno.com\">Deno</a>. I've been intrigued with it ever since it came on the scene, and I've found it particularly nice for building little one-off CLI toys.</p><p>Another fun aspect is you can run the following and install my <code>golinks</code> tool globally:</p><pre><code class=\"language-bash\">deno install --global --allow-read --allow-write --allow-env --allow-net --allow-run jsr:@georgemandis/golinks</code></pre><p>I <a href=\"https://jsr.io/@georgemandis/golinks\">published the package</a> on JSR just to get a feel for what's going on over there. Might save that for a different blog post.</p><h2>Fancy URLs</h2><p>Half the fun is having those fancy TLD-less URLs. I elaborate a little bit in the README for the repo, but it boils down to adding additional aliases for the loopback address (i.e. <code>localhost</code>) on your machine.</p><p>To do that in Unix and MacOS environments all you have to do is add a row to your <code>/etc/hosts</code> file:</p><pre><code>echo &quot;127.0.0.1 go&quot; | sudo tee -a /etc/hosts</code></pre><p>Note, <code>tee</code> command is just a nice way to write our input to a file and stdout at the same time. It’s not a command I use often, so I thought I’d explain it.</p><h2>Running the Server</h2><p>For me, I'm happy to just run it in the background and forget about it:</p><pre><code>golinks --server &amp;</code></pre><p>I always have my <a href=\"https://ghostty.org\">terminal</a> open, so this very lazy solution is Good Enough™ for me. The <code>&amp;</code> runs it as a background process and if I want to stop it I can <code>ps -e | grep &quot;golink&quot;</code> it, find the PID and <code>kill -9</code> it.</p><p>But a few more fun options are available if you want to be less lazy:</p><ul><li><strong>Set it up as a proper service at launch</strong>. I actually went down this route, implemented it and decided I <em>didn't</em> want to commit. Maybe it's irrational, but I like keeping my at-launch processes as clean and minimal as possible.</li><li><strong>Run it on a second computer on my network.</strong> This is actually my favorite approach, conceptually, and combined with <a href=\"https://tailscale.com\">Tailscale</a> it's surprisingly powerful. I have my &quot;under the bed&quot; computer that handles some home automation and other projects. I can run <code>golinks</code> on this machine and map the Tailscale IP address to <code>http://go</code> in my <code>/etc/hosts</code> file. Neat!</li></ul><h2>Future Considerations</h2><p>Like any good project, this one has plenty of rabbit holes to fall into. Here are some other ways I could continue to invest in the project:</p><ul><li>I'm really bothered by the &quot;insecure&quot; nature of these links. Getting SSL certificates working for local URLs was a pain last I checked, and <em>many</em> years ago I had a script to automate this. I'm pretty sure no longer works.</li><li>When in doubt, add AI. I actually added the &quot;description&quot; field with this somewhat in mind. It wouldn't take very much to open up the list of shortened links to an LLM and start querying over it.</li></ul><p>I don't foresee myself investing more time in this unless I really find it useful or people start contributing to/forking/using it, but it was a fun Sunday afternoon project.</p>",
      "date_published": "2025-07-13T00:00:00.000Z"
    },
    {
      "id": "https://george.mand.is/2025/06/openai-charges-by-the-minute-so-make-the-minutes-shorter/",
      "url": "https://george.mand.is/2025/06/openai-charges-by-the-minute-so-make-the-minutes-shorter/",
      "title": "OpenAI Charges by the Minute, So Make the Minutes Shorter",
      "content_html": "<p>Want to make OpenAI transcriptions faster and cheaper? Just speed up your audio.</p><p>I mean that very literally. Run your audio through <a href=\"https://gist.github.com/georgemandis/4fd62bf5027b7a058f913d5dc32c2040\">ffmpeg</a> at 2x or 3x before transcribing it. You’ll spend fewer tokens and less time waiting with almost no drop in transcription quality.</p><p>That’s it!</p><p>Here’s a script combining of all my favorite little toys and tricks to get the job. You’ll need <a href=\"https://github.com/yt-dlp/yt-dlp\">yt-dlp</a>, <a href=\"https://ffmpeg.org\">ffmpeg</a> and <a href=\"https://github.com/simonw/llm\">llm</a> installed.</p><pre><code class=\"language-bash\"># Extract the audio from the videoyt-dlp -f 'bestaudio[ext=m4a]' --extract-audio --audio-format m4a -o 'video-audio.m4a' &quot;https://www.youtube.com/watch?v=LCEmiRjPEtQ&quot; -k;# Create a low-bitrate MP3 version at 3x speedffmpeg -i &quot;video-audio.m4a&quot; -filter:a &quot;atempo=3.0&quot; -ac 1 -b:a 64k video-audio-3x.mp3;# Send it along to OpenAI for a transcriptioncurl --request POST \\  --url https://api.openai.com/v1/audio/transcriptions \\  --header &quot;Authorization: Bearer $OPENAI_API_KEY&quot; \\  --header 'Content-Type: multipart/form-data' \\  --form file=@video-audio-3x.mp3 \\  --form model=gpt-4o-transcribe &gt; video-transcript.txt;# Get a nice little summarycat video-transcript.txt | llm --system &quot;Summarize the main points of this talk.&quot;</code></pre><p>I just saved you time by jumping straight to the point, but read-on if you want more of a story about how I accidentally discovered this while trying to summarize a 40-minute talk from Andrej Karpathy.</p><p>Also read-on if you’re wondering why I didn’t just use the built-in auto-transcription that YouTube provides, though the short answer there is easy: I’m sort of a doofus and thought—incorrectly—it wasn’t available. So I did things the hard way.</p><h3>I Just Wanted the TL;DW(atch)</h3><p>A former colleague of mine sent me <a href=\"https://www.youtube.com/watch?v=LCEmiRjPEtQ\">this talk</a> from Andrej Karpathy about how AI is changing software. I wasn’t familiar with Andrej, but saw he’d worked at Tesla. That coupled with the talk being part of a Y Combinator series and 40 minutes made me think “Ugh. Do I… really want to watch this? Another 'AI is changing everything' talk from the usual suspects, to the usual crowds?”</p><p>If ever there were a use-case for dumping something into an LLM to get the gist of it and walk away, this felt like it. I respected the person who sent it to me though and wanted to do the noble thing: use AI to summarize the thing for me, blindly trust it and engage with the person pretending I had watched it.</p><p>My first instinct was to pipe the transcript into an LLM and get the gist of it. <a href=\"https://gist.github.com/simonw/9932c6f10e241cfa6b19a4e08b283ca9\">This script</a> is the one I would previously reach for to pull the auto-generated transcripts from YouTube:</p><pre><code class=\"language-bash\">yt-dlp --all-subs --skip-download \\  --sub-format ttml/vtt/best \\  [url]</code></pre><p>For some reason though, no subtitles were downloaded. I kept running into an error!</p><p>Later, after some head-scratching and rereading <a href=\"https://github.com/yt-dlp/yt-dlp?tab=readme-ov-file#subtitle-options\">the documentation</a>, I realized my version (2025.04.03) was outdated.</p><p><strong>Long story short</strong>: Updating to the latest version (2025.06.09) fixed it, but for some reason I did not try this <em>before</em> going down a totally different rabbit hole. I guess I got this little write-up and exploration out of it though.</p><p>If you care more about summarizing transcripts and less about the vagaries of audio-transcriptions and tokens, this is the correct answer and your off-ramp.</p><h3>My Transcription Workflow</h3><p>I already had an old, home-brewed script that would extract the audio from any video URL, pipe it through <a href=\"https://github.com/openai/whisper\">whisper</a> locally and dump the transcription in a text file.</p><p>That worked, but I was on dwindling battery power in a coffee shop. Not ideal for longer, local inference, mighty as my M3 MacBook Air still feels to me. I figured I would try offloading it to <a href=\"https://platform.openai.com/docs/guides/speech-to-text\">OpenAI’s API</a> instead. Surely that would be faster?</p><h3>Testing OpenAI’s Transcription Tools</h3><p>Okay, using the <code>whisper-1</code> model it’s <em>still</em> pretty slow, but it gets the job done. Had I opted for the model I knew and moved on, the story might end here.</p><p>However, out of curiosity, I went straight for the newer <code>gpt-4o-transcribe</code> model first. It’s built to handle multimodal inputs and promises faster responses.</p><p>I quickly hit another roadblock: there’s a 25-minute audio limit and my audio was nearly 40 minutes long.</p><h3>Let's Try Something Obvious</h3><p>At first I thought about trimming the audio to fit somehow, but there wasn’t an obvious 14 minutes to cut. Trimming the beginning and end would give me a minute or so at most.</p><p>An interesting, weird idea I thought about for a second but never tried was cutting a chunk or two out of the middle. Maybe I would somehow still have enough info for a relevant summary?</p><p>Then it crossed my mind—<strong>what if I just sped up the audio before sending it over?</strong> People listen to podcasts at accelerated 1-2x speeds all the time.</p><p>So I wrote a <a href=\"https://gist.github.com/georgemandis/4fd62bf5027b7a058f913d5dc32c2040\">quick script</a>:</p><pre><code class=\"language-bash\">ffmpeg -i video-audio.m4a -filter:a &quot;atempo=2.0&quot; -ac 1 -b:a 64k video-audio-2x.mp3</code></pre><p>Ta-da! Now I had something closer to a 20 minute file to send to OpenAI.</p><p>I uploaded it and… it worked like a charm! <a href=\"https://gist.github.com/georgemandis/b2a68b345262b94782fa6b08e41fbcf2\">Behold the summary</a> bestowed upon me that gave me enough confidence to reply to my colleague as though I had watched it.</p><p>But there was something... interesting here. Did I just stumble across a sort of obvious, straightforward hack? Is everyone in the audio-transcription business already doing this and am I just haphazardly bumbling into their secrets?</p><p>I had to dig deeper.</p><h3>Why This Works: Our Brains Forgive, and So Does AI</h3><p>There’s an interesting parallel here in my mind with optimizing images. Traditionally you have lossy and lossless file formats. A lossy file-format kind of gives away the game in its description—the further you crunch and compact the bytes the more fidelity you’re going to lose. It works because the human brain just isn’t likely to pick-up on the artifacts and imperfection</p><p>But even with a “lossless” file format there are tricks you can lean into that rely on the limits of human perception. One of the primary ways you can do that with a PNG or GIF is reducing the number of unique colors in the palette. You’d be surprised by how often a palette of 64 colors or fewer might actually be enough and perceived as significantly more.</p><p>There’s also a parallel in my head between this and the brain’s ability to still comprehend text with spelling mistakes, dropped words and other errors, i.e. <a href=\"https://en.wikipedia.org/wiki/Transposed_letter_effect\">transposed letter effects</a>. Our brains have a knack for filling in the gaps, and when you go looking through the world with magnifying glass you'll start to notice lots of them.</p><p>Speeding up the audio starts to drop the more subtle sounds and occasionally shorter words from the audio, but it doesn’t seem to hurt my ability to <em>comprehend</em> what I’m hearing—even if I do have to focus. These audio transcription models seem to be pretty good at this as well.</p><h3>Wait—how far can I push this? Does It Actually Save Money?</h3><p>Turns out yes. OpenAI <a href=\"https://platform.openai.com/docs/pricing\">charges for transcription</a> based on audio tokens, which scale with the duration of the input. Faster audio = fewer seconds = fewer tokens.</p><p>Here are some rounded numbers based on the 40-minute audio file breaking down the audio input and text output token costs:</p><table><thead><tr><th>Speed</th><th>Duration (seconds)</th><th>Audio Input Tokens</th><th>Input Token Cost</th><th>Output Token Cost</th></tr></thead><tbody><tr><td>1x (original)</td><td>2,372</td><td>NA (too long)</td><td>NA</td><td>NA</td></tr><tr><td>2x</td><td>1,186</td><td>11,856</td><td>$0.07</td><td>$0.02</td></tr><tr><td>3x</td><td>791</td><td>7,904</td><td>$0.04</td><td>$0.02</td></tr></tbody></table><p>That’s a solid 33% price reduction on input tokens at 3x! However the bulk of your costs for these transcription models are still going to be the output tokens. Those are priced at $10 per 1M tokens whereas audio input tokens are priced at $6 per 1M token as of the time of this writing.</p><p>Also interesting to note—my output tokens for the 2x and 3x versions were exactly the same: 2,048. This kind of makes sense, I think? To the extent the output tokens are a reflection of that model's ability to understand and summarize the input, my takeaway is a “summarized” (i.e. reduced-token) version of the same audio yields the same amount of comprehensibility.</p><p>This is also probably a reflection of the 4,096 token ceiling on transcriptions generally when using the <code>gpt-4o-transcription</code> model. I suspect half the context window is reserved for the output tokens and this is basically reflecting our request using it up in its entirety. I suspect we might get diminishing results with longer transcriptions.</p><p>But back to money.</p><p>So the back-of-the-envelope calculator for a single transcription looks something like this:</p><pre><code class=\"language-text\">6 * (audio_input_tokens / 1_000_000) + 10 * (text_output_tokens / 1_000_000);</code></pre><p>That does <em>not</em> quite seem to jibe with the estimated cost of $0.006 per minute stated on the pricing page, at least for the 2x speed. That version (19-20 minutes) seemed to cost about $0.09 whereas the 3x version (13 minutes) cost about $0.07 (pretty accurate actually), if I’m adding up the tokens correctly.</p><pre><code class=\"language-text\"># Pricing for 2x speed6 * (11_856 / 1_000_000) + 10 * (2_048 / 1_000_000) = 0.09# Pricing for 3x speed6 * (7_904 / 1_000_000) + 10 * (2_048 / 1_000_000) = 0.07</code></pre><p>It would seem that estimate isn’t just based on the length of the audio but also some assumptions around how many tokens per minute are going to be generated from a normal speaking cadence.</p><p>That’s… kind of fascinating! I wonder how <a href=\"https://en.wikipedia.org/wiki/John_Moschitta_Jr.\">John Moschitta’s</a> feels about this.</p><p>Comparing these costs to <code>whisper-1</code> is easy because the pricing table more confidently advertises the cost—not “estimated” cost—as a flat $0.006 per minute. I’m assuming that’s minute of audio processed, not minute of inference.</p><p>The <code>gpt-4o-transcription</code> model actually compares pretty favorably.</p><table><thead><tr><th>Speed</th><th>Duration</th><th>Cost</th></tr></thead><tbody><tr><td>1x</td><td>2372</td><td>$0.24</td></tr><tr><td>2x</td><td>1186 seconds</td><td>$0.12</td></tr><tr><td>3x</td><td>791 seconds</td><td>$0.08</td></tr></tbody></table><h3>Does This Save Money?</h3><p>In short, yes! It’s not particularly rigorous, but it seems like we reduced the cost of transcribing our 40-minute audio file by 23% from $0.09 to $0.07 simply by speeding up the audio.</p><p>If we could compare to a 1x version of the audio file trimmed to the 25-minute limit, I bet we could paint an even more impressive picture of cost reduction. We kind of can with the <code>whisper-1</code> chart. You could make the case this technique reduced costs by 67%!</p><h3>Is It Accurate?</h3><p>I don’t know—I didn’t watch it, lol. That was the whole point. And if that answer makes you uncomfortable, buckle-up for this future we're hurtling toward. Boy, howdy.</p><p>More helpfully, I didn’t compare word-for-word, but spot checks on the 2x and 3x versions looked solid. 4x speed was too fast—the transcription started getting hilariously weird. So, 2x and 3x seem to be the sweet spot between efficiency and fidelity, though it will obviously depend on how fast the people are speaking in the first place.</p><h3>Why Not 4x?</h3><p>When I pushed it to 4x the results became <a href=\"https://gist.github.com/georgemandis/1ec4ef084789f92ee06ac6283338a194\">comically unusable</a>.</p><p><img src=\"https://georgemandis.s3-us-west-1.amazonaws.com/4x-speed-transcription-min.png\" alt=\"Output of a 4x transcription mostly repeating &quot;And how do we talk about that?&quot; over and over again\"></p><p>That sure didn't stop my call to summarize from <a href=\"https://gist.github.com/georgemandis/1ec4ef084789f92ee06ac6283338a194#file-summarization-md\">trying</a> though.</p><p>Hey, not the worst talk I've been to!</p><h3>In Summary</h3><p>Always, in short, to save time and money, consider doubling or tripling the speed of the audio you want to transcribe. The trade-off is, as always, fidelity, but it’s not an insignificant savings.</p><p>Simple, fast, and surprisingly effective.</p><h3>TL;DR</h3><ul><li>OpenAI charges for transcriptions based on audio duration (<code>whisper-1</code>) or tokens (<code>gpt-4o-transcribe</code>).</li><li>You can <strong>speed up audio</strong> with <code>ffmpeg</code> before uploading to save time and money.</li><li>This reduces audio tokens (or duration), lowering your bill.</li><li><strong>2x or 3x speed</strong> works well.</li><li><strong>4x speed</strong>? Probably too much—but fun to try.</li></ul><p>If you find problems with my math, have questions, found a more rigorous study qualitatively comparing different output speeds please <a href=\"https://george.mand.is/contact\">get in touch</a>! Or if you thought this was so cool you want to <a href=\"https://george.mand.is/hire\">hire me</a> for something fun...</p><style>table:not(.hljs-ln) {    border:1px rgba(0,0,0, 0.5) solid;    min-width: 100%;    background:rgba(255,255,255,0.9);    color:#000;    font-family: system-ui;    font-size:0.7rem;}table:not(.hljs-ln) thead tr {    background: rgba(0,0,0, 0.05);}</style>",
      "date_published": "2025-06-24T00:00:00.000Z"
    },
    {
      "id": "https://george.mand.is/2025/06/what-it-takes-to-be-a-good-engineering-manager/",
      "url": "https://george.mand.is/2025/06/what-it-takes-to-be-a-good-engineering-manager/",
      "title": "What It Takes to Be a Good Engineering Manager",
      "content_html": "<p>There is no shortage of opinions on this topic. The most honest answer is probably “it depends” but that’s also the least helpful.</p><p>After 4.5 years leading multiple engineering teams, here’s my single-sentence answer to what makes a great manager:</p><blockquote><p>A good manager balances high empathy with high expectations and knows when to pull which lever.</p></blockquote><p>High expectations without empathy might “succeed” but at the cost of toxicity. It’s not my definition of success or <a href=\"https://george.mand.is/2021/10/facebook-recruiter-correspondence/\">something I want to be part of</a>.</p><p>High empathy without expectations can leave a team adrift.  It comes at the cost of growth, learning and output.</p><p>In interviewing for new engineering leadership positions I’ve found this line lands very well when trying to describe my own engineering leadership philosophies and values. I like to think this is because I really <em>mean</em> it. This isn’t a pithy mantra I picked up in an engineering management book—it’s a core tenet I’ve seen, felt, and lived.</p><p>I've been in situations where I could see I was pushing people too hard and needed to back-off without making them feel like they've failed. There have also been situations where I knew people could do more and found ways to gracefully challenge them. It's all part of navigating the tricky calculus of people, projects and priorities.</p><p>What you’re really calibrating for is the right amount of tension. It looks different for each individual, and each person's tension has to be balanced with the team as a whole. Think of it like a musical instrument with strings. There’s an appropriate amount of tension to achieve the desired frequency for each string and there’s an overall amount that the instrument itself can withstand.</p><p>I’m a firm believer in the <a href=\"https://www.recurse.com/self-directives\">Recurse Center's self-directives</a>. They've been a significant part of my &quot;high expectations&quot; for people on the various teams I managed. They're a beautiful set of principles to build any community around.</p><p>This approach engenders a culture that tends to attract and keep curious, life-long learners. People who holistically care about their work and how it fits into the whole. On its best days it feels like a fun class everyone has elected to take.</p><p>There’s no substitute for giving a shit—about every person, the team, and the work we do together.</p><p>❧ If you care as much about people as outcomes and you’re looking for an engineering leader who’ll pull the right levers, <a href=\"/hire\">let’s chat</a>.</p>",
      "date_published": "2025-06-19T00:00:00.000Z"
    },
    {
      "id": "https://george.mand.is/2025/06/exploring-phonics-with-openai/",
      "url": "https://george.mand.is/2025/06/exploring-phonics-with-openai/",
      "title": "Exploring phonics with OpenAI",
      "content_html": "<p>As a child of the 80s and 90s, I have the vaguest memory of <a href=\"https://www.youtube.com/watch?v=rfuGoVgCkJ8\">Hooked on Phonics</a> commercials on television. More specifically their catchphrase &quot;hooked on phonics worked for me!&quot; Reconciling this message in the <a href=\"https://www.youtube.com/watch?v=DQEG8j0lgeE\">D.A.R.E</a> era felt like mixed-messaging. Do you or <em>don't</em> you want me hooked on things?</p><p>Today I was reminded of this musical sign-off—for Hooked on Phonics, not D.A.R.E—after an introductory conversation with an ed-tech organization for an engineering leadership position. They described the product and platform and how it teaches kids to read. &quot;Oh, I'm old enough to remember phonics being a thing that was pushed in schools,&quot; I said.</p><p>It turns out old ideas never stray far.</p><p>An interesting thing that came out of that conversation was noting that the text-to-speech models don't do a great job with the phonetic sounds.</p><p>This makes some sense to me. I imagine the way the models are built, they're going to do a better job of producing entire words. Phonemes—distinct units of sound that make up a word—probably don't map well to tokens, even if tokens themselves can map to parts of words.</p><p>This isn't a researched take—it's just my intuition at the moment.</p><p><a href=\"https://platform.openai.com/tokenizer\"><img src=\"https://georgemandis.s3-us-west-1.amazonaws.com/phonics-tokens.jpg\" alt=\"Screenshot showing how word to token mapping via OpenAI's tokenizer tool\"></a></p><p>I'll have to dig deeper if I stay curious, but if the TTS models are using tokens in a way similar to how LLMs do, I could see how they would map more cleanly to words and partial words than the distinct sounds that necessarily comprise them.</p><p>After the call I immediately thought of a way to leverage AI shenanigans to try and prototype a phoneme-to-audio pipeline using OpenAI’s APIs:</p><ul><li>Identify distinct phonemes within a word, mapping them to <a href=\"https://en.wikipedia.org/wiki/International_Phonetic_Alphabet\">International Phonetic Alphabet</a> (IPA) symbols</li><li>Provide them back as a list via <a href=\"https://platform.openai.com/docs/guides/structured-outputs?api-mode=responses#introduction\">structured outputs</a></li><li>Iterate through the list and feed those individual phonemes back into <a href=\"https://platform.openai.com/docs/guides/audio\">OpenAI's TTS offering</a> to generate the sounds</li><li>Stitch it all together in a cute little web interface and see how it does</li></ul><p>I hacked together a quick demo to test this out. Behold, my mediocre exploration of phonics via OpenAI:</p><iframe width=\"100%\" height=\"315\" src=\"https://www.youtube.com/embed/VaMN-YTJL-M?si=H1FEjGN2q-DZlRYw\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen></iframe><p>You can view the source code for my phoneme explorer here on GitHub:</p><ul><li><a href=\"https://github.com/georgemandis/openai-phoneme-exploration\">https://github.com/georgemandis/openai-phoneme-exploration</a></li></ul><p>The actual structured output of phonemes, to my extremely untrained eye, didn't seem that bad! It seemed to map to the way words were presented in various Wikipedia articles as I compared them. That's an extremely unrigorous test, but it surpassed my expectations.</p><p>The audio however <em>met</em> my expectations, which is to say it was fairly... mediocre. It handled my name with aplomb, which seemed promising at first. A relatively simple but multi-syllabic word like &quot;continental&quot; was a borderline disaster.</p><p>Still, it’s a fun proof of concept—and enough to make me curious about what better phoneme-focused datasets or synthesis models might look like.</p><p>If I want to keep exploring this idea I think the next step might be to look for a more canonical reference set of audio samples out there that map to the IPA. Cursory searching didn't lend itself to an obvious source, but I’d be surprised if something like that isn’t out there already.</p>",
      "date_published": "2025-06-12T00:00:00.000Z"
    },
    {
      "id": "https://george.mand.is/2025/06/future-proofing-my-blog-for-an-ai-audience/",
      "url": "https://george.mand.is/2025/06/future-proofing-my-blog-for-an-ai-audience/",
      "title": "Future-proofing my blog for an AI audience",
      "content_html": "<p>There is nothing more meta than blogging about your blog, but I had to start somewhere after a six-month hiatus.</p><p>Let's cut to the chase. I've been thinking a lot about LLMs and <a href=\"https://llmstxt.org\">llmstxt.org</a> in particular. Although LLMs are pretty darn good at reading HTML and parsing meaning between the brackets, it still seems to do best when the source is as close to plain-text as possible.</p><p>So I made a few changes to facilitate this and make the plain-text versions more discoverable:</p><ul><li>Every blog post has a plain-text and Markdown variant. Just add <code>.md</code> or <code>.txt</code> to the end of the post URL to see it.</li><li>I've added <code>&lt;link rel=&quot;alternate&quot; type=&quot;text/plain&quot;&gt;</code> and <code>&lt;link rel=&quot;alternate&quot; type=&quot;text/markdown&quot;&gt;</code> to the <code>&lt;head&gt;</code> of the HTML for these posts to make it more discoverable.</li><li>I also added links to the end of each post, for the kind of nerds that appreciate that sort of thing, as well as dedicated RSS (<a href=\"/feed.plain.xml\">plain</a>|<a href=\"/feed.markdown.json\">markdown</a>) and JSON (<a href=\"/feed.plain.json\">plain</a>|<a href=\"/feed.markdown.json\">markdown</a>) feeds that will link directly to these versions if you want</li></ul><p>It all makes sense to me. What's good for us tends to be good for the machines: legible systems, up-to-date documentation, well organized projects, etc. I have a whole separate article in the back of my brain about trying to understand why an industry is so eager to treating machines better than we treat ourselves. These same tools and affordances make people more productive too, but for some reason investing time in them for the benefit of people—not machines—always seems to be a harder sell.</p><p>My favorite little trick was this Nunjuck loop to match the length of the title for a given post with enough &quot;=&quot; characters to visually underline it:</p><pre><code>{{ post.data.title }}{% for i in range(0, post.data.title | length) -%}={% endfor %}</code></pre><p>So the end result for the plain-text version looks like this:</p><pre><code class=\"language-markdown\">Future-proofing my blog for an AI audience==========================================</code></pre><p>Silly, little things that make me happy.</p><p>You might be asking why is there plain-text version <em>and</em> a Markdown version when the plain-text version looks like... Markdown.</p><p>That... is a great question. I kind of improvised this evening and will probably go back to the drawing board on this concept. The Markdown files as they are currently served are literally how my posts are stored in the repo. The plain-text versions are formatted a little bit differently (i.e no frontmatter).</p><p>On a different note, my professional obligations have recently changed and I have some free-time on my hands. I want to write about that and reflect on all the things I've learned, but mostly I want to use this time to rebuild the discipline of writing more. Technology, travel stories, creative explorations, opinions and even metacommentary about how I've constructed this blog.</p>",
      "date_published": "2025-06-11T00:00:00.000Z"
    },
    {
      "id": "https://george.mand.is/2025/06/agi-elevators-and-other-thoughts-i-didnt-mean-to-publish/",
      "url": "https://george.mand.is/2025/06/agi-elevators-and-other-thoughts-i-didnt-mean-to-publish/",
      "title": "AGI, elevators, and other thoughts I didn’t mean to publish",
      "content_html": "<p><em>Note</em>: I accidentally published this before it was ready! I decided to tweak the typos, remove a couple terrible fragments and otherwise leave it as-is.</p><p>Ideas have always been cheap; it's the <a href=\"https://sive.rs/multiply\">execution that's the multiplier</a>. In the AI world we're hurtling toward, which is driving down the cost of execution, I guess that means ideas are more devalued than ever?</p><p>Cheaper still than an idea is an <em>observation</em>. It's the Xerox of an idea run through a filter—artifacts and fidelity sometimes lost, but perhaps a bit of unique character introduced.</p><p>Everyone seems to have their own AI thoughts these days. Fresh off 4.5 years leading engineering efforts around a SaaS product through the birth of this LLM era we're all living in now, I thought I'd offer a scattershot collection of my own half-baked ones:</p><h2>Abstractions on Abstractions</h2><p>Computing is the history of abstractions. We've gone from punching holes in cards to perform calculations to talking to pocket devices in our mother tongue, asking what the weather will be—all in less than a century.</p><p>The advent of computer languages to create instructions led to software as we know it. From peeking and poking at individual memory addresses to writing scripts that imitate language to (now, with LLMs) <em>literally</em> using language as we're hard-wired to learn it from birth—conversing with machines.</p><p>What happens when the next &quot;hello world&quot; program is just: &quot;ask the computer to write a hello world program&quot;? Sure, behind the scenes it may literally write an entire operating system from scratch in some virtualized container, all in service of simply spitting out the words <em>Hello World</em>—but it doesn't matter.</p><p>I think there's an inevitable discomfort living through this kind of progress. You see what's lost. You see the depths of understanding falling short of your own. But there's progress on the other side of that that will outweigh anything lost. Feelings that end up bordering on nostalgia cannot slow this train.</p><p>And that's okay.</p><h2>Probabilistic Elevators</h2><p>At some point the interface to everything becomes a machine you talk to. In the same way microcontrollers have found their way into all manner of things. They're <a href=\"https://en.wikipedia.org/wiki/Fast,_Cheap_%26_Out_of_Control\">fast, cheap, and out of control</a>. Wait until we have highly capable LLMs on chips. In that world, why create a unique electronic component for a button when you can manufacture a magical component that you kindly <em>ask</em> to behave like a button? Shrunken to microscopic sizes, how close does this start to resemble magical nanotechnologies capable of anything?</p><p>Imagine a grid of elevator buttons, floors 1–10. Each programmed with a simple prompt: &quot;Send the elevator to floor X.&quot;</p><p>Now, if you're thinking about the probabilistic nature of elevators, you might be thinking: &quot;That is insane. I would like a deterministic elevator, please.&quot;</p><p>But the truth is: if it's cheaper and easier, it will win.</p><p>And the other truth is: we're already living in a probabilistic world. This future is simply embracing it.</p><p>Also, <em>Probabilistic Elevators</em> would be a great band name. Please steal it.</p><h2>AGI</h2><p>The psychology of AGI, SGI, and trusting our robot overlords fascinates me. Science fiction has always been aspirational, but you have to remember that it's ultimately <em>fiction</em>. Real life always lands differently.</p><p>You achieve AGI when you're willing to trust the machine blindly. I think that's a product of comfort over time more than capability. At the end of the day, it's a marketing term for a highly capable version of this tool that hasn't quite landed yet. But I can squint and see it. We're talking about tools, not machines with will. We always have been.</p><p>It says more about us when we decide to feel comfortable making that leap than about the capabilities of the thing. I have a personal feeling that AGI is also only perceivable to the extent you've given the brain-in-a-jar tools to act on its scattered thoughts. I can't really conceive or understand an intelligence in the abstract, independent of tools that help me recognize that level of cognizance—writing, speaking, actions.</p><p>There’s a whole era of study dedicated to pretty much the opposite of my intuition that goes back to the 1950s called <a href=\"https://en.wikipedia.org/wiki/Symbolic_artificial_intelligence\">symbolic AI</a>. I trust those people are much smarter than me—that my intuition is missing the point and that there’s something there—but I do suspect my ability to relate to this kind of intelligence is diminished absent relatable tools. A brain-in-a-jar absent the tool of language to talk to me is just a brain-in-a-jar, no matter what's going on in there. It feels more like philosophy than engineering something in the real world. At least today.</p><p>It’s like anthropomorphizing Excel or a bash script, to some extent. Or maybe asking Deep Blue or Watson to operate a forklift.</p><p>Anyway, debating AGI strikes me as a distraction. A fun distraction, but not a particularly useful one beyond feeding the marketing hype. Discussing the inevitable capabilities of these tools—and the agency we might feel comfortable granting them—is not.</p><p>AGI happens when you're ready.</p>",
      "date_published": "2025-06-07T00:00:00.000Z"
    },
    {
      "id": "https://george.mand.is/2024/12/kagi-orion-and-gopher/",
      "url": "https://george.mand.is/2024/12/kagi-orion-and-gopher/",
      "title": "Kagi, Orion and Gopher",
      "content_html": "<p>The last week of the year is a time for rest and reflection. During that downtime, I decided to try <a href=\"https://kagi.com\">Kagi</a> and <a href=\"https://kagi.com/orion/\">Orion</a> and spent more time than I should have setting up a <a href=\"https://en.wikipedia.org/wiki/Gopher_(protocol)\">Gopher</a> site, just for kicks.</p><h2>Kagi &amp; Orion</h2><p>Kagi is a paid search engine and Orion is a privacy-minded browser they built on <a href=\"https://en.wikipedia.org/wiki/WebKit\">Webkit</a>—not <a href=\"https://en.wikipedia.org/wiki/Blink_(browser_engine)\">Blink</a>!—exclusively for macOS, at least for now.</p><p>Everything about those choices sounds like someone actively whittling down their market share to something so small and specific most VCs would scoff. But somehow, they seem to have cobbled together a surprisingly robust business?</p><p>I listened to Vlad Prelovac (the founder) talk about the history of the company, their design choices and a lot of other things on The Talk Show (the companion podcast to Daring Fireball):</p><ul><li><a href=\"https://daringfireball.net/thetalkshow/2024/12/24/ep-416\">‘A PROFESSIONAL INTERNET USER’, WITH VLAD PRELOVAC</a></li></ul><p>There's something charming and inspiring about this product's success. So, for the first time in well over a decade, I decided to open my mind to the idea of ditching Google and Chrome and trying something new.</p><p>The old adage of &quot;If you're not paying for the product then the product is you&quot; was a great quip in the early 2000s when I first heard it, and almost everything about the way the internet has evolved since then reinforces it for me. It's refreshing to try to consciously take a step out of that world and see what else is out there.</p><p>It's only been a week, but so far... I might really like it?  I love the clever twists like how adding a question mark at the end of your search query invokes their AI assistant, which is kind of like how Google's AI suggestions appear above the search results. There are no ads, of course.</p><p>There are thoughtful filters you can toggle if you want to move from searching the entirety of &quot;the web&quot; to just segments of their index—a mixture of topics like Programming or Recipes or sources like PDF or Fediverse Forums. It's thoughtful and clever, and their endearingly branded &quot;Small Web&quot; portal gives me nostalgic <a href=\"https://en.wikipedia.org/wiki/StumbleUpon\">StumbleUpon</a> vibes.</p><p>I'm actually somewhat more enamored with Orion than Kagi at the moment. It's fast, the memory footprint is low, all the built-in anti-tracking and blocking features are baked in. They've put a lot of time into supporting any and all Chrome Extensions as well as they can, which is pretty wild.</p><p>All-in-all, similar to Kagi, there are just lots of little thoughtful touches sprinkled all over the app. It's fine as a &quot;set it and forget it&quot; kind of browser, but also has lots of power-user features that are easy to find. For example, I switched my user agent to Safari iPhone to trick the wifi portal on the plane to think it's my phone so I could take advantage of the free wifi my T-Mobile plan gets me. Can you do that on other browsers? Yeah, but even with Chrome and Safari I have to go through at least one more click to make it happen. This was evident and elegant out of the box.</p><h2>Gopher</h2><p>Apropos of nothing, I decided to setup a Gopher site:</p><ul><li><a href=\"gopher://build.less.software\">gopher://build.less.software</a></li></ul><p>If you don't have a browser that knows what to do with that link (Orion will not help you here) I'd recommend <a href=\"https://github.com/skyjake/lagrange\">Lagrange</a>, <a href=\"https://github.com/jankammerath/gophie\">Gophie</a> or <a href=\"https://bombadillo.colorfield.space\">Bombadillo</a> if you're more of a TUI person.</p><p>The saga of <em>how</em> I setup that site I will leave for a future post.</p><p>Happy New Year to anyone reading! May the good things be more and the harder things easier for you all in 2025.</p>",
      "date_published": "2024-12-31T00:00:00.000Z"
    }
  ]
}