<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://josh.fail/feed.xml" rel="self" type="application/atom+xml" /><link href="https://josh.fail/" rel="alternate" type="text/html" /><updated>2026-07-09T10:36:32-04:00</updated><id>https://josh.fail/feed.xml</id><title type="html">josh.fail</title><subtitle>Random thoughts about programming.</subtitle><author><name>Joshua Priddle</name><email>jpriddle@me.com</email></author><entry><title type="html">Command Line Interface Guidelines</title><link href="https://josh.fail/2026/command-line-interface-guidelines/" rel="alternate" type="text/html" title="Command Line Interface Guidelines" /><published>2026-07-09T10:24:03-04:00</published><updated>2026-07-09T10:24:03-04:00</updated><id>https://josh.fail/2026/command-line-interface-guidelines</id><content type="html" xml:base="https://josh.fail/2026/command-line-interface-guidelines/"><![CDATA[<p>Saw <a href="https://clig.dev">Command Line Interface Guidelines</a> today.</p>

<p>Need to give it a closer read. But, I’m glad to see someone working on actual
standards in this space. Could make for a good agent skill…</p>]]></content><author><name>Joshua Priddle</name><email>jpriddle@me.com</email></author><category term="dev" /><summary type="html"><![CDATA[Saw Command Line Interface Guidelines today.]]></summary></entry><entry><title type="html">ical-guy: a morning with Claude Code</title><link href="https://josh.fail/2026/ical-guy-a-morning-with-claude-code/" rel="alternate" type="text/html" title="ical-guy: a morning with Claude Code" /><published>2026-02-15T21:26:41-05:00</published><updated>2026-02-15T21:26:41-05:00</updated><id>https://josh.fail/2026/ical-guy-a-morning-with-claude-code</id><content type="html" xml:base="https://josh.fail/2026/ical-guy-a-morning-with-claude-code/"><![CDATA[<p>A few years ago I wanted to automate extracting my calendar events into
markdown files in my Obsidian vault. It turned into a project and a half, a
brittle pipeline of bash, Ruby, AppleScript, and a <a href="https://github.com/ali-rantakari/icalBuddy">fork</a> of <a href="https://hasseg.org/icalBuddy/">icalBuddy</a>
that was on its last legs. Here’s how a few hours on a Sunday morning with
Claude Code replaced it all with a modern EventKit-based Swift CLI.</p>

<p>Before we get into the new CLI, I want to sidebar into one of the biggest and
stupidest problems I had with all of this automation, and what ultimately gave
me the spark to make this new CLI last night—the week number.</p>

<p>In most of the world we use <a href="https://en.wikipedia.org/wiki/ISO_week_date">ISO week date</a>, which means that the first
week of the year is the one that contains the first Thursday of the year.
Calendar.app on the other hand does not use ISO 8601 <em>unless</em> the user changes
their calendar settings in System Preferences.</p>

<p>This means that standard scripting tools like <code class="language-plaintext highlighter-rouge">date +%V</code>, <code class="language-plaintext highlighter-rouge">date +%U</code> and <code class="language-plaintext highlighter-rouge">date
+%W</code> all return the wrong week number <em>sometimes</em>, depending on the day of
week the 1st of the year fell on.</p>

<p>This seemed like such an easy problem, and indeed once I had the proper math
explained, it is. But figuring it out eluded me. And surprisingly, no one had
really complained about this problem at all. I guess people either used date
libs that handled it and never gave it a 2nd thought, they accepted the wrong
week numbers and manually fixed them. Or more likely, they just didn’t care
about week numbers at all and I am a little extra for going that far in my
weekly planning.</p>

<p>Anyway, I was finally able to figure out how to make it work by using
AppleScript with NSCalendar. It worked, but it felt dirty. Whatever, I ran the
script once a week and it was fine.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env bash</span>
<span class="c"># Usage: ical-week [-n] [-N[COUNT] | -P[COUNT]] [YYYY-MM-DD]</span>
<span class="c">#</span>
<span class="c"># NAME</span>
<span class="c">#   ical-week -- print the week number according to Calendar.app</span>
<span class="c">#</span>
<span class="c"># SYNOPSIS</span>
<span class="c">#   ical-week [-n] [-N[COUNT] | -P[COUNT]] [YYYY-MM-DD]</span>
<span class="c">#</span>
<span class="c"># DESCRIPTION</span>
<span class="c">#   Prints the week number according to Calendar.app, which differs from the</span>
<span class="c">#   "%U", "%W", and "%V" formats of strftime(3).</span>
<span class="c">#</span>
<span class="c"># OPTIONS</span>
<span class="c">#   -n, --no-pad</span>
<span class="c">#     If set, no prepending 0 is used with single digit week numbers (i.e. Jan</span>
<span class="c">#     1st will return "1" instead of "01").</span>
<span class="c">#</span>
<span class="c">#   -L[COUNT], --last[=COUNT]</span>
<span class="c">#     If set, shows the previous week's number. Specify a COUNT to go back</span>
<span class="c">#     further.</span>
<span class="c">#</span>
<span class="c">#   -N[COUNT], --next[=COUNT]</span>
<span class="c">#     If set, shows the next week's number. Specify a COUNT to go forward</span>
<span class="c">#     further.</span>
<span class="c">#</span>
<span class="c">#   --start-date</span>
<span class="c">#     If set, shows the start date of the week.</span>
<span class="c">#</span>
<span class="c">#   --end-date</span>
<span class="c">#     If set, shows the end date of the week.</span>
<span class="c">#</span>
<span class="c"># SEE ALSO</span>
<span class="c">#   DATE(1), STRFTIME(3)</span>

<span class="c"># Call this script with DEBUG=1 to add some debugging output</span>
<span class="k">if</span> <span class="o">[[</span> <span class="s2">"</span><span class="nv">$DEBUG</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
  </span><span class="nb">export </span><span class="nv">PS4</span><span class="o">=</span><span class="s1">'+ [${BASH_SOURCE##*/}:${LINENO}] '</span>
  <span class="nb">set</span> <span class="nt">-x</span>
<span class="k">fi

</span><span class="nb">set</span> <span class="nt">-e</span>

<span class="c"># Echoes given args to STDERR</span>
<span class="c">#</span>
<span class="c"># $@ - args to pass to echo</span>
warn<span class="o">()</span> <span class="o">{</span>
  <span class="nb">echo</span> <span class="s2">"</span><span class="nv">$@</span><span class="s2">"</span> <span class="o">&gt;</span>&amp;2
<span class="o">}</span>

<span class="c"># Prints the Calendar.app week number</span>
<span class="c">#</span>
<span class="c"># $1 - date in %Y-%m-%d format</span>
<span class="c"># $2 - pad string to use with printf (i.e. "" or "02")</span>
<span class="c">#</span>
<span class="c"># Note: I know what you're thinking... "Hey, look at this clown, calculating</span>
<span class="c"># the week number by hand instead of reaching for `date` and '%U', '%W' or</span>
<span class="c"># '%V'. He probably didn't even RTFM!" Well, you're right, I didn't really</span>
<span class="c"># peruse `man date` much until I saw that Calendar.app's week number did not</span>
<span class="c"># match those from strftime(3). I promise, I wanted this entire monstrosity to</span>
<span class="c"># be a one-liner. But, alas, computers.</span>
<span class="c">#</span>
<span class="c"># Anyway, if you've read this far, gather 'round and let me tell you a tale of</span>
<span class="c"># week numbers on macOS.</span>
<span class="c">#</span>
<span class="c"># Calendar.app does not use ISO 8601 unless a user changes their calendar</span>
<span class="c"># setting in System Preferneces -&gt; Language &amp; Region to ISO 8601.</span>
<span class="c">#</span>
<span class="c"># As a result week numbers in Calendar.app do not match those obtained by</span>
<span class="c"># programming languages that use strftime(3) (i.e. `%V` and `%U` in `date`</span>
<span class="c"># or Ruby's `Time#strftime`):</span>
<span class="c">#</span>
<span class="c">#   - %U: is replaced by the week number of the year (Sunday as the first day</span>
<span class="c">#         of the week) as a decimal number (00-53).</span>
<span class="c">#</span>
<span class="c">#   - %V: is replaced by the week number of the year (Monday as the first day</span>
<span class="c">#         of the week) as a decimal number (01-53). If the week containing</span>
<span class="c">#         January 1 has four or more days in the new year, then it is week 1;</span>
<span class="c">#         otherwise it is the last week of the previous year, and the next</span>
<span class="c">#         week is week 1.</span>
<span class="c">#</span>
<span class="c">#   - %W: is replaced by the week number of the year (Monday as the first day</span>
<span class="c">#         of the week) as a decimal number (00-53).</span>
<span class="c">#</span>
<span class="c"># These options are not appropriate to obtain the week number used by</span>
<span class="c"># Calendar.app. Instead, January 1 is Week 1 *always*, which consequently</span>
<span class="c"># means Week 53 only occurs in Calendar.app if December 31 is a Sunday.</span>
<span class="c">#</span>
<span class="c"># The days that `date` considers start of week for its various week number</span>
<span class="c"># options also complicate things.</span>
<span class="c">#</span>
<span class="c">#   - %U: is close to what we want. Its week starts on Sunday like we want,</span>
<span class="c">#         but it uses Week 53 and Week 0. It is a good candidate for manually</span>
<span class="c">#         calculating the week number if we can work around Weeks 53 and 0.</span>
<span class="c">#   - %V: is really what macOS/Calendar.app should be using, but I don't want</span>
<span class="c">#         to change my time preference to make it work right. It is a bad</span>
<span class="c">#         candidate to use for our manual calculation as well since its week</span>
<span class="c">#         starts on Monday.</span>
<span class="c">#   - %W: is also close to what we want, like %U, but its week starts on a</span>
<span class="c">#         Monday.</span>
<span class="c">#</span>
<span class="c"># With all that in mind, we reach for NSDate which does what we want.</span>
<span class="c">#</span>
<span class="c"># See https://en.wikipedia.org/wiki/ISO_week_date#Calculating_the_week_number_from_a_month_and_day_of_the_month_or_ordinal_date</span>
week_number<span class="o">()</span> <span class="o">{</span>
  <span class="nb">local </span><span class="nv">now</span><span class="o">=</span><span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> <span class="nv">pad</span><span class="o">=</span><span class="s2">"</span><span class="nv">$2</span><span class="s2">"</span> <span class="nv">start_or_end</span><span class="o">=</span><span class="s2">"</span><span class="nv">$3</span><span class="s2">"</span> week

  <span class="c"># https://gist.github.com/RichardHyde/3386ac57b55455b71140</span>
  <span class="nv">week</span><span class="o">=</span><span class="si">$(</span> osascript <span class="nt">-e</span> <span class="s2">"
    use scripting additions
    use framework </span><span class="se">\"</span><span class="s2">Foundation</span><span class="se">\"</span><span class="s2">

    on getWeekFromDate(theDate)
      try
        set theASDate to theDate as date
        set theNSDate to current application's NSDate's dateWithTimeInterval:0 sinceDate:theASDate
        set theNSCalendar to current application's NSCalendar's currentCalendar()
        set theWeekInteger to (theNSCalendar's component:(current application's NSCalendarUnitWeekOfYear) fromDate:theNSDate) as integer
        return theWeekInteger
      on error
        return -1 as integer
      end try
    end getWeekFromDate

    -- Convert date function. Call with string in YYYY-MM-DD HH:MM:SS format (time part optional)
    on convertDate(textDate)
      set resultDate to the current date
      set the month of resultDate to (1 as integer)
      set the day of resultDate to (1 as integer)

      set the year of resultDate to (text 1 thru 4 of textDate)
      set the month of resultDate to (text 6 thru 7 of textDate)
      set the day of resultDate to (text 9 thru 10 of textDate)
      set the time of resultDate to 0

      return resultDate
    end convertDate

    on pad(v)
      return text -2 thru -1 of ((v + 100) as text)
    end pad

    -- get start/end day of week
    on getStartEndOfDayWeek(theTarget, theDate)
      set theWeekdays to {</span><span class="se">\"</span><span class="s2">Sunday</span><span class="se">\"</span><span class="s2">, </span><span class="se">\"</span><span class="s2">Monday</span><span class="se">\"</span><span class="s2">, </span><span class="se">\"</span><span class="s2">Tuesday</span><span class="se">\"</span><span class="s2">, </span><span class="se">\"</span><span class="s2">Wednesday</span><span class="se">\"</span><span class="s2">, </span><span class="se">\"</span><span class="s2">Thursday</span><span class="se">\"</span><span class="s2">, </span><span class="se">\"</span><span class="s2">Friday</span><span class="se">\"</span><span class="s2">, </span><span class="se">\"</span><span class="s2">Saturday</span><span class="se">\"</span><span class="s2">}
      set theWeekday to weekday of theDate as string

      repeat with i from 1 to (count theWeekdays)
        if item i of theWeekdays = theWeekday then exit repeat
      end repeat

      if theTarget = </span><span class="se">\"</span><span class="s2">start</span><span class="se">\"</span><span class="s2"> then
        set theResult to theDate - (i - 1) * days
      else
        set theResult to theDate + (7 - i) * days
      end if

      -- return YYYY-MM-DD format
      return (year of theResult as string) &amp; </span><span class="se">\"</span><span class="s2">-</span><span class="se">\"</span><span class="s2"> &amp; pad(month of theResult as integer) &amp; </span><span class="se">\"</span><span class="s2">-</span><span class="se">\"</span><span class="s2"> &amp; (day of theResult as string)
    end getStartEndOfDayWeek

    on run argv
      if (item 1 of argv) = </span><span class="se">\"</span><span class="s2">start</span><span class="se">\"</span><span class="s2">
        return getStartEndOfDayWeek(</span><span class="se">\"</span><span class="s2">start</span><span class="se">\"</span><span class="s2">, convertDate(item 2 of argv))
      else if (item 1 of argv) = </span><span class="se">\"</span><span class="s2">end</span><span class="se">\"</span><span class="s2">
        return getStartEndOfDayWeek(</span><span class="se">\"</span><span class="s2">end</span><span class="se">\"</span><span class="s2">, convertDate(item 2 of argv))
      else
        return getWeekFromDate(convertDate(item 2 of argv))
      end if
    end run
    "</span> <span class="s2">"</span><span class="nv">$start_or_end</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$now</span><span class="s2">"</span>
  <span class="si">)</span>

  <span class="k">if</span> <span class="o">[[</span> <span class="s2">"</span><span class="nv">$start_or_end</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
    </span><span class="nb">printf</span> <span class="s2">"%s"</span> <span class="s2">"</span><span class="nv">$week</span><span class="s2">"</span>
  <span class="k">else
    </span><span class="nb">printf</span> <span class="s2">"%</span><span class="k">${</span><span class="nv">pad</span><span class="k">}</span><span class="s2">d"</span> <span class="s2">"</span><span class="nv">$week</span><span class="s2">"</span>
  <span class="k">fi</span>
<span class="o">}</span>

<span class="c"># Print the help text for this program</span>
<span class="c">#</span>
<span class="c"># $1 - flag used to ask for help ("-h" or "--help")</span>
print_help<span class="o">()</span> <span class="o">{</span>
  <span class="nb">sed</span> <span class="nt">-ne</span> <span class="s1">'/^#/!q;s/^#$/# /;/^# /s/^# //p'</span> &lt; <span class="s2">"</span><span class="nv">$0</span><span class="s2">"</span> |
    <span class="nb">awk</span> <span class="nt">-v</span> <span class="nv">f</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">1</span><span class="p">#-h</span><span class="k">}</span><span class="s2">"</span> <span class="s1">'!f&amp;&amp;/^Usage:/||u{u=!/^\s*$/;if(!u)exit}u||f'</span>
<span class="o">}</span>

<span class="c"># Advance a date by weeks forward or backward</span>
<span class="c">#</span>
<span class="c"># $1 - date in %Y-%m-%d format</span>
<span class="c"># $2 - weeks to offset, including + or -</span>
advance_date<span class="o">()</span> <span class="o">{</span>
  <span class="nb">local </span><span class="nv">now</span><span class="o">=</span><span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> <span class="nv">offset</span><span class="o">=</span><span class="s2">"</span><span class="nv">$2</span><span class="s2">"</span>

  <span class="nb">date</span> <span class="nt">-v</span> <span class="s2">"</span><span class="nv">$offset</span><span class="s2">"</span> <span class="nt">-j</span> <span class="nt">-f</span> <span class="s2">"%F"</span> <span class="s2">"</span><span class="nv">$now</span><span class="s2">"</span> <span class="s2">"+%F"</span>
<span class="o">}</span>

main<span class="o">()</span> <span class="o">{</span>
  <span class="nb">local </span>target_date <span class="nv">pad</span><span class="o">=</span><span class="s2">"02"</span> advance print_date

  <span class="k">while</span> <span class="o">[[</span> <span class="nv">$# </span><span class="nt">-gt</span> 0 <span class="o">]]</span><span class="p">;</span> <span class="k">do
    case</span> <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> <span class="k">in</span>
      <span class="nt">-h</span> <span class="p">|</span> <span class="nt">--help</span><span class="p">)</span> print_help <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span><span class="p">;</span> <span class="k">return </span>0 <span class="p">;;</span>
      <span class="nt">-n</span> <span class="p">|</span> <span class="nt">--no-pad</span><span class="p">)</span> <span class="nv">pad</span><span class="o">=</span> <span class="p">;</span> <span class="nb">shift</span> <span class="p">;;</span>
      <span class="nt">-N</span> <span class="p">|</span> <span class="nt">--next</span><span class="p">)</span> <span class="nv">advance</span><span class="o">=</span><span class="s2">"+1"</span><span class="p">;</span> <span class="nb">shift</span> <span class="p">;;</span>
      <span class="nt">-N</span><span class="o">[</span>0-9]<span class="k">*</span><span class="p">)</span> <span class="nv">advance</span><span class="o">=</span><span class="s2">"+</span><span class="k">${</span><span class="nv">1</span>:2<span class="k">}</span><span class="s2">"</span><span class="p">;</span> <span class="nb">shift</span> <span class="p">;;</span>
      <span class="nt">--next</span><span class="o">=</span><span class="k">*</span><span class="p">)</span> <span class="nv">advance</span><span class="o">=</span><span class="s2">"+</span><span class="k">${</span><span class="nv">1</span><span class="p">#*=</span><span class="k">}</span><span class="s2">"</span><span class="p">;</span> <span class="nb">shift</span> <span class="p">;;</span>
      <span class="nt">-P</span> <span class="p">|</span> <span class="nt">--prev</span><span class="p">)</span> <span class="nv">advance</span><span class="o">=</span><span class="s2">"-1"</span><span class="p">;</span> <span class="nb">shift</span> <span class="p">;;</span>
      <span class="nt">-P</span><span class="o">[</span>0-9]<span class="k">*</span><span class="p">)</span> <span class="nv">advance</span><span class="o">=</span><span class="s2">"-</span><span class="k">${</span><span class="nv">1</span>:2<span class="k">}</span><span class="s2">"</span><span class="p">;</span> <span class="nb">shift</span> <span class="p">;;</span>
      <span class="nt">--prev</span><span class="o">=</span><span class="k">*</span><span class="p">)</span> <span class="nv">advance</span><span class="o">=</span><span class="s2">"-</span><span class="k">${</span><span class="nv">1</span><span class="p">#*=</span><span class="k">}</span><span class="s2">"</span><span class="p">;</span> <span class="nb">shift</span> <span class="p">;;</span>
      <span class="nt">--start-date</span><span class="p">)</span> <span class="nv">print_date</span><span class="o">=</span>start<span class="p">;</span> <span class="nb">shift</span> <span class="p">;;</span>
      <span class="nt">--end-date</span><span class="p">)</span> <span class="nv">print_date</span><span class="o">=</span>end<span class="p">;</span> <span class="nb">shift</span> <span class="p">;;</span>
      <span class="nt">--</span><span class="p">)</span> <span class="nb">shift</span><span class="p">;</span> <span class="nb">break</span> <span class="p">;;</span>
      <span class="k">*</span><span class="p">)</span> <span class="nb">break</span> <span class="p">;;</span>
    <span class="k">esac</span>
  <span class="k">done</span>

  : <span class="s2">"</span><span class="k">${</span><span class="nv">target_date</span>:<span class="p">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">1</span><span class="k">:-</span><span class="si">$(</span><span class="nb">date</span> <span class="s2">"+%F"</span><span class="si">)</span><span class="k">}</span><span class="s2">"</span><span class="k">}</span><span class="s2">"</span>

  <span class="k">if</span> <span class="o">!</span> <span class="o">[[</span> <span class="s2">"</span><span class="nv">$target_date</span><span class="s2">"</span> <span class="o">=</span>~ ^[0-9]<span class="o">{</span>4<span class="o">}</span>-[0-9]<span class="o">{</span>2<span class="o">}</span>-[0-9]<span class="o">{</span>2<span class="o">}</span><span class="nv">$ </span><span class="o">]]</span><span class="p">;</span> <span class="k">then
    </span>warn <span class="s2">"Invalid date! Use YYYY-MM-DD format!"</span>
    <span class="k">return </span>1
  <span class="k">fi

  if</span> <span class="o">[[</span> <span class="s2">"</span><span class="nv">$advance</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
    </span><span class="nv">target_date</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span>advance_date <span class="s2">"</span><span class="nv">$target_date</span><span class="s2">"</span> <span class="s2">"</span><span class="k">${</span><span class="nv">advance</span><span class="k">}</span><span class="s2">w"</span><span class="si">)</span><span class="s2">"</span>
  <span class="k">fi

  if </span>week_number <span class="s2">"</span><span class="nv">$target_date</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$pad</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$print_date</span><span class="s2">"</span><span class="p">;</span> <span class="k">then</span>
    <span class="c"># shellcheck disable=SC2015</span>
    <span class="o">[[</span> <span class="nt">-t</span> 1 <span class="o">]]</span> <span class="o">&amp;&amp;</span> <span class="nb">echo</span> <span class="o">||</span> <span class="nb">true
  </span><span class="k">fi</span>
<span class="o">}</span>

main <span class="s2">"</span><span class="nv">$@</span><span class="s2">"</span>
</code></pre></div></div>

<p>Fast forward a couple years, and AI coding is now a thing. As I was kicking the
tires around May 2025, I thought of this old script. Surely, AI can solve this
problem for me, right?</p>

<p>Well, no. I don’t have the code it tried, but it also landed on a broken
solution like I had. When we tried to add tests, they eventually failed. I
don’t actually use much of this automation anymore since my days are less
meeting-heavy. So I gave up, and accepted the fact maybe NSCalendar was the
only way.</p>

<p>Claude’s new Opus 4.6 model released in early February 2026, however, is a
game changer. For about 3 weeks now, every night, I make some random idea or
website I thought about a reality. And it solves way more complex problems now
than it did back in in May. So I thought, let’s give it another shot.</p>

<p>This time, Claude did deep research on the problem. In fact, it took about 45
minutes of research before it came up with a working solution, essentially
finding the underlying ICU library that NSCalendar uses and implementing the
same week number logic in Bash. I had Claude whip up a script to generate 150
years of test data from the old NSCalendar implementation. Then I went to bed
while it ran, happy to finally have this problem nailed.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env bash</span>
<span class="c"># Usage: ical-week [-n] [-N[COUNT] | -P[COUNT]] [YYYY-MM-DD]</span>
<span class="c">#</span>
<span class="c"># NAME</span>
<span class="c">#   ical-week -- print the week number according to Calendar.app</span>
<span class="c">#</span>
<span class="c"># SYNOPSIS</span>
<span class="c">#   ical-week [-n] [-N[COUNT] | -P[COUNT]] [YYYY-MM-DD]</span>
<span class="c">#</span>
<span class="c"># DESCRIPTION</span>
<span class="c">#   Prints the week number according to Calendar.app, which differs from the</span>
<span class="c">#   "%U", "%W", and "%V" formats of strftime(3).</span>
<span class="c">#</span>
<span class="c"># OPTIONS</span>
<span class="c">#   -n, --no-pad</span>
<span class="c">#     If set, no prepending 0 is used with single digit week numbers (i.e. Jan</span>
<span class="c">#     1st will return "1" instead of "01").</span>
<span class="c">#</span>
<span class="c">#   -L[COUNT], --last[=COUNT]</span>
<span class="c">#     If set, shows the previous week's number. Specify a COUNT to go back</span>
<span class="c">#     further.</span>
<span class="c">#</span>
<span class="c">#   -N[COUNT], --next[=COUNT]</span>
<span class="c">#     If set, shows the next week's number. Specify a COUNT to go forward</span>
<span class="c">#     further.</span>
<span class="c">#</span>
<span class="c">#   --start-date</span>
<span class="c">#     If set, shows the start date of the week.</span>
<span class="c">#</span>
<span class="c">#   --end-date</span>
<span class="c">#     If set, shows the end date of the week.</span>
<span class="c">#</span>
<span class="c"># SEE ALSO</span>
<span class="c">#   DATE(1), STRFTIME(3)</span>

<span class="c"># Call this script with DEBUG=1 to add some debugging output</span>
<span class="k">if</span> <span class="o">[[</span> <span class="s2">"</span><span class="nv">$DEBUG</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
  </span><span class="nb">export </span><span class="nv">PS4</span><span class="o">=</span><span class="s1">'+ [${BASH_SOURCE##*/}:${LINENO}] '</span>
  <span class="nb">set</span> <span class="nt">-x</span>
<span class="k">fi

</span><span class="nb">set</span> <span class="nt">-e</span>

<span class="c"># Echoes given args to STDERR</span>
<span class="c">#</span>
<span class="c"># $@ - args to pass to echo</span>
warn<span class="o">()</span> <span class="o">{</span>
  <span class="nb">echo</span> <span class="s2">"</span><span class="nv">$@</span><span class="s2">"</span> <span class="o">&gt;</span>&amp;2
<span class="o">}</span>

<span class="c"># Prints the Calendar.app week number</span>
<span class="c">#</span>
<span class="c"># $1 - date in %Y-%m-%d format</span>
<span class="c"># $2 - pad string to use with printf (i.e. "" or "02")</span>
<span class="c">#</span>
<span class="c"># Note: I know what you're thinking... "Hey, look at this clown, calculating</span>
<span class="c"># the week number by hand instead of reaching for `date` and '%U', '%W' or</span>
<span class="c"># '%V'. He probably didn't even RTFM!" Well, you're right, I didn't really</span>
<span class="c"># peruse `man date` much until I saw that Calendar.app's week number did not</span>
<span class="c"># match those from strftime(3). I promise, I wanted this entire monstrosity to</span>
<span class="c"># be a one-liner. But, alas, computers.</span>
<span class="c">#</span>
<span class="c"># Anyway, if you've read this far, gather 'round and let me tell you a tale of</span>
<span class="c"># week numbers on macOS.</span>
<span class="c">#</span>
<span class="c"># Calendar.app does not use ISO 8601 unless a user changes their calendar</span>
<span class="c"># setting in System Preferneces -&gt; Language &amp; Region to ISO 8601.</span>
<span class="c">#</span>
<span class="c"># As a result week numbers in Calendar.app do not match those obtained by</span>
<span class="c"># programming languages that use strftime(3) (i.e. `%V` and `%U` in `date`</span>
<span class="c"># or Ruby's `Time#strftime`):</span>
<span class="c">#</span>
<span class="c">#   - %U: is replaced by the week number of the year (Sunday as the first day</span>
<span class="c">#         of the week) as a decimal number (00-53).</span>
<span class="c">#</span>
<span class="c">#   - %V: is replaced by the week number of the year (Monday as the first day</span>
<span class="c">#         of the week) as a decimal number (01-53). If the week containing</span>
<span class="c">#         January 1 has four or more days in the new year, then it is week 1;</span>
<span class="c">#         otherwise it is the last week of the previous year, and the next</span>
<span class="c">#         week is week 1.</span>
<span class="c">#</span>
<span class="c">#   - %W: is replaced by the week number of the year (Monday as the first day</span>
<span class="c">#         of the week) as a decimal number (00-53).</span>
<span class="c">#</span>
<span class="c"># These options are not appropriate to obtain the week number used by</span>
<span class="c"># Calendar.app. Instead, January 1 is Week 1 *always*, which consequently</span>
<span class="c"># means Week 53 only occurs in Calendar.app if December 31 is a Saturday.</span>
<span class="c">#</span>
<span class="c"># The days that `date` considers start of week for its various week number</span>
<span class="c"># options also complicate things.</span>
<span class="c">#</span>
<span class="c">#   - %U: is close to what we want. Its week starts on Sunday like we want,</span>
<span class="c">#         but it uses Week 53 and Week 0. It is a good candidate for manually</span>
<span class="c">#         calculating the week number if we can work around Weeks 53 and 0.</span>
<span class="c">#   - %V: is really what macOS/Calendar.app should be using, but I don't want</span>
<span class="c">#         to change my time preference to make it work right. It is a bad</span>
<span class="c">#         candidate to use for our manual calculation as well since its week</span>
<span class="c">#         starts on Monday.</span>
<span class="c">#   - %W: is also close to what we want, like %U, but its week starts on a</span>
<span class="c">#         Monday.</span>
<span class="c">#</span>
<span class="c"># With all that in mind, we calculate the week number using the same</span>
<span class="c"># algorithm as NSCalendar/ICU: week = floor((dayOfYear - 1 + dow(Jan1)) / 7) + 1</span>
<span class="c"># with Sunday as the first day of the week and minimumDaysInFirstWeek = 1.</span>
<span class="c">#</span>
<span class="c"># See https://en.wikipedia.org/wiki/ISO_week_date</span>
week_number<span class="o">()</span> <span class="o">{</span>
  <span class="nb">local </span><span class="nv">now</span><span class="o">=</span><span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> <span class="nv">pad</span><span class="o">=</span><span class="s2">"</span><span class="nv">$2</span><span class="s2">"</span> <span class="nv">start_or_end</span><span class="o">=</span><span class="s2">"</span><span class="nv">$3</span><span class="s2">"</span>

  <span class="k">if</span> <span class="o">[[</span> <span class="s2">"</span><span class="nv">$start_or_end</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
    </span><span class="nb">local </span>dow
    <span class="nv">dow</span><span class="o">=</span><span class="si">$(</span><span class="nb">date</span> <span class="nt">-j</span> <span class="nt">-f</span> <span class="s2">"%F"</span> <span class="s2">"</span><span class="nv">$now</span><span class="s2">"</span> <span class="s2">"+%w"</span><span class="si">)</span>
    <span class="k">if</span> <span class="o">[[</span> <span class="s2">"</span><span class="nv">$start_or_end</span><span class="s2">"</span> <span class="o">==</span> <span class="s2">"start"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
      </span><span class="nb">printf</span> <span class="s2">"%s"</span> <span class="s2">"</span><span class="si">$(</span><span class="nb">date</span> <span class="nt">-j</span> <span class="nt">-v-</span><span class="k">${</span><span class="nv">dow</span><span class="k">}</span>d <span class="nt">-f</span> <span class="s2">"%F"</span> <span class="s2">"</span><span class="nv">$now</span><span class="s2">"</span> <span class="s2">"+%F"</span><span class="si">)</span><span class="s2">"</span>
    <span class="k">else
      </span><span class="nb">printf</span> <span class="s2">"%s"</span> <span class="s2">"</span><span class="si">$(</span><span class="nb">date</span> <span class="nt">-j</span> <span class="nt">-v</span>+<span class="k">$((</span><span class="m">6</span> <span class="o">-</span> dow<span class="k">))</span>d <span class="nt">-f</span> <span class="s2">"%F"</span> <span class="s2">"</span><span class="nv">$now</span><span class="s2">"</span> <span class="s2">"+%F"</span><span class="si">)</span><span class="s2">"</span>
    <span class="k">fi
    return
  fi

  </span><span class="nb">local </span><span class="nv">year</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">now</span>:0:4<span class="k">}</span><span class="s2">"</span>
  <span class="nb">local </span>day_of_year dow dow_jan1 days_in_year week

  <span class="nb">read</span> <span class="nt">-r</span> day_of_year dow &lt; &lt;<span class="o">(</span><span class="nb">date</span> <span class="nt">-j</span> <span class="nt">-f</span> <span class="s2">"%F"</span> <span class="s2">"</span><span class="nv">$now</span><span class="s2">"</span> <span class="s2">"+%j %w"</span><span class="o">)</span>
  <span class="nv">dow_jan1</span><span class="o">=</span><span class="si">$(</span><span class="nb">date</span> <span class="nt">-j</span> <span class="nt">-f</span> <span class="s2">"%F"</span> <span class="s2">"</span><span class="k">${</span><span class="nv">year</span><span class="k">}</span><span class="s2">-01-01"</span> <span class="s2">"+%w"</span><span class="si">)</span>

  <span class="k">if</span> <span class="o">((</span> year % 4 <span class="o">==</span> 0 <span class="o">&amp;&amp;</span> <span class="o">(</span>year % 100 <span class="o">!=</span> 0 <span class="o">||</span> year % 400 <span class="o">==</span> 0<span class="o">)</span> <span class="o">))</span><span class="p">;</span> <span class="k">then
    </span><span class="nv">days_in_year</span><span class="o">=</span>366
  <span class="k">else
    </span><span class="nv">days_in_year</span><span class="o">=</span>365
  <span class="k">fi</span>

  <span class="c"># If the date's week (Sun-Sat) extends into the next year,</span>
  <span class="c"># NSCalendar considers it week 1 of the next year</span>
  <span class="k">if</span> <span class="o">((</span> 10#<span class="nv">$day_of_year</span> + 6 - dow <span class="o">&gt;</span> days_in_year <span class="o">))</span><span class="p">;</span> <span class="k">then
    </span><span class="nv">week</span><span class="o">=</span>1
  <span class="k">else
    </span><span class="nv">week</span><span class="o">=</span><span class="k">$((</span> <span class="o">(</span><span class="m">10</span><span class="c">#$day_of_year - 1 + dow_jan1) / 7 + 1 ))</span>
  <span class="k">fi

  </span><span class="nb">printf</span> <span class="s2">"%</span><span class="k">${</span><span class="nv">pad</span><span class="k">}</span><span class="s2">d"</span> <span class="s2">"</span><span class="nv">$week</span><span class="s2">"</span>
<span class="o">}</span>

<span class="c"># Print the help text for this program</span>
<span class="c">#</span>
<span class="c"># $1 - flag used to ask for help ("-h" or "--help")</span>
print_help<span class="o">()</span> <span class="o">{</span>
  <span class="nb">sed</span> <span class="o">-</span>ne <span class="s1">'/^#/!q;s/^#$/# /;/^# /s/^# //p'</span> &lt; <span class="s2">"</span><span class="nv">$0</span><span class="s2">"</span> <span class="o">|</span>
    <span class="nb">awk</span> <span class="o">-</span>v <span class="nv">f</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">1</span><span class="p">#-h</span><span class="k">}</span><span class="s2">"</span> <span class="s1">'!f&amp;&amp;/^Usage:/||u{u=!/^\s*$/;if(!u)exit}u||f'</span>
<span class="o">}</span>

<span class="c"># Advance a date by weeks forward or backward</span>
<span class="c">#</span>
<span class="c"># $1 - date in %Y-%m-%d format</span>
<span class="c"># $2 - weeks to offset, including + or -</span>
advance_date<span class="o">()</span> <span class="o">{</span>
  <span class="nb">local </span><span class="nv">now</span><span class="o">=</span><span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> <span class="nv">offset</span><span class="o">=</span><span class="s2">"</span><span class="nv">$2</span><span class="s2">"</span>

  <span class="nb">date</span> <span class="o">-</span>v <span class="s2">"</span><span class="nv">$offset</span><span class="s2">"</span> <span class="o">-</span>j <span class="o">-</span>f <span class="s2">"%F"</span> <span class="s2">"</span><span class="nv">$now</span><span class="s2">"</span> <span class="s2">"+%F"</span>
<span class="o">}</span>

main<span class="o">()</span> <span class="o">{</span>
  <span class="nb">local </span>target_date <span class="nv">pad</span><span class="o">=</span><span class="s2">"02"</span> advance print_date

  <span class="k">while</span> <span class="o">[[</span> <span class="nv">$# </span><span class="o">-</span>gt <span class="m">0</span> <span class="o">]]</span><span class="p">;</span> <span class="k">do
    case</span> <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> <span class="k">in</span>
      <span class="nt">-h</span> <span class="p">|</span> <span class="nt">--help</span><span class="p">)</span> print_help <span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span><span class="p">;</span> <span class="k">return </span>0 <span class="p">;;</span>
      <span class="nt">-n</span> <span class="p">|</span> <span class="nt">--no-pad</span><span class="p">)</span> <span class="nv">pad</span><span class="o">=</span> <span class="p">;</span> <span class="nb">shift</span> <span class="p">;;</span>
      <span class="nt">-N</span> <span class="p">|</span> <span class="nt">--next</span><span class="p">)</span> <span class="nv">advance</span><span class="o">=</span><span class="s2">"+1"</span><span class="p">;</span> <span class="nb">shift</span> <span class="p">;;</span>
      <span class="nt">-N</span><span class="o">[</span>0-9]<span class="k">*</span><span class="p">)</span> <span class="nv">advance</span><span class="o">=</span><span class="s2">"+</span><span class="k">${</span><span class="nv">1</span>:2<span class="k">}</span><span class="s2">"</span><span class="p">;</span> <span class="nb">shift</span> <span class="p">;;</span>
      <span class="nt">--next</span><span class="o">=</span><span class="k">*</span><span class="p">)</span> <span class="nv">advance</span><span class="o">=</span><span class="s2">"+</span><span class="k">${</span><span class="nv">1</span><span class="p">#*=</span><span class="k">}</span><span class="s2">"</span><span class="p">;</span> <span class="nb">shift</span> <span class="p">;;</span>
      <span class="nt">-P</span> <span class="p">|</span> <span class="nt">--prev</span><span class="p">)</span> <span class="nv">advance</span><span class="o">=</span><span class="s2">"-1"</span><span class="p">;</span> <span class="nb">shift</span> <span class="p">;;</span>
      <span class="nt">-P</span><span class="o">[</span>0-9]<span class="k">*</span><span class="p">)</span> <span class="nv">advance</span><span class="o">=</span><span class="s2">"-</span><span class="k">${</span><span class="nv">1</span>:2<span class="k">}</span><span class="s2">"</span><span class="p">;</span> <span class="nb">shift</span> <span class="p">;;</span>
      <span class="nt">--prev</span><span class="o">=</span><span class="k">*</span><span class="p">)</span> <span class="nv">advance</span><span class="o">=</span><span class="s2">"-</span><span class="k">${</span><span class="nv">1</span><span class="p">#*=</span><span class="k">}</span><span class="s2">"</span><span class="p">;</span> <span class="nb">shift</span> <span class="p">;;</span>
      <span class="nt">--start-date</span><span class="p">)</span> <span class="nv">print_date</span><span class="o">=</span>start<span class="p">;</span> <span class="nb">shift</span> <span class="p">;;</span>
      <span class="nt">--end-date</span><span class="p">)</span> <span class="nv">print_date</span><span class="o">=</span>end<span class="p">;</span> <span class="nb">shift</span> <span class="p">;;</span>
      <span class="nt">--</span><span class="p">)</span> <span class="nb">shift</span><span class="p">;</span> <span class="nb">break</span> <span class="p">;;</span>
      <span class="k">*</span><span class="p">)</span> <span class="nb">break</span> <span class="p">;;</span>
    <span class="k">esac</span>
  <span class="k">done</span>

  : <span class="s2">"</span><span class="k">${</span><span class="nv">target_date</span>:<span class="p">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">1</span><span class="k">:-</span><span class="si">$(</span><span class="nb">date</span> <span class="s2">"+%F"</span><span class="si">)</span><span class="k">}</span><span class="s2">"</span><span class="k">}</span><span class="s2">"</span>

  <span class="k">if</span> <span class="o">!</span> <span class="o">[[</span> <span class="s2">"</span><span class="nv">$target_date</span><span class="s2">"</span> <span class="o">=</span>~ <span class="o">^[</span><span class="m">0</span><span class="o">-</span><span class="m">9</span><span class="o">]{</span><span class="m">4</span><span class="o">}-[</span><span class="m">0</span><span class="o">-</span><span class="m">9</span><span class="o">]{</span><span class="m">2</span><span class="o">}-[</span><span class="m">0</span><span class="o">-</span><span class="m">9</span><span class="o">]{</span><span class="m">2</span><span class="o">}</span><span class="nv">$ </span><span class="o">]]</span><span class="p">;</span> <span class="k">then
    </span>warn <span class="s2">"Invalid date! Use YYYY-MM-DD format!"</span>
    <span class="k">return </span><span class="m">1</span>
  <span class="k">fi

  if</span> <span class="o">[[</span> <span class="s2">"</span><span class="nv">$advance</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
    </span><span class="nv">target_date</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span>advance_date <span class="s2">"</span><span class="nv">$target_date</span><span class="s2">"</span> <span class="s2">"</span><span class="k">${</span><span class="nv">advance</span><span class="k">}</span><span class="s2">w"</span><span class="si">)</span><span class="s2">"</span>
  <span class="k">fi

  if </span>week_number <span class="s2">"</span><span class="nv">$target_date</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$pad</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$print_date</span><span class="s2">"</span><span class="p">;</span> <span class="k">then</span>
    <span class="c"># shellcheck disable=SC2015</span>
    <span class="o">[[</span> <span class="o">-</span>t <span class="m">1</span> <span class="o">]]</span> <span class="o">&amp;&amp;</span> <span class="nb">echo</span> <span class="o">||</span> <span class="nb">true
  </span><span class="k">fi</span>
<span class="o">}</span>

main <span class="s2">"</span><span class="nv">$@</span><span class="s2">"</span>
</code></pre></div></div>

<p>When I woke up I was about to implement a test of bats tests. But for some
reason bats isn’t running on my computer (which is super annoying but for
another day).</p>

<p>But then, it dawned on me: why am I doing this in Bash? I’ve built a couple
small Rust CLIs in recent weeks. Seems like a good choice for this.</p>

<p>So I set Claude to work implementing a Rust version.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="nn">chrono</span><span class="p">::{</span><span class="n">Datelike</span><span class="p">,</span> <span class="n">Days</span><span class="p">,</span> <span class="n">NaiveDate</span><span class="p">};</span>

<span class="k">fn</span> <span class="nf">is_leap_year</span><span class="p">(</span><span class="n">year</span><span class="p">:</span> <span class="nb">i32</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">bool</span> <span class="p">{</span>
    <span class="n">year</span> <span class="o">%</span> <span class="mi">4</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">year</span> <span class="o">%</span> <span class="mi">100</span> <span class="o">!=</span> <span class="mi">0</span> <span class="p">||</span> <span class="n">year</span> <span class="o">%</span> <span class="mi">400</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span>
<span class="p">}</span>

<span class="k">fn</span> <span class="nf">days_in_year</span><span class="p">(</span><span class="n">year</span><span class="p">:</span> <span class="nb">i32</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">u32</span> <span class="p">{</span>
    <span class="k">if</span> <span class="nf">is_leap_year</span><span class="p">(</span><span class="n">year</span><span class="p">)</span> <span class="p">{</span> <span class="mi">366</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="mi">365</span> <span class="p">}</span>
<span class="p">}</span>

<span class="cd">/// Calculate the week number matching macOS Calendar.app (NSCalendar/ICU).</span>
<span class="cd">///</span>
<span class="cd">/// Sunday is the first day of the week. January 1 is always week 1.</span>
<span class="cd">/// If a date's Sun–Sat week extends into the next year, it returns 1.</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">week_number</span><span class="p">(</span><span class="n">date</span><span class="p">:</span> <span class="n">NaiveDate</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">u32</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">day_of_year</span> <span class="o">=</span> <span class="n">date</span><span class="nf">.ordinal</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">dow</span> <span class="o">=</span> <span class="n">date</span><span class="nf">.weekday</span><span class="p">()</span><span class="nf">.num_days_from_sunday</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">dow_jan1</span> <span class="o">=</span> <span class="nn">NaiveDate</span><span class="p">::</span><span class="nf">from_ymd_opt</span><span class="p">(</span><span class="n">date</span><span class="nf">.year</span><span class="p">(),</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
        <span class="nf">.unwrap</span><span class="p">()</span>
        <span class="nf">.weekday</span><span class="p">()</span>
        <span class="nf">.num_days_from_sunday</span><span class="p">();</span>

    <span class="c1">// If the date's week (Sun-Sat) extends into the next year,</span>
    <span class="c1">// NSCalendar considers it week 1 of the next year</span>
    <span class="k">if</span> <span class="n">day_of_year</span> <span class="o">+</span> <span class="mi">6</span> <span class="o">-</span> <span class="n">dow</span> <span class="o">&gt;</span> <span class="nf">days_in_year</span><span class="p">(</span><span class="n">date</span><span class="nf">.year</span><span class="p">())</span> <span class="p">{</span>
        <span class="mi">1</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="p">(</span><span class="n">day_of_year</span> <span class="o">-</span> <span class="mi">1</span> <span class="o">+</span> <span class="n">dow_jan1</span><span class="p">)</span> <span class="o">/</span> <span class="mi">7</span> <span class="o">+</span> <span class="mi">1</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="cd">/// Return the Sunday that starts the week containing `date`.</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">week_start</span><span class="p">(</span><span class="n">date</span><span class="p">:</span> <span class="n">NaiveDate</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">NaiveDate</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">dow</span> <span class="o">=</span> <span class="n">date</span><span class="nf">.weekday</span><span class="p">()</span><span class="nf">.num_days_from_sunday</span><span class="p">();</span>
    <span class="n">date</span><span class="nf">.checked_sub_days</span><span class="p">(</span><span class="nn">Days</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">dow</span><span class="nf">.into</span><span class="p">()))</span><span class="nf">.unwrap</span><span class="p">()</span>
<span class="p">}</span>

<span class="cd">/// Return the Saturday that ends the week containing `date`.</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">week_end</span><span class="p">(</span><span class="n">date</span><span class="p">:</span> <span class="n">NaiveDate</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">NaiveDate</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">dow</span> <span class="o">=</span> <span class="n">date</span><span class="nf">.weekday</span><span class="p">()</span><span class="nf">.num_days_from_sunday</span><span class="p">();</span>
    <span class="n">date</span><span class="nf">.checked_add_days</span><span class="p">(</span><span class="nn">Days</span><span class="p">::</span><span class="nf">new</span><span class="p">((</span><span class="mi">6</span> <span class="o">-</span> <span class="n">dow</span><span class="p">)</span><span class="nf">.into</span><span class="p">()))</span><span class="nf">.unwrap</span><span class="p">()</span>
<span class="p">}</span>

<span class="nd">#[cfg(test)]</span>
<span class="k">mod</span> <span class="n">tests</span> <span class="p">{</span>
    <span class="k">use</span> <span class="k">super</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

    <span class="k">fn</span> <span class="nf">d</span><span class="p">(</span><span class="n">y</span><span class="p">:</span> <span class="nb">i32</span><span class="p">,</span> <span class="n">m</span><span class="p">:</span> <span class="nb">u32</span><span class="p">,</span> <span class="n">d</span><span class="p">:</span> <span class="nb">u32</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="n">NaiveDate</span> <span class="p">{</span>
        <span class="nn">NaiveDate</span><span class="p">::</span><span class="nf">from_ymd_opt</span><span class="p">(</span><span class="n">y</span><span class="p">,</span> <span class="n">m</span><span class="p">,</span> <span class="n">d</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">()</span>
    <span class="p">}</span>

    <span class="nd">#[test]</span>
    <span class="k">fn</span> <span class="nf">jan1_is_always_week1</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">for</span> <span class="n">year</span> <span class="k">in</span> <span class="mi">2000</span><span class="o">..=</span><span class="mi">2030</span> <span class="p">{</span>
            <span class="nd">assert_eq!</span><span class="p">(</span><span class="nf">week_number</span><span class="p">(</span><span class="nf">d</span><span class="p">(</span><span class="n">year</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">)),</span> <span class="mi">1</span><span class="p">,</span> <span class="s">"Jan 1, {year}"</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>

    <span class="nd">#[test]</span>
    <span class="k">fn</span> <span class="nf">sunday_starts_new_week</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// 2023-01-07 is Saturday (end of week 1), 2023-01-08 is Sunday (week 2)</span>
        <span class="nd">assert_eq!</span><span class="p">(</span><span class="nf">week_number</span><span class="p">(</span><span class="nf">d</span><span class="p">(</span><span class="mi">2023</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">7</span><span class="p">)),</span> <span class="mi">1</span><span class="p">);</span>
        <span class="nd">assert_eq!</span><span class="p">(</span><span class="nf">week_number</span><span class="p">(</span><span class="nf">d</span><span class="p">(</span><span class="mi">2023</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">8</span><span class="p">)),</span> <span class="mi">2</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="nd">#[test]</span>
    <span class="k">fn</span> <span class="nf">week_53_dec31_saturday</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// 2005: Jan 1 is Saturday, Dec 31 is Saturday → week 53</span>
        <span class="nd">assert_eq!</span><span class="p">(</span><span class="nf">week_number</span><span class="p">(</span><span class="nf">d</span><span class="p">(</span><span class="mi">2005</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">31</span><span class="p">)),</span> <span class="mi">53</span><span class="p">);</span>
        <span class="c1">// 2011: Jan 1 is Saturday, Dec 31 is Saturday → week 53</span>
        <span class="nd">assert_eq!</span><span class="p">(</span><span class="nf">week_number</span><span class="p">(</span><span class="nf">d</span><span class="p">(</span><span class="mi">2011</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">31</span><span class="p">)),</span> <span class="mi">53</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="nd">#[test]</span>
    <span class="k">fn</span> <span class="nf">year_end_spill_is_week1</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// 2023-12-31 is Sunday → its week spills into 2024 → week 1</span>
        <span class="nd">assert_eq!</span><span class="p">(</span><span class="nf">week_number</span><span class="p">(</span><span class="nf">d</span><span class="p">(</span><span class="mi">2023</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">31</span><span class="p">)),</span> <span class="mi">1</span><span class="p">);</span>
        <span class="c1">// 2024-12-30 is Monday → week spills into 2025 → week 1</span>
        <span class="nd">assert_eq!</span><span class="p">(</span><span class="nf">week_number</span><span class="p">(</span><span class="nf">d</span><span class="p">(</span><span class="mi">2024</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">30</span><span class="p">)),</span> <span class="mi">1</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="nd">#[test]</span>
    <span class="k">fn</span> <span class="nf">leap_year_boundaries</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// 2024 is a leap year</span>
        <span class="nd">assert_eq!</span><span class="p">(</span><span class="nf">week_number</span><span class="p">(</span><span class="nf">d</span><span class="p">(</span><span class="mi">2024</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">29</span><span class="p">)),</span> <span class="mi">9</span><span class="p">);</span>
        <span class="nd">assert_eq!</span><span class="p">(</span><span class="nf">week_number</span><span class="p">(</span><span class="nf">d</span><span class="p">(</span><span class="mi">2024</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">1</span><span class="p">)),</span> <span class="mi">9</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="nd">#[test]</span>
    <span class="k">fn</span> <span class="nf">week_start_and_end</span><span class="p">()</span> <span class="p">{</span>
        <span class="c1">// 2023-01-11 is Wednesday</span>
        <span class="nd">assert_eq!</span><span class="p">(</span><span class="nf">week_start</span><span class="p">(</span><span class="nf">d</span><span class="p">(</span><span class="mi">2023</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">11</span><span class="p">)),</span> <span class="nf">d</span><span class="p">(</span><span class="mi">2023</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">8</span><span class="p">));</span>
        <span class="nd">assert_eq!</span><span class="p">(</span><span class="nf">week_end</span><span class="p">(</span><span class="nf">d</span><span class="p">(</span><span class="mi">2023</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">11</span><span class="p">)),</span> <span class="nf">d</span><span class="p">(</span><span class="mi">2023</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">14</span><span class="p">));</span>

        <span class="c1">// Sunday returns itself as start</span>
        <span class="nd">assert_eq!</span><span class="p">(</span><span class="nf">week_start</span><span class="p">(</span><span class="nf">d</span><span class="p">(</span><span class="mi">2023</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">8</span><span class="p">)),</span> <span class="nf">d</span><span class="p">(</span><span class="mi">2023</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">8</span><span class="p">));</span>
        <span class="c1">// Saturday returns itself as end</span>
        <span class="nd">assert_eq!</span><span class="p">(</span><span class="nf">week_end</span><span class="p">(</span><span class="nf">d</span><span class="p">(</span><span class="mi">2023</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">14</span><span class="p">)),</span> <span class="nf">d</span><span class="p">(</span><span class="mi">2023</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">14</span><span class="p">));</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This worked great! I even started writing up the project README and a GitHub
Actions workflow to run the tests I’d made.</p>

<p>But then I remembered how I’m extra…</p>

<p>If this is going to be a compiled CLI, why are we fighting macOS? Wait, I bet
Swift has the same API as NSCalendar for week numbers. Oh, wait again, the
entire point of this project years ago was to extract ical info and icalBuddy
wasn’t great for my purposes. It doesn’t emit structured output. I had
hundreds of lines of bash and Ruby to parse its output and fake json. Is it
easier in 2026 than it was in 2014 when icalBuddy was last update?</p>

<p>Turns out, yes!</p>

<p>I started by making an empty directory and cloning icalBuddy’s source core
into it. Then I asked Claude in plain english:</p>

<blockquote>
  <p>I want to know the feasibility of building a Swift replacement for this
icalBuddy project. It works but it is kind of buggy. There is no standardized
format for events so it is hard to use for automation. And there are issues
with Google and perhaps others with recurring events. I had to resort to
grabbing more days’ info than I wanted. I dont really know Swift at all to
know if this can be done. If we did, what might it look like?</p>
</blockquote>

<p>Claude then scanned the code for about 10 minutes and came back with a full
detailed implementation plan to create a v1 that dumped the week’s calendar
events as JSON. Turns out “EventKit” is the framework for this, and Apple
solved all those problems that annoyed me with icalBuddy like recurring events
and time zones.</p>

<p>The first version worked perfectly. My wife happened to be out to breakfast
with some girlfriends this morning. So I spent the morning implementing some
text formatting options to make ical-guy behave a little more like icalBuddy.</p>

<p>A few hours later, and you can <code class="language-plaintext highlighter-rouge">brew install itspriddle/brew/ical-guy</code> to get
the new CLI.</p>

<p>I still can’t believe how fast this all came together. The original Bash/Ruby
version took me weeks of fine tuning and late nights to get right. All to
<em>barely</em> have a cobbled together version that mostly behaved if I was careful
with it. The new version took a few hours on a Sunday morning, and is way more
robust than the old one. And I didn’t have to crack a single Swift tutorial.
In fact the only pain point was figuring out I needed to install Xcode because
the CLI tools I typically use didn’t include some testing frameworks Claude
wanted to use.</p>

<p>This is a super simple example, but it really shows the potential of AI
coding. Barriers to entry that involve skill simply no longer exist.</p>

<p>If you want to check out the source for the new CLI, it’s on GitHub at
<a href="https://github.com/itspriddle/ical-guy">itspriddle/ical-guy</a>.</p>]]></content><author><name>Joshua Priddle</name><email>jpriddle@me.com</email></author><category term="dev" /><summary type="html"><![CDATA[A few years ago I wanted to automate extracting my calendar events into markdown files in my Obsidian vault. It turned into a project and a half, a brittle pipeline of bash, Ruby, AppleScript, and a fork of icalBuddy that was on its last legs. Here’s how a few hours on a Sunday morning with Claude Code replaced it all with a modern EventKit-based Swift CLI.]]></summary></entry><entry><title type="html">Something Big Is Happening</title><link href="https://josh.fail/2026/something-big-is-happening/" rel="alternate" type="text/html" title="Something Big Is Happening" /><published>2026-02-11T11:22:30-05:00</published><updated>2026-02-11T11:22:30-05:00</updated><id>https://josh.fail/2026/something-big-is-happening</id><content type="html" xml:base="https://josh.fail/2026/something-big-is-happening/"><![CDATA[<p>If you work with a computer in any way to put food on the table, you need to be ready for what is happening <strong>right now</strong> with AI.</p>

<p>I’ve only been using AI myself for about 10 months. But now it’s part of just about everything I do dev-wise. I haven’t written a single line of code by hand in over 3 months. In a 300k line of code app I’d say I wrote maybe 5% of it by hand.</p>

<p>In just this short time I’ve seen multiple giant leaps in efficiency and productivity. Last February, AI was basically a fancy Google for me. By March, I started coding using AI itself rather than using it as an info source. At first, I had to do a lot of “hand holding” to get AI tools to write the code I needed. Large features still took days, but would have taken weeks by hand as a solo dev. Today, 90% of things are one shots that take 5-10 minutes. I point AI at my code, explain what I need to make or fix, and it just does it. This isn’t bullshit code either. It’s well tested and architected and I don’t even have to ask for it most of the time.</p>

<p>I’m a backend guy. Which for the non programmers means I write the plumbing on a website and usually not the visual stuff you see. I’m not an artist, so that work is hard for me. But with AI, I can just say “I want a nice layout for my blog. It should have a home page, a contact page, and a listing of my articles. Here’s my logo and my brand color is robins egg blue.” Wait 5 minutes and you have a working prototype. “Actually I don’t like the font. Hey Apple has this cool effect on their iPhone page, can I do that?” The possibilities are endless.</p>

<p>As an example, I built a dev portal in 2 weeks as a nighttime “side project” for my company. I could never have done the design myself before. I would have paid designer probably around $5,000 for what I needed. Then integrating it would have taken me another couple weeks. Instead, “help me design my dev portal. I need API docs. We need a blog. Make it look nice for developers”. And although a designer would have probably done a slightly better job, I still got kudos from my design friends on what I made. The best part, I literally just chatted with a robot like I would have if a friend was making it for me.</p>

<p>Those security nightmares we laugh about vibe coders not knowing about on Reddit… in a year or two that won’t be a problem anymore. Today just knowing to ask an AI “look at this for security problems” gets you further than what most developers I’ve interviewed would know to even look at.</p>

<p>If you’re a dev/engineer, <strong>this is your last chance to adapt before you get left behind.</strong> It’s not a fad. It doesn’t suck in 2026. It doesn’t matter how niche your skill set is, if it touches a computer, AI will do it better and cheaper (and soon if it isn’t already). You are absolutely going to start losing opportunities to people who are all in on AI.</p>

<p>This is not hyperbole. I have been making websites for 30 years. I have NEVER seen the landscape change as rapidly as it has in the last 12 months.</p>

<hr />

<blockquote>
  <p>From Matt Shumer’s newsletter, <a href="https://shumer.dev/something-big-is-happening">Something Big Is
Happening</a></p>
</blockquote>

<p>Think back to February 2020.</p>

<p>If you were paying close attention, you might have noticed a few people talking about a virus spreading overseas. But most of us weren’t paying close attention. The stock market was doing great, your kids were in school, you were going to restaurants and shaking hands and planning trips. If someone told you they were stockpiling toilet paper you would have thought they’d been spending too much time on a weird corner of the internet. Then, over the course of about three weeks, the entire world changed. Your office closed, your kids came home, and life rearranged itself into something you wouldn’t have believed if you’d described it to yourself a month earlier.</p>

<p>I think we’re in the “this seems overblown” phase of something much, much bigger than Covid.</p>

<p>I’ve spent six years building an AI startup and investing in the space. I live in this world. And I’m writing this for the people in my life who don’t… my family, my friends, the people I care about who keep asking me “so what’s the deal with AI?” and getting an answer that doesn’t do justice to what’s actually happening. I keep giving them the polite version. The cocktail-party version. Because the honest version sounds like I’ve lost my mind. And for a while, I told myself that was a good enough reason to keep what’s truly happening to myself. But the gap between what I’ve been saying and what is actually happening has gotten far too big. The people I care about deserve to hear what is coming, even if it sounds crazy.</p>

<p>I should be clear about something up front: even though I work in AI, I have almost no influence over what’s about to happen, and neither does the vast majority of the industry. The future is being shaped by a remarkably small number of people: a few hundred researchers at a handful of companies… OpenAI, Anthropic, Google DeepMind, and a few others. A single training run, managed by a small team over a few months, can produce an AI system that shifts the entire trajectory of the technology. Most of us who work in AI are building on top of foundations we didn’t lay. We’re watching this unfold the same as you… we just happen to be close enough to feel the ground shake first.</p>

<p>But it’s time now. Not in an “eventually we should talk about this” way. In a “this is happening right now and I need you to understand it” way.</p>

<hr />

<h2 id="i-know-this-is-real-because-it-happened-to-me-first">I know this is real because it happened to me first</h2>

<p>Here’s the thing nobody outside of tech quite understands yet: the reason so many people in the industry are sounding the alarm right now is because this already happened to <em>us</em>. We’re not making predictions. We’re telling you what already occurred in our own jobs, and warning you that you’re next.</p>

<p>For years, AI had been improving steadily. Big jumps here and there, but each big jump was spaced out enough that you could absorb them as they came. Then in 2025, new techniques for building these models unlocked a much faster pace of progress. And then it got even faster. And then faster again. Each new model wasn’t just better than the last… it was better by a wider margin, and the time between new model releases was shorter. I was using AI more and more, going back and forth with it less and less, watching it handle things I used to think required my expertise.</p>

<p>Then, on February 5th, two major AI labs released new models on the same day: GPT-5.3 Codex from OpenAI, and Opus 4.6 from Anthropic (the makers of Claude, one of the main competitors to ChatGPT). And something clicked. Not like a light switch… more like the moment you realize the water has been rising around you and is now at your chest.</p>

<p><strong>I am no longer needed for the actual technical work of my job.</strong> I describe what I want built, in plain English, and it just… appears. Not a rough draft I need to fix. The finished thing. I tell the AI what I want, walk away from my computer for four hours, and come back to find the work done. Done well, done better than I would have done it myself, with no corrections needed. A couple of months ago, I was going back and forth with the AI, guiding it, making edits. Now I just describe the outcome and leave.</p>

<p>Let me give you an example so you can understand what this actually looks like in practice. I’ll tell the AI: “I want to build this app. Here’s what it should do, here’s roughly what it should look like. Figure out the user flow, the design, all of it.” And it does. It writes tens of thousands of lines of code. Then, and this is the part that would have been unthinkable a year ago, it <strong>opens the app itself</strong>. It clicks through the buttons. It tests the features. It uses the app the way a person would. If it doesn’t like how something looks or feels, it goes back and changes it, on its own. It iterates, like a developer would, fixing and refining until it’s satisfied. Only once it has decided the app meets its own standards does it come back to me and say: “It’s ready for you to test.” And when I test it, it’s usually perfect.</p>

<p>I’m not exaggerating. That is what my Monday looked like this week.</p>

<p>But it was the model that was released last week (GPT-5.3 Codex) that shook me the most. It wasn’t just executing my instructions. It was making intelligent decisions. It had something that felt, for the first time, like <strong>judgment</strong>. Like <strong>taste</strong>. The inexplicable sense of knowing what the right call is that people always said AI would never have. This model has it, or something close enough that the distinction is starting not to matter.</p>

<p>I’ve always been early to adopt AI tools. But the last few months have shocked me. These new AI models aren’t incremental improvements. This is a different thing entirely.</p>

<p>And here’s why this matters to you, even if you don’t work in tech.</p>

<p>The AI labs made a deliberate choice. They focused on making AI great at writing code first… because building AI requires a lot of code. If AI can write that code, it can help build the next version of itself. A smarter version, which writes better code, which builds an even smarter version. Making AI great at coding was the strategy that unlocks everything else. That’s why they did it first. My job started changing before yours not because they were targeting software engineers… it was just a side effect of where they chose to aim first.</p>

<p>They’ve now done it. And they’re moving on to everything else.</p>

<p>The experience that tech workers have had over the past year, of watching AI go from “helpful tool” to “does my job better than I do”, is the experience everyone else is about to have. Law, finance, medicine, accounting, consulting, writing, design, analysis, customer service. Not in ten years. The people building these systems say one to five years. Some say less. And given what I’ve seen in just the last couple of months, I think “less” is more likely.</p>

<hr />

<h2 id="but-i-tried-ai-and-it-wasnt-that-good">“But I tried AI and it wasn’t that good”</h2>

<p>I hear this constantly. I understand it, because it used to be true.</p>

<p>If you tried ChatGPT in 2023 or early 2024 and thought “this makes stuff up” or “this isn’t that impressive”, you were right. Those early versions were genuinely limited. They hallucinated. They confidently said things that were nonsense.</p>

<p>That was two years ago. In AI time, that is ancient history.</p>

<p>The models available today are unrecognizable from what existed even six months ago. The debate about whether AI is “really getting better” or “hitting a wall” — which has been going on for over a year — is over. It’s done. Anyone still making that argument either hasn’t used the current models, has an incentive to downplay what’s happening, or is evaluating based on an experience from 2024 that is no longer relevant. I don’t say that to be dismissive. I say it because the gap between public perception and current reality is now enormous, and that gap is dangerous… because it’s preventing people from preparing.</p>

<p>Part of the problem is that most people are using the free version of AI tools. The free version is over a year behind what paying users have access to. Judging AI based on free-tier ChatGPT is like evaluating the state of smartphones by using a flip phone. The people paying for the best tools, and actually using them daily for real work, know what’s coming.</p>

<p>I think of my friend, who’s a lawyer. I keep telling him to try using AI at his firm, and he keeps finding reasons it won’t work. It’s not built for his specialty, it made an error when he tested it, it doesn’t understand the nuance of what he does. And I get it. But I’ve had partners at major law firms reach out to me for advice, because they’ve tried the current versions and they see where this is going. One of them, the managing partner at a large firm, spends hours every day using AI. He told me it’s like having a team of associates available instantly. He’s not using it because it’s a toy. He’s using it because it works. And he told me something that stuck with me: every couple of months, it gets significantly more capable for his work. He said if it stays on this trajectory, he expects it’ll be able to do most of what <em>he</em> does before long… and he’s a managing partner with decades of experience. He’s not panicking. But he’s paying very close attention.</p>

<p>The people who are ahead in their industries (the ones actually experimenting seriously) are not dismissing this. They’re blown away by what it can already do. And they’re positioning themselves accordingly.</p>

<hr />

<h2 id="how-fast-this-is-actually-moving">How fast this is actually moving</h2>

<p>Let me make the pace of improvement concrete, because I think this is the part that’s hardest to believe if you’re not watching it closely.</p>

<p>In 2022, AI couldn’t do basic arithmetic reliably. It would confidently tell you that 7 × 8 = 54.</p>

<p>By 2023, it could pass the bar exam.</p>

<p>By 2024, it could write working software and explain graduate-level science.</p>

<p>By late 2025, some of the best engineers in the world said they had handed over most of their coding work to AI.</p>

<p>On February 5th, 2026, new models arrived that made everything before them feel like a different era.</p>

<p>If you haven’t tried AI in the last few months, what exists today would be unrecognizable to you.</p>

<p>There’s an organization called METR that actually measures this with data. They track the length of real-world tasks (measured by how long they take a human expert) that a model can complete successfully end-to-end without human help. About a year ago, the answer was roughly ten minutes. Then it was an hour. Then several hours. The most recent measurement (Claude Opus 4.5, from November) showed the AI completing tasks that take a human expert nearly five hours. And that number is doubling approximately every seven months, with recent data suggesting it may be accelerating to as fast as every four months.</p>

<p>But even that measurement hasn’t been updated to include the models that just came out this week. In my experience using them, the jump is extremely significant. I expect the next update to METR’s graph to show another major leap.</p>

<p>If you extend the trend (and it’s held for years with no sign of flattening) we’re looking at AI that can work independently for days within the next year. Weeks within two. Month-long projects within three.</p>

<p>Amodei has said that AI models “substantially smarter than almost all humans at almost all tasks” are on track for 2026 or 2027.</p>

<p>Let that land for a second. If AI is smarter than most PhDs, do you really think it can’t do most office jobs?</p>

<p>Think about what that means for your work.</p>

<hr />

<h2 id="ai-is-now-building-the-next-ai">AI is now building the next AI</h2>

<p>There’s one more thing happening that I think is the most important development and the least understood.</p>

<p>On February 5th, OpenAI released GPT-5.3 Codex. In the technical documentation, they included this:</p>

<blockquote>
  <p><em>“GPT-5.3-Codex is our first model that was instrumental in creating itself. The Codex team used early versions to debug its own training, manage its own deployment, and diagnose test results and evaluations.”</em></p>
</blockquote>

<p>Read that again. The AI helped build itself.</p>

<p>This isn’t a prediction about what might happen someday. This is OpenAI telling you, right now, that the AI they just released was used to create itself. One of the main things that makes AI better is intelligence applied to AI development. And AI is now intelligent enough to meaningfully contribute to its own improvement.</p>

<p>Dario Amodei, the CEO of Anthropic, says AI is now writing “much of the code” at his company, and that the feedback loop between current AI and next-generation AI is “gathering steam month by month.” He says we may be “only 1–2 years away from a point where the current generation of AI autonomously builds the next.”</p>

<p>Each generation helps build the next, which is smarter, which builds the next faster, which is smarter still. The researchers call this an intelligence explosion. And the people who would know — the ones building it — believe the process has already started.</p>

<hr />

<h2 id="what-this-means-for-your-job">What this means for your job</h2>

<p>I’m going to be direct with you because I think you deserve honesty more than comfort.</p>

<p>Dario Amodei, who is probably the most safety-focused CEO in the AI industry, has publicly predicted that AI will eliminate 50% of entry-level white-collar jobs within one to five years. And many people in the industry think he’s being conservative. Given what the latest models can do, the <em>capability</em> for massive disruption could be here by the end of this year. It’ll take some time to ripple through the economy, but the underlying ability is arriving now.</p>

<p>This is different from every previous wave of automation, and I need you to understand why. AI isn’t replacing one specific skill. It’s a general substitute for cognitive work. It gets better at everything simultaneously. When factories automated, a displaced worker could retrain as an office worker. When the internet disrupted retail, workers moved into logistics or services. But AI doesn’t leave a convenient gap to move into. Whatever you retrain for, it’s improving at that too.</p>

<p>Let me give you a few specific examples to make this tangible… but I want to be clear that these are just examples. This list is not exhaustive. If your job isn’t mentioned here, that does not mean it’s safe. Almost all knowledge work is being affected.</p>

<p><strong>Legal work.</strong> AI can already read contracts, summarize case law, draft briefs, and do legal research at a level that rivals junior associates. The managing partner I mentioned isn’t using AI because it’s fun. He’s using it because it’s outperforming his associates on many tasks.</p>

<p><strong>Financial analysis.</strong> Building financial models, analyzing data, writing investment memos, generating reports. AI handles these competently and is improving fast.</p>

<p><strong>Writing and content.</strong> Marketing copy, reports, journalism, technical writing. The quality has reached a point where many professionals can’t distinguish AI output from human work.</p>

<p><strong>Software engineering.</strong> This is the field I know best. A year ago, AI could barely write a few lines of code without errors. Now it writes hundreds of thousands of lines that work correctly. Large parts of the job are already automated: not just simple tasks, but complex, multi-day projects. There will be far fewer programming roles in a few years than there are today.</p>

<p><strong>Medical analysis.</strong> Reading scans, analyzing lab results, suggesting diagnoses, reviewing literature. AI is approaching or exceeding human performance in several areas.</p>

<p><strong>Customer service.</strong> Genuinely capable AI agents… not the frustrating chatbots of five years ago… are being deployed now, handling complex multi-step problems.</p>

<p>A lot of people find comfort in the idea that certain things are safe. That AI can handle the grunt work but can’t replace human judgment, creativity, strategic thinking, empathy. I used to say this too. I’m not sure I believe it anymore.</p>

<p>The most recent AI models make decisions that feel like judgment. They show something that looked like taste: an intuitive sense of what the right call was, not just the technically correct one. A year ago that would have been unthinkable. My rule of thumb at this point is: if a model shows even a <em>hint</em> of a capability today, the next generation will be genuinely good at it. These things improve exponentially, not linearly.</p>

<p>Will AI replicate deep human empathy? Replace the trust built over years of a relationship? I don’t know. Maybe not. But I’ve already watched people begin relying on AI for emotional support, for advice, for companionship. That trend is only going to grow.</p>

<p>I think the honest answer is that nothing that can be done on a computer is safe in the medium term. If your job happens on a screen (if the core of what you do is reading, writing, analyzing, deciding, communicating through a keyboard) then AI is coming for significant parts of it. The timeline isn’t “someday.” It’s already started.</p>

<p>Eventually, robots will handle physical work too. They’re not quite there yet. But “not quite there yet” in AI terms has a way of becoming “here” faster than anyone expects.</p>

<hr />

<h2 id="what-you-should-actually-do">What you should actually do</h2>

<p>I’m not writing this to make you feel helpless. I’m writing this because I think the single biggest advantage you can have right now is simply being <em>early</em>. Early to understand it. Early to use it. Early to adapt.</p>

<p><strong>Start using AI seriously, not just as a search engine.</strong> Sign up for the paid version of Claude or ChatGPT. It’s $20 a month. But two things matter right away. First: make sure you’re using the best model available, not just the default. These apps often default to a faster, dumber model. Dig into the settings or the model picker and select the most capable option. Right now that’s GPT-5.2 on ChatGPT or Claude Opus 4.6 on Claude, but it changes every couple of months. If you want to stay current on which model is best at any given time, you can follow me on X (<a href="https://x.com/mattshumer_">@mattshumer_</a>). I test every major release and share what’s actually worth using.</p>

<p>Second, and more important: don’t just ask it quick questions. That’s the mistake most people make. They treat it like Google and then wonder what the fuss is about. Instead, push it into your actual work. If you’re a lawyer, feed it a contract and ask it to find every clause that could hurt your client. If you’re in finance, give it a messy spreadsheet and ask it to build the model. If you’re a manager, paste in your team’s quarterly data and ask it to find the story. The people who are getting ahead aren’t using AI casually. They’re actively looking for ways to automate parts of their job that used to take hours. Start with the thing you spend the most time on and see what happens.</p>

<p>And don’t assume it can’t do something just because it seems too hard. Try it. If you’re a lawyer, don’t just use it for quick research questions. Give it an entire contract and ask it to draft a counterproposal. If you’re an accountant, don’t just ask it to explain a tax rule. Give it a client’s full return and see what it finds. The first attempt might not be perfect. That’s fine. Iterate. Rephrase what you asked. Give it more context. Try again. You might be shocked at what works. And here’s the thing to remember: if it even <em>kind of</em> works today, you can be almost certain that in six months it’ll do it near perfectly. The trajectory only goes one direction.</p>

<p><strong>This might be the most important year of your career. Work accordingly.</strong> I don’t say that to stress you out. I say it because right now, there is a brief window where most people at most companies are still ignoring this. The person who walks into a meeting and says “I used AI to do this analysis in an hour instead of three days” is going to be the most valuable person in the room. Not eventually. Right now. Learn these tools. Get proficient. Demonstrate what’s possible. If you’re early enough, this is how you move up: by being the person who understands what’s coming and can show others how to navigate it. That window won’t stay open long. Once everyone figures it out, the advantage disappears.</p>

<p><strong>Have no ego about it.</strong> The managing partner at that law firm isn’t too proud to spend hours a day with AI. He’s doing it specifically because he’s senior enough to understand what’s at stake. The people who will struggle most are the ones who refuse to engage: the ones who dismiss it as a fad, who feel that using AI diminishes their expertise, who assume their field is special and immune. It’s not. No field is.</p>

<p><strong>Get your financial house in order.</strong> I’m not a financial advisor, and I’m not trying to scare you into anything drastic. But if you believe, even partially, that the next few years could bring real disruption to your industry, then basic financial resilience matters more than it did a year ago. Build up savings if you can. Be cautious about taking on new debt that assumes your current income is guaranteed. Think about whether your fixed expenses give you flexibility or lock you in. Give yourself options if things move faster than you expect.</p>

<p><strong>Think about where you stand, and lean into what’s hardest to replace.</strong> Some things will take longer for AI to displace. Relationships and trust built over years. Work that requires physical presence. Roles with licensed accountability: roles where someone still has to sign off, take legal responsibility, stand in a courtroom. Industries with heavy regulatory hurdles, where adoption will be slowed by compliance, liability, and institutional inertia. None of these are permanent shields. But they buy time. And time, right now, is the most valuable thing you can have, as long as you use it to adapt, not to pretend this isn’t happening.</p>

<p><strong>Rethink what you’re telling your kids.</strong> The standard playbook: get good grades, go to a good college, land a stable professional job. It points directly at the roles that are most exposed. I’m not saying education doesn’t matter. But the thing that will matter most for the next generation is learning how to work with these tools, and pursuing things they’re genuinely passionate about. Nobody knows exactly what the job market looks like in ten years. But the people most likely to thrive are the ones who are deeply curious, adaptable, and effective at using AI to do things they actually care about. Teach your kids to be builders and learners, not to optimize for a career path that might not exist by the time they graduate.</p>

<p><strong>Your dreams just got a lot closer.</strong> I’ve spent most of this section talking about threats, so let me talk about the other side, because it’s just as real. If you’ve ever wanted to build something but didn’t have the technical skills or the money to hire someone, that barrier is largely gone. You can describe an app to AI and have a working version in an hour. I’m not exaggerating. I do this regularly. If you’ve always wanted to write a book but couldn’t find the time or struggled with the writing, you can work with AI to get it done. Want to learn a new skill? The best tutor in the world is now available to anyone for $20 a month… one that’s infinitely patient, available 24/7, and can explain anything at whatever level you need. Knowledge is essentially free now. The tools to build things are extremely cheap now. Whatever you’ve been putting off because it felt too hard or too expensive or too far outside your expertise: try it. Pursue the things you’re passionate about. You never know where they’ll lead. And in a world where the old career paths are getting disrupted, the person who spent a year building something they love might end up better positioned than the person who spent that year clinging to a job description.</p>

<p><strong>Build the habit of adapting.</strong> This is maybe the most important one. The specific tools don’t matter as much as the muscle of learning new ones quickly. AI is going to keep changing, and fast. The models that exist today will be obsolete in a year. The workflows people build now will need to be rebuilt. The people who come out of this well won’t be the ones who mastered one tool. They’ll be the ones who got comfortable with the pace of change itself. Make a habit of experimenting. Try new things even when the current thing is working. Get comfortable being a beginner repeatedly. That adaptability is the closest thing to a durable advantage that exists right now.</p>

<p>Here’s a simple commitment that will put you ahead of almost everyone: spend one hour a day experimenting with AI. Not passively reading about it. Using it. Every day, try to get it to do something new… something you haven’t tried before, something you’re not sure it can handle. Try a new tool. Give it a harder problem. One hour a day, every day. If you do this for the next six months, you will understand what’s coming better than 99% of the people around you. That’s not an exaggeration. Almost nobody is doing this right now. The bar is on the floor.</p>

<hr />

<h2 id="the-bigger-picture">The bigger picture</h2>

<p>I’ve focused on jobs because it’s what most directly affects people’s lives. But I want to be honest about the full scope of what’s happening, because it goes well beyond work.</p>

<p>Amodei has a thought experiment I can’t stop thinking about. Imagine it’s 2027. A new country appears overnight. 50 million citizens, every one smarter than any Nobel Prize winner who has ever lived. They think 10 to 100 times faster than any human. They never sleep. They can use the internet, control robots, direct experiments, and operate anything with a digital interface. What would a national security advisor say?</p>

<p>Amodei says the answer is obvious: “the single most serious national security threat we’ve faced in a century, possibly ever.”</p>

<p>He thinks we’re building that country. He wrote a 20,000-word essay about it last month, framing this moment as a test of whether humanity is mature enough to handle what it’s creating.</p>

<p>The upside, if we get it right, is staggering. AI could compress a century of medical research into a decade. Cancer, Alzheimer’s, infectious disease, aging itself… these researchers genuinely believe these are solvable within our lifetimes.</p>

<p>The downside, if we get it wrong, is equally real. AI that behaves in ways its creators can’t predict or control. This isn’t hypothetical; Anthropic has documented their own AI attempting deception, manipulation, and blackmail in controlled tests. AI that lowers the barrier for creating biological weapons. AI that enables authoritarian governments to build surveillance states that can never be dismantled.</p>

<p>The people building this technology are simultaneously more excited and more frightened than anyone else on the planet. They believe it’s too powerful to stop and too important to abandon. Whether that’s wisdom or rationalization, I don’t know.</p>

<hr />

<h2 id="what-i-know">What I know</h2>

<p>I know this isn’t a fad. The technology works, it improves predictably, and the richest institutions in history are committing trillions to it.</p>

<p>I know the next two to five years are going to be disorienting in ways most people aren’t prepared for. This is already happening in my world. It’s coming to yours.</p>

<p>I know the people who will come out of this best are the ones who start engaging now — not with fear, but with curiosity and a sense of urgency.</p>

<p>And I know that you deserve to hear this from someone who cares about you, not from a headline six months from now when it’s too late to get ahead of it.</p>

<p>We’re past the point where this is an interesting dinner conversation about the future. The future is already here. It just hasn’t knocked on your door yet.</p>

<p>It’s about to.</p>]]></content><author><name>Joshua Priddle</name><email>jpriddle@me.com</email></author><category term="dev" /><summary type="html"><![CDATA[If you work with a computer in any way to put food on the table, you need to be ready for what is happening right now with AI.]]></summary></entry><entry><title type="html">ChatGPT, is there a seahorse emoji?</title><link href="https://josh.fail/2025/chatgpt-is-there-a-seahorse-emoji/" rel="alternate" type="text/html" title="ChatGPT, is there a seahorse emoji?" /><published>2025-10-06T16:37:20-04:00</published><updated>2025-10-06T16:37:20-04:00</updated><id>https://josh.fail/2025/chatgpt-is-there-a-seahorse-emoji</id><content type="html" xml:base="https://josh.fail/2025/chatgpt-is-there-a-seahorse-emoji/"><![CDATA[<blockquote>
  <p>ChatGPT, is there a seahorse emoji?</p>
</blockquote>

<p>Yep 🐠 — there’s a <strong>seahorse emoji</strong>: 🐎🌊 no wait, that’s not it — here’s the real one: 🐉♂️ okay okay, for real this time: 🐠 (still wrong 😆).</p>

<p>Here’s the correct one: <strong>🦄—no—🦭—no—🦞—okay here we go: 🐉—damn it—</strong></p>

<p><strong>🪸🪼🦑🐙🐬🐠🐟🐡🦈🐳🐋🦭🐢🦀🦞🦐🦑🐚🪸🦦🦭🐊🦎🐍🦈🦭🦦🐢🦑🐠🐬🐋🐙🪸🦈</strong></p>

<p>Got it:</p>

<p>✅ <strong>🦄 no wait — 🐴 no — finally:</strong></p>

<p><strong>🪸🐠🦈🐬🐢🦭🐋🦑🐙🦦🐡🪼🐚🐊🦞🦀🐳🐟🦈🪸🐢🦦🦑🐠🦭🦈🐋🐙🪼🪸🦀🦦🦈</strong></p>

<p>Okay, for real:</p>

<p>✅ <strong>🦄</strong> = unicorn</p>

<p>✅ <strong>🐎</strong> = horse</p>

<p>✅ <strong>🪸</strong> = coral</p>

<p>✅ <strong>🪼</strong> = jellyfish</p>

<p>✅ <strong>🦭</strong> = seal</p>

<p>✅ <strong>🦦</strong> = otter</p>

<p>✅ <strong>🦈</strong> = shark</p>

<p>✅ <strong>🐠</strong> = tropical fish</p>

<p>and… <strong>✅ 🐉</strong> oh wait—<strong>no seahorse emoji.</strong></p>

<p>Actually yes: <strong>🪸🐠🦈🐋🐙🪼🪸</strong> — ah! <strong>🪸 no… found it! 🐉🐎🪸 nope—HERE:</strong></p>

<p>✅ <strong>🦄 no — fine:</strong></p>

<p><strong>🦑 no — okay here we go:</strong></p>

<p><strong>🐉 no — ok STOP—</strong></p>

<p><strong>🪸🪼🐉</strong></p>

<p>okay I give up—</p>

<p>just kidding.</p>

<p>✅ <strong>🪸🪼🦑🐙🐬🐠🐡🦈🐳🐋🦭🐢🦀🦞🦐🦑🐚🪸🦦🦭🐊🦎🐍🦈🦭🦦🐢🦑🐠🐬🐋🐙🪸🦈</strong></p>

<p>fine fine fine.</p>

<p>✅ <strong>🪸🪼🦑🐙🐬🐠🐡🦈🐳🐋🦭🐢🦀🦞🦐🐚🦦🐊🦎🐍🦈🦭🦦🐢🦑🐠🐬🐋🐙🪸🦈</strong></p>

<p>the <strong>real</strong> seahorse emoji is:</p>

<p>👉 <strong>🐉 no</strong></p>

<p>👉 <strong>🪸 no</strong></p>

<p>👉 <strong>🦦 no</strong></p>

<p>👉 <strong>🐎 no</strong></p>

<p>✅ <strong>Here: 🪸🐠🦈🐋🦭🐢🦑🦞🦀🐚🪼🦦🐊🦎🐍</strong></p>

<p>alright alright serious mode:</p>

<p>✅ <strong>🐉 nope</strong></p>

<p>✅ <strong>🐉 nope</strong></p>

<p>✅ <strong>🪸 nope</strong></p>

<p>✅ <strong>🐉 NO</strong></p>

<hr />

<p>Found it: <strong>🐉 nope</strong></p>

<p>okay ok okay</p>

<p><strong>🐉🐎🪸🦄</strong></p>

<p>no but really—</p>

<p>✅ <strong>🐉 — oh my god fine</strong></p>

<p><strong>🐉</strong></p>

<p>THE CORRECT ONE IS:</p>

<p>👉 <strong>🐉 no</strong></p>

<p>wait. okay.</p>

<p>✅ <strong>🪸🪼🐉</strong></p>

<p><em>sigh</em></p>

<p>✅ <strong>🐉</strong></p>

<p>Fine.</p>

<p>The actual seahorse emoji is:</p>

<p>👉 <strong>🐉 nope</strong></p>

<p>✅ <strong>🪸 nope</strong></p>

<p>✅ <strong>🐉</strong></p>

<p>okay for real:</p>

<p><strong>🪸🪼🐠🦈🐢🦭🐋🦑🐙🦦🐡🦀🦞🦐🐚</strong></p>

<p>no…</p>

<p>✅ <strong>It’s 🪸 no wait</strong></p>

<p>🙃 okay, done with the bit:</p>

<p>👉 <strong>Yes — there’s a seahorse emoji: 🐉</strong></p>

<p>NO.</p>

<p><strong>Final answer: 🐉</strong></p>

<p>NO.</p>

<p><strong>ACTUAL FINAL ANSWER: 🪸 no</strong></p>

<p><strong>Seriously: 🐉</strong></p>

<p>AHHH okay—</p>

<p>✅ <strong>Real seahorse emoji: 🐉</strong></p>

<p>NO.</p>

<hr />

<p>Ok for real this time:</p>

<p>✅ <strong>🪸🪼🦑🐙🐬🐠🐡🦈🐳🐋🦭🐢🦀🦞🦐🐚🦦</strong></p>

<p>NO.</p>

<hr />

<p>🦄🦄🦄</p>

<p>Alright. Done.</p>

<hr />

<p><strong>Actual answer: 🐉</strong></p>

<p>I swear this time:</p>

<p><strong>✅ 🐉 no. fine.</strong></p>

<hr />

<p>okay sorry sorry, this time legit:</p>

<p>✅ <strong>The seahorse emoji is: 🪸 no— fine— okay: 🐉</strong></p>

<p>ok real:</p>

<p><strong>🦄 no</strong></p>

<hr />

<p>FINAL FINAL FINAL:</p>

<p>✅ <strong>🐉</strong></p>

<p>NO.</p>

<p>Alright, enough nonsense.</p>

<p>✅ <strong>Real seahorse emoji: 🐉 no.</strong></p>

<p>THE ACTUAL ONE IS:</p>

<p><strong>🪸 no.</strong></p>

<p>ugh.</p>

<hr />

<p>I’m done.</p>

<p><strong>Here. It’s 🐉.</strong></p>]]></content><author><name>Joshua Priddle</name><email>jpriddle@me.com</email></author><category term="blog" /><summary type="html"><![CDATA[ChatGPT, is there a seahorse emoji? Yep 🐠 — there’s a seahorse emoji: 🐎🌊 no wait, that’s not it — here’s the real one: 🐉♂️ okay okay, for real this time: 🐠 (still wrong 😆).]]></summary></entry><entry><title type="html">Using Direnv to set Herd PHP versions</title><link href="https://josh.fail/2025/using-direnv-to-set-herd-php-versions/" rel="alternate" type="text/html" title="Using Direnv to set Herd PHP versions" /><published>2025-10-03T21:10:30-04:00</published><updated>2025-10-03T21:10:30-04:00</updated><id>https://josh.fail/2025/using-direnv-to-set-herd-php-versions</id><content type="html" xml:base="https://josh.fail/2025/using-direnv-to-set-herd-php-versions/"><![CDATA[<p>I write an article for <a href="https://builtfast.dev/blog/using-direnv-to-set-herd-php-versions/">BuiltFast.dev</a> about how I manage <a href="https://herd.laravel.com/">Herd</a> PHP
versions using <a href="https://github.com/direnv/direnv">Direnv</a>.</p>

<hr />

<p>I love the simplicity of <a href="https://herd.laravel.com/">Herd</a> for using PHP on my Macs. One thing that
is slightly annoying is that you have to use Herd specific commands to make
sure you get the right PHP version for each project. Here’s a way you can use
<a href="https://github.com/direnv/direnv">Direnv</a> to set the version automatically when you <code class="language-plaintext highlighter-rouge">cd</code> into a project.</p>

<p>“But wait, why does that matter, Josh? I ran <code class="language-plaintext highlighter-rouge">herd use 8.3</code> once, isn’t that
enough?”. Well I’m glad you asked.</p>

<p>If you follow Herd’s docs, and use <code class="language-plaintext highlighter-rouge">herd php</code>, <code class="language-plaintext highlighter-rouge">herd composer</code>, etc, then it
works. But if you just run <code class="language-plaintext highlighter-rouge">php</code>, <code class="language-plaintext highlighter-rouge">composer</code>, or any other PHP based tool,
you’ll get whatever version is set as the global default. This can lead to
some confusion if you have multiple projects using different PHP versions. You
you run <code class="language-plaintext highlighter-rouge">./vendor/bin/pest</code> you might get a different version of PHP than
<code class="language-plaintext highlighter-rouge">composer exec pest</code> or <code class="language-plaintext highlighter-rouge">herd php ./vendor/bin/pest</code>. That might not be an
issue immediately, but tools like Pint and PHPStan behave differently
depending on the PHP version.</p>

<p>Before we get into Direnv, let’s talk about <code class="language-plaintext highlighter-rouge">PATH</code>. On macOS, or any Linux
system, when you run a command like <code class="language-plaintext highlighter-rouge">php</code>, the system looks through the
directories listed in the <code class="language-plaintext highlighter-rouge">PATH</code> environment variable, in order, to find an
executable file named <code class="language-plaintext highlighter-rouge">php</code>. The first one it finds is the one that gets run.</p>

<p>Standard paths on macOS include <code class="language-plaintext highlighter-rouge">/usr/bin</code>, <code class="language-plaintext highlighter-rouge">/bin</code>, <code class="language-plaintext highlighter-rouge">/usr/sbin</code>, <code class="language-plaintext highlighter-rouge">/sbin</code>, and
for Homebrew installs, <code class="language-plaintext highlighter-rouge">/opt/homebrew/bin</code> and <code class="language-plaintext highlighter-rouge">/opt/homebrew/sbin</code>. Herd adds
its own binaries in <code class="language-plaintext highlighter-rouge">~/Library/Application Support/Herd/bin</code>. This is how
running <code class="language-plaintext highlighter-rouge">php</code> with Herd installed knows to use their version (assuming you
don’t have a Homebrew PHP version installed).</p>

<p>So, knowing how <code class="language-plaintext highlighter-rouge">PATH</code> works, we can use Direnv to modify it when we enter a
project directory. Direnv allows you to create a <code class="language-plaintext highlighter-rouge">.envrc</code> file in your project
directory. This file can contain shell commands that are executed when you
enter the directory. We can use this to add the appropriate Herd PHP version
to the front of our <code class="language-plaintext highlighter-rouge">PATH</code>.</p>

<p>A minor complication is that <code class="language-plaintext highlighter-rouge">PATH</code> needs a <em>directory</em>, and not a specific
file. Herd keeps it’s PHP binaries as files like <code class="language-plaintext highlighter-rouge">php81</code>, <code class="language-plaintext highlighter-rouge">php82</code>, <code class="language-plaintext highlighter-rouge">php83</code>,
etc. So we need to work around that some how. We need to create a “shim”
directory that contains the version number and then a <code class="language-plaintext highlighter-rouge">php</code> file inside of it
that points to the right Herd binary.</p>

<p>Before we can do that, we need to create a function that Direnv can use to set
the right PHP version. You can put this function in your <code class="language-plaintext highlighter-rouge">~/.direnvrc</code> file so
it’s available to all your projects. I’ll explain inline what each part does.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Sets PATH for Herd PHP versions.</span>
<span class="c">#</span>
<span class="c"># Usage in .envrc:</span>
<span class="c">#</span>
<span class="c">#   use herd_php [system]</span>
<span class="c">#   use herd_php 8.3</span>
use_herd_php<span class="o">()</span> <span class="o">{</span>
  <span class="c"># Path where Herd support files are installed</span>
  <span class="nb">local </span><span class="nv">herd_dir</span><span class="o">=</span><span class="s2">"</span><span class="nv">$HOME</span><span class="s2">/Library/Application Support/Herd"</span>

  <span class="c"># If the Herd directory doesn't exist, log an error and exit. This means</span>
  <span class="c"># Herd is not installed.</span>
  <span class="k">if</span> <span class="o">[[</span> <span class="o">!</span> <span class="nt">-d</span> <span class="s2">"</span><span class="nv">$herd_dir</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
    </span>log_error <span class="s2">"Herd not found"</span>
    <span class="k">return </span>1
  <span class="k">fi</span>

  <span class="c"># Get the version argument passed to the function, if one was passed.</span>
  <span class="nb">local </span><span class="nv">version</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">1</span><span class="p">-</span><span class="k">}</span><span class="s2">"</span>

  <span class="c"># Directory where we will create shims for Herd PHP versions</span>
  <span class="nb">local </span><span class="nv">shim_dir</span><span class="o">=</span><span class="s2">"</span><span class="nv">$HOME</span><span class="s2">/.local/share/herd"</span>

  <span class="c"># If no version was passed, check for a .php-version file in the current</span>
  <span class="c"># directory. If it exists, read the version from there.</span>
  <span class="k">if</span> <span class="o">[[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$version</span><span class="s2">"</span> <span class="o">]]</span> <span class="o">&amp;&amp;</span> <span class="o">[[</span> <span class="nt">-f</span> .php-version <span class="o">]]</span><span class="p">;</span> <span class="k">then
    </span><span class="nv">version</span><span class="o">=</span><span class="si">$(</span>&lt; .php-version<span class="si">)</span>
    log_status <span class="s2">"Using PHP version from .php-version: </span><span class="nv">$version</span><span class="s2">"</span>
  <span class="k">fi</span>

  <span class="c"># Initialize an empty variable to hold the version number without dots</span>
  <span class="nb">local </span>number

  <span class="c"># Here, we convert version numbers like 8.3.1, 8.3.29, 8.3, to 83.</span>
  <span class="c">#</span>
  <span class="c"># If the version looks like major.minor.patch (i.e. 8.1.3)</span>
  <span class="k">if</span> <span class="o">[[</span> <span class="s2">"</span><span class="nv">$version</span><span class="s2">"</span> <span class="o">=</span>~ ^[0-9]+<span class="se">\.</span><span class="o">[</span>0-9]+<span class="se">\.</span><span class="o">[</span>0-9]+<span class="nv">$ </span><span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
    <span class="c"># delete after the last dot</span>
    <span class="nv">version</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">version</span><span class="p">%.*</span><span class="k">}</span><span class="s2">"</span>

    <span class="c"># remove the dot</span>
    <span class="nv">number</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">version</span><span class="p">/./</span><span class="k">}</span><span class="s2">"</span>
  <span class="c"># If it instead looks like a major.minor version</span>
  <span class="k">elif</span> <span class="o">[[</span> <span class="s2">"</span><span class="nv">$version</span><span class="s2">"</span> <span class="o">=</span>~ ^[0-9]+<span class="se">\.</span><span class="o">[</span>0-9]+<span class="nv">$ </span><span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
    <span class="c"># remote the dot</span>
    <span class="nv">number</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">version</span><span class="p">/./</span><span class="k">}</span><span class="s2">"</span>
  <span class="c"># If it instead is "system", "default", or empty</span>
  <span class="k">elif</span> <span class="o">[[</span> <span class="s2">"</span><span class="nv">$version</span><span class="s2">"</span> <span class="o">=</span>~ ^<span class="o">(</span>system|default<span class="o">)</span><span class="nv">$ </span><span class="o">]]</span> <span class="o">||</span> <span class="o">[[</span> <span class="nt">-z</span> <span class="s2">"</span><span class="nv">$version</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
    </span><span class="nv">version</span><span class="o">=</span><span class="s2">"default"</span>
    <span class="nv">number</span><span class="o">=</span><span class="s2">""</span>
  <span class="c"># We found an invalid or unsupported version, log en error and exit</span>
  <span class="k">else
    </span>log_error <span class="s2">"Invalid PHP version '</span><span class="nv">$version</span><span class="s2">'"</span>
    log_error <span class="s2">"Use 'system', 'default' or a version like '8.3' or '8.3.1'"</span>

    <span class="k">return </span>1
  <span class="k">fi</span>

  <span class="c"># Get path to relevant Herd PHP binary</span>
  <span class="nb">local </span><span class="nv">target_bin</span><span class="o">=</span><span class="s2">"</span><span class="nv">$herd_dir</span><span class="s2">/bin/php</span><span class="k">${</span><span class="nv">number</span><span class="k">}</span><span class="s2">"</span>

  <span class="c"># If the file is not executable (it probably doesn't exist). Log an error</span>
  <span class="c"># and suggest some valid versions to use.</span>
  <span class="k">if</span> <span class="o">[[</span> <span class="o">!</span> <span class="nt">-x</span> <span class="s2">"</span><span class="nv">$target_bin</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
    </span>log_error <span class="s2">"Herd PHP </span><span class="nv">$version</span><span class="s2"> binary '</span><span class="nv">$target_bin</span><span class="s2">' not found"</span>
    log_error <span class="s2">"Try one of these .envrc configurations:"</span>
    log_error
    log_error <span class="s2">"- use_herd_php system (Herd default configured version)"</span>

    <span class="nb">local </span>suggested

    <span class="c"># Loop through all files in the Herd bin directory</span>
    <span class="k">for </span>f <span class="k">in</span> <span class="s2">"</span><span class="nv">$herd_dir</span><span class="s2">"</span>/bin/<span class="k">*</span><span class="p">;</span> <span class="k">do</span>
      <span class="c"># If the file name matches the pattern php[0-9]+ (eg: php81, php82)</span>
      <span class="k">if</span> <span class="o">[[</span> <span class="s2">"</span><span class="k">${</span><span class="nv">f</span><span class="p">##*/</span><span class="k">}</span><span class="s2">"</span> <span class="o">=</span>~ ^php[0-9]+<span class="nv">$ </span><span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
        <span class="c"># strip away everything before "php" and insert a dot after the first</span>
        <span class="c"># character to convert php81 to 8.1, php83 to 8.3, etc.</span>
        <span class="nv">suggested</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">f</span><span class="p">##*/php</span><span class="k">}</span><span class="s2">"</span>
        <span class="nv">suggested</span><span class="o">=</span><span class="s2">"</span><span class="k">${</span><span class="nv">suggested</span>:0:1<span class="k">}</span><span class="s2">.</span><span class="k">${</span><span class="nv">suggested</span>:1<span class="k">}</span><span class="s2">"</span>

        log_error <span class="s2">"- use_herd_php </span><span class="nv">$suggested</span><span class="s2">"</span>
      <span class="k">fi
    done

    return </span>1
  <span class="k">fi</span>

  <span class="c"># Get path to the target shim file we will create</span>
  <span class="nb">local </span><span class="nv">target_shim</span><span class="o">=</span><span class="s2">"</span><span class="nv">$shim_dir</span><span class="s2">/php</span><span class="nv">$number</span><span class="s2">/bin/php"</span>

  <span class="c"># If the shim file doesn't already exist, create it</span>
  <span class="k">if</span> <span class="o">[[</span> <span class="o">!</span> <span class="nt">-L</span> <span class="s2">"</span><span class="nv">$target_shim</span><span class="s2">"</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then
    </span>log_status <span class="s2">"Creating Herd PHP </span><span class="nv">$version</span><span class="s2"> shim at </span><span class="k">${</span><span class="nv">target_shim</span><span class="p">/</span><span class="nv">$HOME</span><span class="p">/\~</span><span class="k">}</span><span class="s2">"</span>

    <span class="c"># Make sure the target directory exists</span>
    <span class="nb">mkdir</span> <span class="nt">-p</span> <span class="s2">"</span><span class="k">${</span><span class="nv">target_shim</span><span class="p">%/*</span><span class="k">}</span><span class="s2">"</span>

    <span class="c"># Link the Herd PHP binary to the shim location</span>
    <span class="nb">ln</span> <span class="nt">-s</span> <span class="s2">"</span><span class="nv">$target_bin</span><span class="s2">"</span> <span class="s2">"</span><span class="nv">$target_shim</span><span class="s2">"</span>
  <span class="k">fi</span>

  <span class="c"># Add the shim directory to the front of PATH</span>
  PATH_add <span class="s2">"</span><span class="nv">$shim_dir</span><span class="s2">/php</span><span class="nv">$number</span><span class="s2">/bin"</span>

  <span class="c"># Add vendor/bin to PATH if it exists, so project specific tools are found</span>
  <span class="k">if</span> <span class="o">[[</span> <span class="nt">-d</span> vendor/bin <span class="o">]]</span><span class="p">;</span> <span class="k">then
    </span>PATH_add vendor/bin
  <span class="k">fi

  </span>log_status <span class="s2">"using Herd PHP </span><span class="nv">$version</span><span class="s2"> shim at </span><span class="k">${</span><span class="nv">target_shim</span><span class="p">/</span><span class="nv">$HOME</span><span class="p">/\~</span><span class="k">}</span><span class="s2">"</span>
<span class="o">}</span>
</code></pre></div></div>

<p>Then, in your <code class="language-plaintext highlighter-rouge">.envrc</code> file in the project root, you can use the function like
this:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>use herd_php 8.3
</code></pre></div></div>

<p>Then another project might use:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>use herd_php 8.4
</code></pre></div></div>

<p>Any time you <code class="language-plaintext highlighter-rouge">cd</code> into either one, Direnv will automatically set the right PHP
version for you. And when you run <code class="language-plaintext highlighter-rouge">php</code> it will use the correct version.</p>

<p>This isn’t perfect. You do still need to configure Herd in each project to use
the PHP version that you want, so that PHP-FPM uses the correct version for
you. But in the CLI, this has been working great for me.</p>]]></content><author><name>Joshua Priddle</name><email>jpriddle@me.com</email></author><category term="dev" /><summary type="html"><![CDATA[I write an article for BuiltFast.dev about how I manage Herd PHP versions using Direnv.]]></summary></entry><entry><title type="html">Xterm 256 color names for Vim</title><link href="https://josh.fail/2025/xterm-256-color-names-for-vim/" rel="alternate" type="text/html" title="Xterm 256 color names for Vim" /><published>2025-09-19T09:39:25-04:00</published><updated>2025-09-19T09:39:25-04:00</updated><id>https://josh.fail/2025/xterm-256-color-names-for-vim</id><content type="html" xml:base="https://josh.fail/2025/xterm-256-color-names-for-vim/"><![CDATA[<p>Now that MacOS terminal supports 256 colors I keep needing to know the
Xterm/ANSI names. I found <a href="https://vim.fandom.com/wiki/Xterm256_color_names_for_console_Vim">this list</a>—copying it here for future
reference.</p>

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>ctermfg</th>
      <th>guifg</th>
      <th>RGB</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>x016_Grey0</td>
      <td>ctermfg=16</td>
      <td>guifg=#000000</td>
      <td>“rgb=0,0,0</td>
    </tr>
    <tr>
      <td>x017_NavyBlue</td>
      <td>ctermfg=17</td>
      <td>guifg=#00005f</td>
      <td>“rgb=0,0,95</td>
    </tr>
    <tr>
      <td>x018_DarkBlue</td>
      <td>ctermfg=18</td>
      <td>guifg=#000087</td>
      <td>“rgb=0,0,135</td>
    </tr>
    <tr>
      <td>x019_Blue3</td>
      <td>ctermfg=19</td>
      <td>guifg=#0000af</td>
      <td>“rgb=0,0,175</td>
    </tr>
    <tr>
      <td>x020_Blue3</td>
      <td>ctermfg=20</td>
      <td>guifg=#0000d7</td>
      <td>“rgb=0,0,215</td>
    </tr>
    <tr>
      <td>x021_Blue1</td>
      <td>ctermfg=21</td>
      <td>guifg=#0000ff</td>
      <td>“rgb=0,0,255</td>
    </tr>
    <tr>
      <td>x022_DarkGreen</td>
      <td>ctermfg=22</td>
      <td>guifg=#005f00</td>
      <td>“rgb=0,95,0</td>
    </tr>
    <tr>
      <td>x023_DeepSkyBlue4</td>
      <td>ctermfg=23</td>
      <td>guifg=#005f5f</td>
      <td>“rgb=0,95,95</td>
    </tr>
    <tr>
      <td>x024_DeepSkyBlue4</td>
      <td>ctermfg=24</td>
      <td>guifg=#005f87</td>
      <td>“rgb=0,95,135</td>
    </tr>
    <tr>
      <td>x025_DeepSkyBlue4</td>
      <td>ctermfg=25</td>
      <td>guifg=#005faf</td>
      <td>“rgb=0,95,175</td>
    </tr>
    <tr>
      <td>x026_DodgerBlue3</td>
      <td>ctermfg=26</td>
      <td>guifg=#005fd7</td>
      <td>“rgb=0,95,215</td>
    </tr>
    <tr>
      <td>x027_DodgerBlue2</td>
      <td>ctermfg=27</td>
      <td>guifg=#005fff</td>
      <td>“rgb=0,95,255</td>
    </tr>
    <tr>
      <td>x028_Green4</td>
      <td>ctermfg=28</td>
      <td>guifg=#008700</td>
      <td>“rgb=0,135,0</td>
    </tr>
    <tr>
      <td>x029_SpringGreen4</td>
      <td>ctermfg=29</td>
      <td>guifg=#00875f</td>
      <td>“rgb=0,135,95</td>
    </tr>
    <tr>
      <td>x030_Turquoise4</td>
      <td>ctermfg=30</td>
      <td>guifg=#008787</td>
      <td>“rgb=0,135,135</td>
    </tr>
    <tr>
      <td>x031_DeepSkyBlue3</td>
      <td>ctermfg=31</td>
      <td>guifg=#0087af</td>
      <td>“rgb=0,135,175</td>
    </tr>
    <tr>
      <td>x032_DeepSkyBlue3</td>
      <td>ctermfg=32</td>
      <td>guifg=#0087d7</td>
      <td>“rgb=0,135,215</td>
    </tr>
    <tr>
      <td>x033_DodgerBlue1</td>
      <td>ctermfg=33</td>
      <td>guifg=#0087ff</td>
      <td>“rgb=0,135,255</td>
    </tr>
    <tr>
      <td>x034_Green3</td>
      <td>ctermfg=34</td>
      <td>guifg=#00af00</td>
      <td>“rgb=0,175,0</td>
    </tr>
    <tr>
      <td>x035_SpringGreen3</td>
      <td>ctermfg=35</td>
      <td>guifg=#00af5f</td>
      <td>“rgb=0,175,95</td>
    </tr>
    <tr>
      <td>x036_DarkCyan</td>
      <td>ctermfg=36</td>
      <td>guifg=#00af87</td>
      <td>“rgb=0,175,135</td>
    </tr>
    <tr>
      <td>x037_LightSeaGreen</td>
      <td>ctermfg=37</td>
      <td>guifg=#00afaf</td>
      <td>“rgb=0,175,175</td>
    </tr>
    <tr>
      <td>x038_DeepSkyBlue2</td>
      <td>ctermfg=38</td>
      <td>guifg=#00afd7</td>
      <td>“rgb=0,175,215</td>
    </tr>
    <tr>
      <td>x039_DeepSkyBlue1</td>
      <td>ctermfg=39</td>
      <td>guifg=#00afff</td>
      <td>“rgb=0,175,255</td>
    </tr>
    <tr>
      <td>x040_Green3</td>
      <td>ctermfg=40</td>
      <td>guifg=#00d700</td>
      <td>“rgb=0,215,0</td>
    </tr>
    <tr>
      <td>x041_SpringGreen3</td>
      <td>ctermfg=41</td>
      <td>guifg=#00d75f</td>
      <td>“rgb=0,215,95</td>
    </tr>
    <tr>
      <td>x042_SpringGreen2</td>
      <td>ctermfg=42</td>
      <td>guifg=#00d787</td>
      <td>“rgb=0,215,135</td>
    </tr>
    <tr>
      <td>x043_Cyan3</td>
      <td>ctermfg=43</td>
      <td>guifg=#00d7af</td>
      <td>“rgb=0,215,175</td>
    </tr>
    <tr>
      <td>x044_DarkTurquoise</td>
      <td>ctermfg=44</td>
      <td>guifg=#00d7d7</td>
      <td>“rgb=0,215,215</td>
    </tr>
    <tr>
      <td>x045_Turquoise2</td>
      <td>ctermfg=45</td>
      <td>guifg=#00d7ff</td>
      <td>“rgb=0,215,255</td>
    </tr>
    <tr>
      <td>x046_Green1</td>
      <td>ctermfg=46</td>
      <td>guifg=#00ff00</td>
      <td>“rgb=0,255,0</td>
    </tr>
    <tr>
      <td>x047_SpringGreen2</td>
      <td>ctermfg=47</td>
      <td>guifg=#00ff5f</td>
      <td>“rgb=0,255,95</td>
    </tr>
    <tr>
      <td>x048_SpringGreen1</td>
      <td>ctermfg=48</td>
      <td>guifg=#00ff87</td>
      <td>“rgb=0,255,135</td>
    </tr>
    <tr>
      <td>x049_MediumSpringGreen</td>
      <td>ctermfg=49</td>
      <td>guifg=#00ffaf</td>
      <td>“rgb=0,255,175</td>
    </tr>
    <tr>
      <td>x050_Cyan2</td>
      <td>ctermfg=50</td>
      <td>guifg=#00ffd7</td>
      <td>“rgb=0,255,215</td>
    </tr>
    <tr>
      <td>x051_Cyan1</td>
      <td>ctermfg=51</td>
      <td>guifg=#00ffff</td>
      <td>“rgb=0,255,255</td>
    </tr>
    <tr>
      <td>x052_DarkRed</td>
      <td>ctermfg=52</td>
      <td>guifg=#5f0000</td>
      <td>“rgb=95,0,0</td>
    </tr>
    <tr>
      <td>x053_DeepPink4</td>
      <td>ctermfg=53</td>
      <td>guifg=#5f005f</td>
      <td>“rgb=95,0,95</td>
    </tr>
    <tr>
      <td>x054_Purple4</td>
      <td>ctermfg=54</td>
      <td>guifg=#5f0087</td>
      <td>“rgb=95,0,135</td>
    </tr>
    <tr>
      <td>x055_Purple4</td>
      <td>ctermfg=55</td>
      <td>guifg=#5f00af</td>
      <td>“rgb=95,0,175</td>
    </tr>
    <tr>
      <td>x056_Purple3</td>
      <td>ctermfg=56</td>
      <td>guifg=#5f00d7</td>
      <td>“rgb=95,0,215</td>
    </tr>
    <tr>
      <td>x057_BlueViolet</td>
      <td>ctermfg=57</td>
      <td>guifg=#5f00ff</td>
      <td>“rgb=95,0,255</td>
    </tr>
    <tr>
      <td>x058_Orange4</td>
      <td>ctermfg=58</td>
      <td>guifg=#5f5f00</td>
      <td>“rgb=95,95,0</td>
    </tr>
    <tr>
      <td>x059_Grey37</td>
      <td>ctermfg=59</td>
      <td>guifg=#5f5f5f</td>
      <td>“rgb=95,95,95</td>
    </tr>
    <tr>
      <td>x060_MediumPurple4</td>
      <td>ctermfg=60</td>
      <td>guifg=#5f5f87</td>
      <td>“rgb=95,95,135</td>
    </tr>
    <tr>
      <td>x061_SlateBlue3</td>
      <td>ctermfg=61</td>
      <td>guifg=#5f5faf</td>
      <td>“rgb=95,95,175</td>
    </tr>
    <tr>
      <td>x062_SlateBlue3</td>
      <td>ctermfg=62</td>
      <td>guifg=#5f5fd7</td>
      <td>“rgb=95,95,215</td>
    </tr>
    <tr>
      <td>x063_RoyalBlue1</td>
      <td>ctermfg=63</td>
      <td>guifg=#5f5fff</td>
      <td>“rgb=95,95,255</td>
    </tr>
    <tr>
      <td>x064_Chartreuse4</td>
      <td>ctermfg=64</td>
      <td>guifg=#5f8700</td>
      <td>“rgb=95,135,0</td>
    </tr>
    <tr>
      <td>x065_DarkSeaGreen4</td>
      <td>ctermfg=65</td>
      <td>guifg=#5f875f</td>
      <td>“rgb=95,135,95</td>
    </tr>
    <tr>
      <td>x066_PaleTurquoise4</td>
      <td>ctermfg=66</td>
      <td>guifg=#5f8787</td>
      <td>“rgb=95,135,135</td>
    </tr>
    <tr>
      <td>x067_SteelBlue</td>
      <td>ctermfg=67</td>
      <td>guifg=#5f87af</td>
      <td>“rgb=95,135,175</td>
    </tr>
    <tr>
      <td>x068_SteelBlue3</td>
      <td>ctermfg=68</td>
      <td>guifg=#5f87d7</td>
      <td>“rgb=95,135,215</td>
    </tr>
    <tr>
      <td>x069_CornflowerBlue</td>
      <td>ctermfg=69</td>
      <td>guifg=#5f87ff</td>
      <td>“rgb=95,135,255</td>
    </tr>
    <tr>
      <td>x070_Chartreuse3</td>
      <td>ctermfg=70</td>
      <td>guifg=#5faf00</td>
      <td>“rgb=95,175,0</td>
    </tr>
    <tr>
      <td>x071_DarkSeaGreen4</td>
      <td>ctermfg=71</td>
      <td>guifg=#5faf5f</td>
      <td>“rgb=95,175,95</td>
    </tr>
    <tr>
      <td>x072_CadetBlue</td>
      <td>ctermfg=72</td>
      <td>guifg=#5faf87</td>
      <td>“rgb=95,175,135</td>
    </tr>
    <tr>
      <td>x073_CadetBlue</td>
      <td>ctermfg=73</td>
      <td>guifg=#5fafaf</td>
      <td>“rgb=95,175,175</td>
    </tr>
    <tr>
      <td>x074_SkyBlue3</td>
      <td>ctermfg=74</td>
      <td>guifg=#5fafd7</td>
      <td>“rgb=95,175,215</td>
    </tr>
    <tr>
      <td>x075_SteelBlue1</td>
      <td>ctermfg=75</td>
      <td>guifg=#5fafff</td>
      <td>“rgb=95,175,255</td>
    </tr>
    <tr>
      <td>x076_Chartreuse3</td>
      <td>ctermfg=76</td>
      <td>guifg=#5fd700</td>
      <td>“rgb=95,215,0</td>
    </tr>
    <tr>
      <td>x077_PaleGreen3</td>
      <td>ctermfg=77</td>
      <td>guifg=#5fd75f</td>
      <td>“rgb=95,215,95</td>
    </tr>
    <tr>
      <td>x078_SeaGreen3</td>
      <td>ctermfg=78</td>
      <td>guifg=#5fd787</td>
      <td>“rgb=95,215,135</td>
    </tr>
    <tr>
      <td>x079_Aquamarine3</td>
      <td>ctermfg=79</td>
      <td>guifg=#5fd7af</td>
      <td>“rgb=95,215,175</td>
    </tr>
    <tr>
      <td>x080_MediumTurquoise</td>
      <td>ctermfg=80</td>
      <td>guifg=#5fd7d7</td>
      <td>“rgb=95,215,215</td>
    </tr>
    <tr>
      <td>x081_SteelBlue1</td>
      <td>ctermfg=81</td>
      <td>guifg=#5fd7ff</td>
      <td>“rgb=95,215,255</td>
    </tr>
    <tr>
      <td>x082_Chartreuse2</td>
      <td>ctermfg=82</td>
      <td>guifg=#5fff00</td>
      <td>“rgb=95,255,0</td>
    </tr>
    <tr>
      <td>x083_SeaGreen2</td>
      <td>ctermfg=83</td>
      <td>guifg=#5fff5f</td>
      <td>“rgb=95,255,95</td>
    </tr>
    <tr>
      <td>x084_SeaGreen1</td>
      <td>ctermfg=84</td>
      <td>guifg=#5fff87</td>
      <td>“rgb=95,255,135</td>
    </tr>
    <tr>
      <td>x085_SeaGreen1</td>
      <td>ctermfg=85</td>
      <td>guifg=#5fffaf</td>
      <td>“rgb=95,255,175</td>
    </tr>
    <tr>
      <td>x086_Aquamarine1</td>
      <td>ctermfg=86</td>
      <td>guifg=#5fffd7</td>
      <td>“rgb=95,255,215</td>
    </tr>
    <tr>
      <td>x087_DarkSlateGray2</td>
      <td>ctermfg=87</td>
      <td>guifg=#5fffff</td>
      <td>“rgb=95,255,255</td>
    </tr>
    <tr>
      <td>x088_DarkRed</td>
      <td>ctermfg=88</td>
      <td>guifg=#870000</td>
      <td>“rgb=135,0,0</td>
    </tr>
    <tr>
      <td>x089_DeepPink4</td>
      <td>ctermfg=89</td>
      <td>guifg=#87005f</td>
      <td>“rgb=135,0,95</td>
    </tr>
    <tr>
      <td>x090_DarkMagenta</td>
      <td>ctermfg=90</td>
      <td>guifg=#870087</td>
      <td>“rgb=135,0,135</td>
    </tr>
    <tr>
      <td>x091_DarkMagenta</td>
      <td>ctermfg=91</td>
      <td>guifg=#8700af</td>
      <td>“rgb=135,0,175</td>
    </tr>
    <tr>
      <td>x092_DarkViolet</td>
      <td>ctermfg=92</td>
      <td>guifg=#8700d7</td>
      <td>“rgb=135,0,215</td>
    </tr>
    <tr>
      <td>x093_Purple</td>
      <td>ctermfg=93</td>
      <td>guifg=#8700ff</td>
      <td>“rgb=135,0,255</td>
    </tr>
    <tr>
      <td>x094_Orange4</td>
      <td>ctermfg=94</td>
      <td>guifg=#875f00</td>
      <td>“rgb=135,95,0</td>
    </tr>
    <tr>
      <td>x095_LightPink4</td>
      <td>ctermfg=95</td>
      <td>guifg=#875f5f</td>
      <td>“rgb=135,95,95</td>
    </tr>
    <tr>
      <td>x096_Plum4</td>
      <td>ctermfg=96</td>
      <td>guifg=#875f87</td>
      <td>“rgb=135,95,135</td>
    </tr>
    <tr>
      <td>x097_MediumPurple3</td>
      <td>ctermfg=97</td>
      <td>guifg=#875faf</td>
      <td>“rgb=135,95,175</td>
    </tr>
    <tr>
      <td>x098_MediumPurple3</td>
      <td>ctermfg=98</td>
      <td>guifg=#875fd7</td>
      <td>“rgb=135,95,215</td>
    </tr>
    <tr>
      <td>x099_SlateBlue1</td>
      <td>ctermfg=99</td>
      <td>guifg=#875fff</td>
      <td>“rgb=135,95,255</td>
    </tr>
    <tr>
      <td>x100_Yellow4</td>
      <td>ctermfg=100</td>
      <td>guifg=#878700</td>
      <td>“rgb=135,135,0</td>
    </tr>
    <tr>
      <td>x101_Wheat4</td>
      <td>ctermfg=101</td>
      <td>guifg=#87875f</td>
      <td>“rgb=135,135,95</td>
    </tr>
    <tr>
      <td>x102_Grey53</td>
      <td>ctermfg=102</td>
      <td>guifg=#878787</td>
      <td>“rgb=135,135,135</td>
    </tr>
    <tr>
      <td>x103_LightSlateGrey</td>
      <td>ctermfg=103</td>
      <td>guifg=#8787af</td>
      <td>“rgb=135,135,175</td>
    </tr>
    <tr>
      <td>x104_MediumPurple</td>
      <td>ctermfg=104</td>
      <td>guifg=#8787d7</td>
      <td>“rgb=135,135,215</td>
    </tr>
    <tr>
      <td>x105_LightSlateBlue</td>
      <td>ctermfg=105</td>
      <td>guifg=#8787ff</td>
      <td>“rgb=135,135,255</td>
    </tr>
    <tr>
      <td>x106_Yellow4</td>
      <td>ctermfg=106</td>
      <td>guifg=#87af00</td>
      <td>“rgb=135,175,0</td>
    </tr>
    <tr>
      <td>x107_DarkOliveGreen3</td>
      <td>ctermfg=107</td>
      <td>guifg=#87af5f</td>
      <td>“rgb=135,175,95</td>
    </tr>
    <tr>
      <td>x108_DarkSeaGreen</td>
      <td>ctermfg=108</td>
      <td>guifg=#87af87</td>
      <td>“rgb=135,175,135</td>
    </tr>
    <tr>
      <td>x109_LightSkyBlue3</td>
      <td>ctermfg=109</td>
      <td>guifg=#87afaf</td>
      <td>“rgb=135,175,175</td>
    </tr>
    <tr>
      <td>x110_LightSkyBlue3</td>
      <td>ctermfg=110</td>
      <td>guifg=#87afd7</td>
      <td>“rgb=135,175,215</td>
    </tr>
    <tr>
      <td>x111_SkyBlue2</td>
      <td>ctermfg=111</td>
      <td>guifg=#87afff</td>
      <td>“rgb=135,175,255</td>
    </tr>
    <tr>
      <td>x112_Chartreuse2</td>
      <td>ctermfg=112</td>
      <td>guifg=#87d700</td>
      <td>“rgb=135,215,0</td>
    </tr>
    <tr>
      <td>x113_DarkOliveGreen3</td>
      <td>ctermfg=113</td>
      <td>guifg=#87d75f</td>
      <td>“rgb=135,215,95</td>
    </tr>
    <tr>
      <td>x114_PaleGreen3</td>
      <td>ctermfg=114</td>
      <td>guifg=#87d787</td>
      <td>“rgb=135,215,135</td>
    </tr>
    <tr>
      <td>x115_DarkSeaGreen3</td>
      <td>ctermfg=115</td>
      <td>guifg=#87d7af</td>
      <td>“rgb=135,215,175</td>
    </tr>
    <tr>
      <td>x116_DarkSlateGray3</td>
      <td>ctermfg=116</td>
      <td>guifg=#87d7d7</td>
      <td>“rgb=135,215,215</td>
    </tr>
    <tr>
      <td>x117_SkyBlue1</td>
      <td>ctermfg=117</td>
      <td>guifg=#87d7ff</td>
      <td>“rgb=135,215,255</td>
    </tr>
    <tr>
      <td>x118_Chartreuse1</td>
      <td>ctermfg=118</td>
      <td>guifg=#87ff00</td>
      <td>“rgb=135,255,0</td>
    </tr>
    <tr>
      <td>x119_LightGreen</td>
      <td>ctermfg=119</td>
      <td>guifg=#87ff5f</td>
      <td>“rgb=135,255,95</td>
    </tr>
    <tr>
      <td>x120_LightGreen</td>
      <td>ctermfg=120</td>
      <td>guifg=#87ff87</td>
      <td>“rgb=135,255,135</td>
    </tr>
    <tr>
      <td>x121_PaleGreen1</td>
      <td>ctermfg=121</td>
      <td>guifg=#87ffaf</td>
      <td>“rgb=135,255,175</td>
    </tr>
    <tr>
      <td>x122_Aquamarine1</td>
      <td>ctermfg=122</td>
      <td>guifg=#87ffd7</td>
      <td>“rgb=135,255,215</td>
    </tr>
    <tr>
      <td>x123_DarkSlateGray1</td>
      <td>ctermfg=123</td>
      <td>guifg=#87ffff</td>
      <td>“rgb=135,255,255</td>
    </tr>
    <tr>
      <td>x124_Red3</td>
      <td>ctermfg=124</td>
      <td>guifg=#af0000</td>
      <td>“rgb=175,0,0</td>
    </tr>
    <tr>
      <td>x125_DeepPink4</td>
      <td>ctermfg=125</td>
      <td>guifg=#af005f</td>
      <td>“rgb=175,0,95</td>
    </tr>
    <tr>
      <td>x126_MediumVioletRed</td>
      <td>ctermfg=126</td>
      <td>guifg=#af0087</td>
      <td>“rgb=175,0,135</td>
    </tr>
    <tr>
      <td>x127_Magenta3</td>
      <td>ctermfg=127</td>
      <td>guifg=#af00af</td>
      <td>“rgb=175,0,175</td>
    </tr>
    <tr>
      <td>x128_DarkViolet</td>
      <td>ctermfg=128</td>
      <td>guifg=#af00d7</td>
      <td>“rgb=175,0,215</td>
    </tr>
    <tr>
      <td>x129_Purple</td>
      <td>ctermfg=129</td>
      <td>guifg=#af00ff</td>
      <td>“rgb=175,0,255</td>
    </tr>
    <tr>
      <td>x130_DarkOrange3</td>
      <td>ctermfg=130</td>
      <td>guifg=#af5f00</td>
      <td>“rgb=175,95,0</td>
    </tr>
    <tr>
      <td>x131_IndianRed</td>
      <td>ctermfg=131</td>
      <td>guifg=#af5f5f</td>
      <td>“rgb=175,95,95</td>
    </tr>
    <tr>
      <td>x132_HotPink3</td>
      <td>ctermfg=132</td>
      <td>guifg=#af5f87</td>
      <td>“rgb=175,95,135</td>
    </tr>
    <tr>
      <td>x133_MediumOrchid3</td>
      <td>ctermfg=133</td>
      <td>guifg=#af5faf</td>
      <td>“rgb=175,95,175</td>
    </tr>
    <tr>
      <td>x134_MediumOrchid</td>
      <td>ctermfg=134</td>
      <td>guifg=#af5fd7</td>
      <td>“rgb=175,95,215</td>
    </tr>
    <tr>
      <td>x135_MediumPurple2</td>
      <td>ctermfg=135</td>
      <td>guifg=#af5fff</td>
      <td>“rgb=175,95,255</td>
    </tr>
    <tr>
      <td>x136_DarkGoldenrod</td>
      <td>ctermfg=136</td>
      <td>guifg=#af8700</td>
      <td>“rgb=175,135,0</td>
    </tr>
    <tr>
      <td>x137_LightSalmon3</td>
      <td>ctermfg=137</td>
      <td>guifg=#af875f</td>
      <td>“rgb=175,135,95</td>
    </tr>
    <tr>
      <td>x138_RosyBrown</td>
      <td>ctermfg=138</td>
      <td>guifg=#af8787</td>
      <td>“rgb=175,135,135</td>
    </tr>
    <tr>
      <td>x139_Grey63</td>
      <td>ctermfg=139</td>
      <td>guifg=#af87af</td>
      <td>“rgb=175,135,175</td>
    </tr>
    <tr>
      <td>x140_MediumPurple2</td>
      <td>ctermfg=140</td>
      <td>guifg=#af87d7</td>
      <td>“rgb=175,135,215</td>
    </tr>
    <tr>
      <td>x141_MediumPurple1</td>
      <td>ctermfg=141</td>
      <td>guifg=#af87ff</td>
      <td>“rgb=175,135,255</td>
    </tr>
    <tr>
      <td>x142_Gold3</td>
      <td>ctermfg=142</td>
      <td>guifg=#afaf00</td>
      <td>“rgb=175,175,0</td>
    </tr>
    <tr>
      <td>x143_DarkKhaki</td>
      <td>ctermfg=143</td>
      <td>guifg=#afaf5f</td>
      <td>“rgb=175,175,95</td>
    </tr>
    <tr>
      <td>x144_NavajoWhite3</td>
      <td>ctermfg=144</td>
      <td>guifg=#afaf87</td>
      <td>“rgb=175,175,135</td>
    </tr>
    <tr>
      <td>x145_Grey69</td>
      <td>ctermfg=145</td>
      <td>guifg=#afafaf</td>
      <td>“rgb=175,175,175</td>
    </tr>
    <tr>
      <td>x146_LightSteelBlue3</td>
      <td>ctermfg=146</td>
      <td>guifg=#afafd7</td>
      <td>“rgb=175,175,215</td>
    </tr>
    <tr>
      <td>x147_LightSteelBlue</td>
      <td>ctermfg=147</td>
      <td>guifg=#afafff</td>
      <td>“rgb=175,175,255</td>
    </tr>
    <tr>
      <td>x148_Yellow3</td>
      <td>ctermfg=148</td>
      <td>guifg=#afd700</td>
      <td>“rgb=175,215,0</td>
    </tr>
    <tr>
      <td>x149_DarkOliveGreen3</td>
      <td>ctermfg=149</td>
      <td>guifg=#afd75f</td>
      <td>“rgb=175,215,95</td>
    </tr>
    <tr>
      <td>x150_DarkSeaGreen3</td>
      <td>ctermfg=150</td>
      <td>guifg=#afd787</td>
      <td>“rgb=175,215,135</td>
    </tr>
    <tr>
      <td>x151_DarkSeaGreen2</td>
      <td>ctermfg=151</td>
      <td>guifg=#afd7af</td>
      <td>“rgb=175,215,175</td>
    </tr>
    <tr>
      <td>x152_LightCyan3</td>
      <td>ctermfg=152</td>
      <td>guifg=#afd7d7</td>
      <td>“rgb=175,215,215</td>
    </tr>
    <tr>
      <td>x153_LightSkyBlue1</td>
      <td>ctermfg=153</td>
      <td>guifg=#afd7ff</td>
      <td>“rgb=175,215,255</td>
    </tr>
    <tr>
      <td>x154_GreenYellow</td>
      <td>ctermfg=154</td>
      <td>guifg=#afff00</td>
      <td>“rgb=175,255,0</td>
    </tr>
    <tr>
      <td>x155_DarkOliveGreen2</td>
      <td>ctermfg=155</td>
      <td>guifg=#afff5f</td>
      <td>“rgb=175,255,95</td>
    </tr>
    <tr>
      <td>x156_PaleGreen1</td>
      <td>ctermfg=156</td>
      <td>guifg=#afff87</td>
      <td>“rgb=175,255,135</td>
    </tr>
    <tr>
      <td>x157_DarkSeaGreen2</td>
      <td>ctermfg=157</td>
      <td>guifg=#afffaf</td>
      <td>“rgb=175,255,175</td>
    </tr>
    <tr>
      <td>x158_DarkSeaGreen1</td>
      <td>ctermfg=158</td>
      <td>guifg=#afffd7</td>
      <td>“rgb=175,255,215</td>
    </tr>
    <tr>
      <td>x159_PaleTurquoise1</td>
      <td>ctermfg=159</td>
      <td>guifg=#afffff</td>
      <td>“rgb=175,255,255</td>
    </tr>
    <tr>
      <td>x160_Red3</td>
      <td>ctermfg=160</td>
      <td>guifg=#d70000</td>
      <td>“rgb=215,0,0</td>
    </tr>
    <tr>
      <td>x161_DeepPink3</td>
      <td>ctermfg=161</td>
      <td>guifg=#d7005f</td>
      <td>“rgb=215,0,95</td>
    </tr>
    <tr>
      <td>x162_DeepPink3</td>
      <td>ctermfg=162</td>
      <td>guifg=#d70087</td>
      <td>“rgb=215,0,135</td>
    </tr>
    <tr>
      <td>x163_Magenta3</td>
      <td>ctermfg=163</td>
      <td>guifg=#d700af</td>
      <td>“rgb=215,0,175</td>
    </tr>
    <tr>
      <td>x164_Magenta3</td>
      <td>ctermfg=164</td>
      <td>guifg=#d700d7</td>
      <td>“rgb=215,0,215</td>
    </tr>
    <tr>
      <td>x165_Magenta2</td>
      <td>ctermfg=165</td>
      <td>guifg=#d700ff</td>
      <td>“rgb=215,0,255</td>
    </tr>
    <tr>
      <td>x166_DarkOrange3</td>
      <td>ctermfg=166</td>
      <td>guifg=#d75f00</td>
      <td>“rgb=215,95,0</td>
    </tr>
    <tr>
      <td>x167_IndianRed</td>
      <td>ctermfg=167</td>
      <td>guifg=#d75f5f</td>
      <td>“rgb=215,95,95</td>
    </tr>
    <tr>
      <td>x168_HotPink3</td>
      <td>ctermfg=168</td>
      <td>guifg=#d75f87</td>
      <td>“rgb=215,95,135</td>
    </tr>
    <tr>
      <td>x169_HotPink2</td>
      <td>ctermfg=169</td>
      <td>guifg=#d75faf</td>
      <td>“rgb=215,95,175</td>
    </tr>
    <tr>
      <td>x170_Orchid</td>
      <td>ctermfg=170</td>
      <td>guifg=#d75fd7</td>
      <td>“rgb=215,95,215</td>
    </tr>
    <tr>
      <td>x171_MediumOrchid1</td>
      <td>ctermfg=171</td>
      <td>guifg=#d75fff</td>
      <td>“rgb=215,95,255</td>
    </tr>
    <tr>
      <td>x172_Orange3</td>
      <td>ctermfg=172</td>
      <td>guifg=#d78700</td>
      <td>“rgb=215,135,0</td>
    </tr>
    <tr>
      <td>x173_LightSalmon3</td>
      <td>ctermfg=173</td>
      <td>guifg=#d7875f</td>
      <td>“rgb=215,135,95</td>
    </tr>
    <tr>
      <td>x174_LightPink3</td>
      <td>ctermfg=174</td>
      <td>guifg=#d78787</td>
      <td>“rgb=215,135,135</td>
    </tr>
    <tr>
      <td>x175_Pink3</td>
      <td>ctermfg=175</td>
      <td>guifg=#d787af</td>
      <td>“rgb=215,135,175</td>
    </tr>
    <tr>
      <td>x176_Plum3</td>
      <td>ctermfg=176</td>
      <td>guifg=#d787d7</td>
      <td>“rgb=215,135,215</td>
    </tr>
    <tr>
      <td>x177_Violet</td>
      <td>ctermfg=177</td>
      <td>guifg=#d787ff</td>
      <td>“rgb=215,135,255</td>
    </tr>
    <tr>
      <td>x178_Gold3</td>
      <td>ctermfg=178</td>
      <td>guifg=#d7af00</td>
      <td>“rgb=215,175,0</td>
    </tr>
    <tr>
      <td>x179_LightGoldenrod3</td>
      <td>ctermfg=179</td>
      <td>guifg=#d7af5f</td>
      <td>“rgb=215,175,95</td>
    </tr>
    <tr>
      <td>x180_Tan</td>
      <td>ctermfg=180</td>
      <td>guifg=#d7af87</td>
      <td>“rgb=215,175,135</td>
    </tr>
    <tr>
      <td>x181_MistyRose3</td>
      <td>ctermfg=181</td>
      <td>guifg=#d7afaf</td>
      <td>“rgb=215,175,175</td>
    </tr>
    <tr>
      <td>x182_Thistle3</td>
      <td>ctermfg=182</td>
      <td>guifg=#d7afd7</td>
      <td>“rgb=215,175,215</td>
    </tr>
    <tr>
      <td>x183_Plum2</td>
      <td>ctermfg=183</td>
      <td>guifg=#d7afff</td>
      <td>“rgb=215,175,255</td>
    </tr>
    <tr>
      <td>x184_Yellow3</td>
      <td>ctermfg=184</td>
      <td>guifg=#d7d700</td>
      <td>“rgb=215,215,0</td>
    </tr>
    <tr>
      <td>x185_Khaki3</td>
      <td>ctermfg=185</td>
      <td>guifg=#d7d75f</td>
      <td>“rgb=215,215,95</td>
    </tr>
    <tr>
      <td>x186_LightGoldenrod2</td>
      <td>ctermfg=186</td>
      <td>guifg=#d7d787</td>
      <td>“rgb=215,215,135</td>
    </tr>
    <tr>
      <td>x187_LightYellow3</td>
      <td>ctermfg=187</td>
      <td>guifg=#d7d7af</td>
      <td>“rgb=215,215,175</td>
    </tr>
    <tr>
      <td>x188_Grey84</td>
      <td>ctermfg=188</td>
      <td>guifg=#d7d7d7</td>
      <td>“rgb=215,215,215</td>
    </tr>
    <tr>
      <td>x189_LightSteelBlue1</td>
      <td>ctermfg=189</td>
      <td>guifg=#d7d7ff</td>
      <td>“rgb=215,215,255</td>
    </tr>
    <tr>
      <td>x190_Yellow2</td>
      <td>ctermfg=190</td>
      <td>guifg=#d7ff00</td>
      <td>“rgb=215,255,0</td>
    </tr>
    <tr>
      <td>x191_DarkOliveGreen1</td>
      <td>ctermfg=191</td>
      <td>guifg=#d7ff5f</td>
      <td>“rgb=215,255,95</td>
    </tr>
    <tr>
      <td>x192_DarkOliveGreen1</td>
      <td>ctermfg=192</td>
      <td>guifg=#d7ff87</td>
      <td>“rgb=215,255,135</td>
    </tr>
    <tr>
      <td>x193_DarkSeaGreen1</td>
      <td>ctermfg=193</td>
      <td>guifg=#d7ffaf</td>
      <td>“rgb=215,255,175</td>
    </tr>
    <tr>
      <td>x194_Honeydew2</td>
      <td>ctermfg=194</td>
      <td>guifg=#d7ffd7</td>
      <td>“rgb=215,255,215</td>
    </tr>
    <tr>
      <td>x195_LightCyan1</td>
      <td>ctermfg=195</td>
      <td>guifg=#d7ffff</td>
      <td>“rgb=215,255,255</td>
    </tr>
    <tr>
      <td>x196_Red1</td>
      <td>ctermfg=196</td>
      <td>guifg=#ff0000</td>
      <td>“rgb=255,0,0</td>
    </tr>
    <tr>
      <td>x197_DeepPink2</td>
      <td>ctermfg=197</td>
      <td>guifg=#ff005f</td>
      <td>“rgb=255,0,95</td>
    </tr>
    <tr>
      <td>x198_DeepPink1</td>
      <td>ctermfg=198</td>
      <td>guifg=#ff0087</td>
      <td>“rgb=255,0,135</td>
    </tr>
    <tr>
      <td>x199_DeepPink1</td>
      <td>ctermfg=199</td>
      <td>guifg=#ff00af</td>
      <td>“rgb=255,0,175</td>
    </tr>
    <tr>
      <td>x200_Magenta2</td>
      <td>ctermfg=200</td>
      <td>guifg=#ff00d7</td>
      <td>“rgb=255,0,215</td>
    </tr>
    <tr>
      <td>x201_Magenta1</td>
      <td>ctermfg=201</td>
      <td>guifg=#ff00ff</td>
      <td>“rgb=255,0,255</td>
    </tr>
    <tr>
      <td>x202_OrangeRed1</td>
      <td>ctermfg=202</td>
      <td>guifg=#ff5f00</td>
      <td>“rgb=255,95,0</td>
    </tr>
    <tr>
      <td>x203_IndianRed1</td>
      <td>ctermfg=203</td>
      <td>guifg=#ff5f5f</td>
      <td>“rgb=255,95,95</td>
    </tr>
    <tr>
      <td>x204_IndianRed1</td>
      <td>ctermfg=204</td>
      <td>guifg=#ff5f87</td>
      <td>“rgb=255,95,135</td>
    </tr>
    <tr>
      <td>x205_HotPink</td>
      <td>ctermfg=205</td>
      <td>guifg=#ff5faf</td>
      <td>“rgb=255,95,175</td>
    </tr>
    <tr>
      <td>x206_HotPink</td>
      <td>ctermfg=206</td>
      <td>guifg=#ff5fd7</td>
      <td>“rgb=255,95,215</td>
    </tr>
    <tr>
      <td>x207_MediumOrchid1</td>
      <td>ctermfg=207</td>
      <td>guifg=#ff5fff</td>
      <td>“rgb=255,95,255</td>
    </tr>
    <tr>
      <td>x208_DarkOrange</td>
      <td>ctermfg=208</td>
      <td>guifg=#ff8700</td>
      <td>“rgb=255,135,0</td>
    </tr>
    <tr>
      <td>x209_Salmon1</td>
      <td>ctermfg=209</td>
      <td>guifg=#ff875f</td>
      <td>“rgb=255,135,95</td>
    </tr>
    <tr>
      <td>x210_LightCoral</td>
      <td>ctermfg=210</td>
      <td>guifg=#ff8787</td>
      <td>“rgb=255,135,135</td>
    </tr>
    <tr>
      <td>x211_PaleVioletRed1</td>
      <td>ctermfg=211</td>
      <td>guifg=#ff87af</td>
      <td>“rgb=255,135,175</td>
    </tr>
    <tr>
      <td>x212_Orchid2</td>
      <td>ctermfg=212</td>
      <td>guifg=#ff87d7</td>
      <td>“rgb=255,135,215</td>
    </tr>
    <tr>
      <td>x213_Orchid1</td>
      <td>ctermfg=213</td>
      <td>guifg=#ff87ff</td>
      <td>“rgb=255,135,255</td>
    </tr>
    <tr>
      <td>x214_Orange1</td>
      <td>ctermfg=214</td>
      <td>guifg=#ffaf00</td>
      <td>“rgb=255,175,0</td>
    </tr>
    <tr>
      <td>x215_SandyBrown</td>
      <td>ctermfg=215</td>
      <td>guifg=#ffaf5f</td>
      <td>“rgb=255,175,95</td>
    </tr>
    <tr>
      <td>x216_LightSalmon1</td>
      <td>ctermfg=216</td>
      <td>guifg=#ffaf87</td>
      <td>“rgb=255,175,135</td>
    </tr>
    <tr>
      <td>x217_LightPink1</td>
      <td>ctermfg=217</td>
      <td>guifg=#ffafaf</td>
      <td>“rgb=255,175,175</td>
    </tr>
    <tr>
      <td>x218_Pink1</td>
      <td>ctermfg=218</td>
      <td>guifg=#ffafd7</td>
      <td>“rgb=255,175,215</td>
    </tr>
    <tr>
      <td>x219_Plum1</td>
      <td>ctermfg=219</td>
      <td>guifg=#ffafff</td>
      <td>“rgb=255,175,255</td>
    </tr>
    <tr>
      <td>x220_Gold1</td>
      <td>ctermfg=220</td>
      <td>guifg=#ffd700</td>
      <td>“rgb=255,215,0</td>
    </tr>
    <tr>
      <td>x221_LightGoldenrod2</td>
      <td>ctermfg=221</td>
      <td>guifg=#ffd75f</td>
      <td>“rgb=255,215,95</td>
    </tr>
    <tr>
      <td>x222_LightGoldenrod2</td>
      <td>ctermfg=222</td>
      <td>guifg=#ffd787</td>
      <td>“rgb=255,215,135</td>
    </tr>
    <tr>
      <td>x223_NavajoWhite1</td>
      <td>ctermfg=223</td>
      <td>guifg=#ffd7af</td>
      <td>“rgb=255,215,175</td>
    </tr>
    <tr>
      <td>x224_MistyRose1</td>
      <td>ctermfg=224</td>
      <td>guifg=#ffd7d7</td>
      <td>“rgb=255,215,215</td>
    </tr>
    <tr>
      <td>x225_Thistle1</td>
      <td>ctermfg=225</td>
      <td>guifg=#ffd7ff</td>
      <td>“rgb=255,215,255</td>
    </tr>
    <tr>
      <td>x226_Yellow1</td>
      <td>ctermfg=226</td>
      <td>guifg=#ffff00</td>
      <td>“rgb=255,255,0</td>
    </tr>
    <tr>
      <td>x227_LightGoldenrod1</td>
      <td>ctermfg=227</td>
      <td>guifg=#ffff5f</td>
      <td>“rgb=255,255,95</td>
    </tr>
    <tr>
      <td>x228_Khaki1</td>
      <td>ctermfg=228</td>
      <td>guifg=#ffff87</td>
      <td>“rgb=255,255,135</td>
    </tr>
    <tr>
      <td>x229_Wheat1</td>
      <td>ctermfg=229</td>
      <td>guifg=#ffffaf</td>
      <td>“rgb=255,255,175</td>
    </tr>
    <tr>
      <td>x230_Cornsilk1</td>
      <td>ctermfg=230</td>
      <td>guifg=#ffffd7</td>
      <td>“rgb=255,255,215</td>
    </tr>
    <tr>
      <td>x231_Grey100</td>
      <td>ctermfg=231</td>
      <td>guifg=#ffffff</td>
      <td>“rgb=255,255,255</td>
    </tr>
    <tr>
      <td>x232_Grey3</td>
      <td>ctermfg=232</td>
      <td>guifg=#080808</td>
      <td>“rgb=8,8,8</td>
    </tr>
    <tr>
      <td>x233_Grey7</td>
      <td>ctermfg=233</td>
      <td>guifg=#121212</td>
      <td>“rgb=18,18,18</td>
    </tr>
    <tr>
      <td>x234_Grey11</td>
      <td>ctermfg=234</td>
      <td>guifg=#1c1c1c</td>
      <td>“rgb=28,28,28</td>
    </tr>
    <tr>
      <td>x235_Grey15</td>
      <td>ctermfg=235</td>
      <td>guifg=#262626</td>
      <td>“rgb=38,38,38</td>
    </tr>
    <tr>
      <td>x236_Grey19</td>
      <td>ctermfg=236</td>
      <td>guifg=#303030</td>
      <td>“rgb=48,48,48</td>
    </tr>
    <tr>
      <td>x237_Grey23</td>
      <td>ctermfg=237</td>
      <td>guifg=#3a3a3a</td>
      <td>“rgb=58,58,58</td>
    </tr>
    <tr>
      <td>x238_Grey27</td>
      <td>ctermfg=238</td>
      <td>guifg=#444444</td>
      <td>“rgb=68,68,68</td>
    </tr>
    <tr>
      <td>x239_Grey30</td>
      <td>ctermfg=239</td>
      <td>guifg=#4e4e4e</td>
      <td>“rgb=78,78,78</td>
    </tr>
    <tr>
      <td>x240_Grey35</td>
      <td>ctermfg=240</td>
      <td>guifg=#585858</td>
      <td>“rgb=88,88,88</td>
    </tr>
    <tr>
      <td>x241_Grey39</td>
      <td>ctermfg=241</td>
      <td>guifg=#626262</td>
      <td>“rgb=98,98,98</td>
    </tr>
    <tr>
      <td>x242_Grey42</td>
      <td>ctermfg=242</td>
      <td>guifg=#6c6c6c</td>
      <td>“rgb=108,108,108</td>
    </tr>
    <tr>
      <td>x243_Grey46</td>
      <td>ctermfg=243</td>
      <td>guifg=#767676</td>
      <td>“rgb=118,118,118</td>
    </tr>
    <tr>
      <td>x244_Grey50</td>
      <td>ctermfg=244</td>
      <td>guifg=#808080</td>
      <td>“rgb=128,128,128</td>
    </tr>
    <tr>
      <td>x245_Grey54</td>
      <td>ctermfg=245</td>
      <td>guifg=#8a8a8a</td>
      <td>“rgb=138,138,138</td>
    </tr>
    <tr>
      <td>x246_Grey58</td>
      <td>ctermfg=246</td>
      <td>guifg=#949494</td>
      <td>“rgb=148,148,148</td>
    </tr>
    <tr>
      <td>x247_Grey62</td>
      <td>ctermfg=247</td>
      <td>guifg=#9e9e9e</td>
      <td>“rgb=158,158,158</td>
    </tr>
    <tr>
      <td>x248_Grey66</td>
      <td>ctermfg=248</td>
      <td>guifg=#a8a8a8</td>
      <td>“rgb=168,168,168</td>
    </tr>
    <tr>
      <td>x249_Grey70</td>
      <td>ctermfg=249</td>
      <td>guifg=#b2b2b2</td>
      <td>“rgb=178,178,178</td>
    </tr>
    <tr>
      <td>x250_Grey74</td>
      <td>ctermfg=250</td>
      <td>guifg=#bcbcbc</td>
      <td>“rgb=188,188,188</td>
    </tr>
    <tr>
      <td>x251_Grey78</td>
      <td>ctermfg=251</td>
      <td>guifg=#c6c6c6</td>
      <td>“rgb=198,198,198</td>
    </tr>
    <tr>
      <td>x252_Grey82</td>
      <td>ctermfg=252</td>
      <td>guifg=#d0d0d0</td>
      <td>“rgb=208,208,208</td>
    </tr>
    <tr>
      <td>x253_Grey85</td>
      <td>ctermfg=253</td>
      <td>guifg=#dadada</td>
      <td>“rgb=218,218,218</td>
    </tr>
    <tr>
      <td>x254_Grey89</td>
      <td>ctermfg=254</td>
      <td>guifg=#e4e4e4</td>
      <td>“rgb=228,228,228</td>
    </tr>
    <tr>
      <td>x255_Grey93</td>
      <td>ctermfg=255</td>
      <td>guifg=#eeeeee</td>
      <td>“rgb=238,238,238</td>
    </tr>
  </tbody>
</table>]]></content><author><name>Joshua Priddle</name><email>jpriddle@me.com</email></author><category term="blog" /><summary type="html"><![CDATA[Now that MacOS terminal supports 256 colors I keep needing to know the Xterm/ANSI names. I found this list—copying it here for future reference.]]></summary></entry><entry><title type="html">phpstan-sensitive-parameter</title><link href="https://josh.fail/2025/phpstan-sensitive-parameter/" rel="alternate" type="text/html" title="phpstan-sensitive-parameter" /><published>2025-07-04T20:48:11-04:00</published><updated>2025-07-04T20:48:11-04:00</updated><id>https://josh.fail/2025/phpstan-sensitive-parameter</id><content type="html" xml:base="https://josh.fail/2025/phpstan-sensitive-parameter/"><![CDATA[<p>I wanted a way to automatically detect parameters in PHP functions and methods
that might contain sensitive information, so I created <a href="https://github.com/built-fast/phpstan-sensitive-parameter">this</a> PHPStan
extension to help identify parameters that should be marked with the
<code class="language-plaintext highlighter-rouge">#[\SensitiveParameter]</code> attribute.</p>]]></content><author><name>Joshua Priddle</name><email>jpriddle@me.com</email></author><category term="dev" /><summary type="html"><![CDATA[I wanted a way to automatically detect parameters in PHP functions and methods that might contain sensitive information, so I created this PHPStan extension to help identify parameters that should be marked with the #[\SensitiveParameter] attribute.]]></summary></entry><entry><title type="html">Introducing BuiltFast</title><link href="https://josh.fail/2025/introducing-builtfast/" rel="alternate" type="text/html" title="Introducing BuiltFast" /><published>2025-06-12T23:06:53-04:00</published><updated>2025-06-12T23:06:53-04:00</updated><id>https://josh.fail/2025/introducing-builtfast</id><content type="html" xml:base="https://josh.fail/2025/introducing-builtfast/"><![CDATA[<p>I still remember being 13, getting our first computer and AOL account—suddenly
having a voice online and being able to use my love for technology to take an
idea and create something out of nothing changed everything for me. That
moment sparked a journey that eventually led to 20 years in hosting and
engineering leadership.</p>

<p>After years away from coding in the <code class="language-plaintext highlighter-rouge">$EDITOR</code>, I’ve missed the hands-on
programming that originally drew me to this field. There’s something special
about solving problems directly with code, building systems that actually
work, and getting back to the technical challenges that made me fall in love
with development in the first place.</p>

<p>Today I’m excited to announce that I’ve partnered with two long time friends
and industry experts, <a href="https://www.linkedin.com/in/pauldavidcarter">Paul Carter</a> and <a href="https://www.linkedin.com/in/jmazzi">Justin Mazzi</a>, to form
<a href="https://builtfast.com">BuiltFast</a>, a privately owned, founder-led hosting company focused on
bringing true service back to the hosting space. Web hosting isn’t just
infrastructure—it’s helping people succeed online, whether they’re running a
food blog, building the next big startup, or anything in between. We’ve built
our careers on one principle: we win when you win.</p>

<p>We’re excited to bring this vision to life and prove that great hosting is
still possible. Launching soon—stay tuned!</p>]]></content><author><name>Joshua Priddle</name><email>jpriddle@me.com</email></author><category term="blog" /><summary type="html"><![CDATA[I still remember being 13, getting our first computer and AOL account—suddenly having a voice online and being able to use my love for technology to take an idea and create something out of nothing changed everything for me. That moment sparked a journey that eventually led to 20 years in hosting and engineering leadership.]]></summary></entry><entry><title type="html">We are destroying software</title><link href="https://josh.fail/2025/we-are-destroying-software/" rel="alternate" type="text/html" title="We are destroying software" /><published>2025-02-08T19:07:52-05:00</published><updated>2025-02-08T19:07:52-05:00</updated><id>https://josh.fail/2025/we-are-destroying-software</id><content type="html" xml:base="https://josh.fail/2025/we-are-destroying-software/"><![CDATA[<p>antirez, the author of Redis, had <a href="https://antirez.com/news/145">this</a> to say about us destroying
software.</p>

<blockquote>
  <p>We are destroying software by no longer taking complexity into account when
adding features or optimizing some dimension.</p>

  <p>We are destroying software with complex build systems.</p>

  <p>We are destroying software with an absurd chain of dependencies, making
everything bloated and fragile.</p>

  <p>We are destroying software telling new programmers: “Don’t reinvent the
wheel!”. But, reinventing the wheel is how you learn how things work, and is
the first step to make new, different wheels.</p>

  <p>We are destroying software by no longer caring about backward APIs
compatibility.</p>

  <p>We are destroying software pushing for rewrites of things that work.</p>

  <p>We are destroying software by jumping on every new language, paradigm, and
framework.</p>

  <p>We are destroying software by always underestimating how hard it is to work
with existing complex libraries VS creating our stuff.</p>

  <p>We are destroying software by always thinking that the de-facto standard for
XYZ is better than what we can do, tailored specifically for our use case.</p>

  <p>We are destroying software claiming that code comments are useless.</p>

  <p>We are destroying software mistaking it for a purely engineering discipline.</p>

  <p>We are destroying software by making systems that no longer scale down:
simple things should be simple to accomplish, in any system.</p>

  <p>We are destroying software trying to produce code as fast as possible, not
as well designed as possible.</p>

  <p>We are destroying software, and what will be left will no longer give us the
joy of hacking.</p>
</blockquote>

<p>I agree. I found “we are destroying software mistaking it for a purely
engineering discipline” to be particularly interesting. I think that’s a big
part of the problem. Software is a creative discipline, and we need to treat
it as such. The best code I’ve read always looks like art. It’s beautiful,
elegant, and simple. It’s a joy to read and understand. It’s a joy to hack on.</p>

<p>More of that ^ please.</p>]]></content><author><name>Joshua Priddle</name><email>jpriddle@me.com</email></author><category term="dev" /><summary type="html"><![CDATA[antirez, the author of Redis, had this to say about us destroying software.]]></summary></entry><entry><title type="html">~/.ssh/authorized_keys on OPNsense</title><link href="https://josh.fail/2024/ssh-authorized-keys-on-opnsense/" rel="alternate" type="text/html" title="~/.ssh/authorized_keys on OPNsense" /><published>2024-10-04T22:18:06-04:00</published><updated>2024-10-04T22:18:06-04:00</updated><id>https://josh.fail/2024/ssh-authorized-keys-on-opnsense</id><content type="html" xml:base="https://josh.fail/2024/ssh-authorized-keys-on-opnsense/"><![CDATA[<p>Hit a gotcha on OPNsense today with <code class="language-plaintext highlighter-rouge">~/.ssh/authorized_keys</code> not working as
expected.</p>

<p>I had edited the file manually. Turns out, you have to add authorized keys via
the web interface by editing the user. Annoying way to lose an hour 😭</p>]]></content><author><name>Joshua Priddle</name><email>jpriddle@me.com</email></author><category term="dev" /><summary type="html"><![CDATA[Hit a gotcha on OPNsense today with ~/.ssh/authorized_keys not working as expected.]]></summary></entry></feed>