<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>CLI on Grant Lucas</title>
    <link>https://grantlucas.com/tags/cli/</link>
    <description>Recent content in CLI on Grant Lucas</description>
    <generator>Hugo</generator>
    <language>en</language>
    <lastBuildDate>Wed, 29 Jul 2026 19:00:00 -0400</lastBuildDate>
    <atom:link href="https://grantlucas.com/tags/cli/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>For Agents, --help Has to Be Enough</title>
      <link>https://grantlucas.com/posts/2026/07/help-has-to-be-enough/</link>
      <pubDate>Wed, 29 Jul 2026 19:00:00 -0400</pubDate>
      <guid>https://grantlucas.com/posts/2026/07/help-has-to-be-enough/</guid>
      <description>&lt;p&gt;&lt;em&gt;Designing an AI-native CLI: usable by humans, and especially usable by coding agents&lt;/em&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&#34;https://github.com/grantlucas/Logger-TXT&#34;&gt;Logger-TXT&lt;/a&gt; started life as a shell&#xA;script over a decade ago. It does one small thing: append a timestamped line to&#xA;a text file so I can remember what I did on that Tuesday two weeks ago. I&#xA;rewrote it in Go recently, and the surface reason was capabilities. Date ranges,&#xA;type and project filters, reverse scanning so a decade-deep log file stays fast.&lt;/p&gt;&#xA;&lt;p&gt;That&amp;rsquo;s not really why I did it, though. The rewrite was an excuse to practise&#xA;designing an AI-native command-line tool: one a coding agent can pick up and&#xA;drive correctly, with no help from me, on the first try.&lt;/p&gt;&#xA;&lt;p&gt;Every loop in the process that follows (the constraint, the testing, the&#xA;rewrites of my own help text) was for the agent. Humans get a better tool out of&#xA;it, and I&amp;rsquo;ll get to why that happens, but that&amp;rsquo;s the dividend rather than the&#xA;motivation.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-ai-native-means-here&#34;&gt;What &amp;ldquo;AI-Native&amp;rdquo; Means Here&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#what-ai-native-means-here&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Worth defining, since the phrase gets used for something else entirely.&lt;/p&gt;&#xA;&lt;p&gt;An AI-native CLI isn&amp;rsquo;t a CLI with AI inside it. There&amp;rsquo;s no model in Logger-TXT,&#xA;no natural-language parsing, no &lt;code&gt;logger-txt ask &amp;quot;what did I do Tuesday&amp;quot;&lt;/code&gt;. &lt;strong&gt;It&amp;rsquo;s&#xA;a tool that treats a coding agent as a first-class consumer of its interface,&#xA;alongside the human at the terminal.&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;A human explores. They run the command, squint at the output, try a flag, read&#xA;the error, and converge. They carry context between attempts, and they can go&#xA;ask someone or read the source when they&amp;rsquo;re stuck.&lt;/p&gt;&#xA;&lt;p&gt;An agent commits. It reads the interface once, picks the command that best&#xA;matches its intent, and runs it. An error it will read and route around, so a&#xA;command that fails outright is no problem. A plausible-looking result it simply&#xA;accepts, because it has no independent sense that the number should have been 40&#xA;instead of 10.&lt;/p&gt;&#xA;&lt;p&gt;So an agent is functionally a user with no prior context, no accumulated&#xA;intuition about your tool, and no way to ask a clarifying question, who&#xA;nonetheless has to get it right immediately.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-constraint---help-has-to-be-enough&#34;&gt;The Constraint: &lt;code&gt;--help&lt;/code&gt; Has to Be Enough&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#the-constraint---help-has-to-be-enough&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s the rule I gave myself: &lt;strong&gt;an agent handed nothing but this binary should&#xA;reach the right command on the first try.&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;No instruction file to load. No skill to install. No MCP server wrapping the&#xA;CLI. No prose in a repo telling the agent which flag to prefer. Everything it&#xA;needs to use the tool correctly ships inside the tool.&lt;/p&gt;&#xA;&lt;h3 id=&#34;why-not-a-claudemd-a-skill-or-an-mcp-server&#34;&gt;Why Not a CLAUDE.md, a Skill, or an MCP Server&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#why-not-a-claudemd-a-skill-or-an-mcp-server&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Those are the normal answers to &amp;ldquo;make this tool legible to an agent,&amp;rdquo; and each&#xA;one puts the knowledge in the wrong place:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;A &lt;code&gt;CLAUDE.md&lt;/code&gt; section&lt;/strong&gt; lives in &lt;em&gt;your&lt;/em&gt; repo, not mine. It drifts out of&#xA;date the moment I ship a flag, and it only helps agents that happen to read&#xA;that file, in that repo.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;An MCP server&lt;/strong&gt; is a second artifact to build, version, release, and keep in&#xA;sync with the binary. Now there are two things that can disagree about how the&#xA;tool behaves. It does buy something help text can&amp;rsquo;t, in fairness: a typed&#xA;schema constrains arguments structurally, where help text only asks nicely. I&#xA;went the other way regardless, for the reason below.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;A skill&lt;/strong&gt; has to be installed before it can help, so the tool doesn&amp;rsquo;t work&#xA;until someone has already done setup on the agent&amp;rsquo;s side.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Help text ships with the binary, versions with the binary, and is already the&#xA;first place both consumers look. It&amp;rsquo;s the only documentation surface that can&amp;rsquo;t&#xA;drift from the thing it documents. That matters most in the case the tool&#xA;actually gets used in: someone runs &lt;code&gt;brew install grantlucas/tap/logger-txt&lt;/code&gt; and&#xA;there is no repo, no README, and no source to fall back on. There&amp;rsquo;s a binary and&#xA;its help output, and nothing else.&lt;/p&gt;&#xA;&lt;p&gt;Though the better reason I picked it is that it&amp;rsquo;s a forcing function. If&#xA;&lt;code&gt;--help&lt;/code&gt; is the only channel I&amp;rsquo;m allowed to use, every ambiguity in the tool has&#xA;to get sorted out in the help text or in the design itself. I can&amp;rsquo;t paper over a&#xA;confusing interface with a paragraph somewhere else.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-design-loop&#34;&gt;The Design Loop&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#the-design-loop&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;The constraint makes the workflow concrete, and it ends up looking like&#xA;usability testing with an agent as the test subject:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Hand an agent the binary and nothing but &lt;code&gt;--help&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;Watch for the spot where it picks a plausible-but-wrong command&lt;/li&gt;&#xA;&lt;li&gt;Fix the help text, or the design, until a cold read leads somewhere better&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;Step 2 is the whole game, because &lt;strong&gt;plausible-but-wrong is the failure mode&#xA;agents produce and humans mostly don&amp;rsquo;t.&lt;/strong&gt; An agent that picks a command which&#xA;errors out is fine, since it reads the error, adjusts, and gets there. An agent&#xA;that picks a command which succeeds and returns believable-but-wrong results has&#xA;no signal that anything went wrong, and neither do I until I notice the numbers&#xA;look off weeks later.&lt;/p&gt;&#xA;&lt;h3 id=&#34;the-trap-i-built-myself&#34;&gt;The Trap I Built Myself&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#the-trap-i-built-myself&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;The best example came from a change that looked purely additive.&lt;/p&gt;&#xA;&lt;p&gt;I added &lt;code&gt;-t&lt;/code&gt; (type) and &lt;code&gt;-p&lt;/code&gt; (project) filters to both &lt;code&gt;show&lt;/code&gt; and &lt;code&gt;search&lt;/code&gt;. Nice&#xA;and consistent: same flags, both commands. Except that quietly created a trap.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&#xA;&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-0-1&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-0-1&#34;&gt;1&lt;/a&gt;&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-0-2&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-0-2&#34;&gt;2&lt;/a&gt;&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;logger-txt search WORK   &lt;span style=&#34;color:#6272a4&#34;&gt;# matches &amp;#34;WORK&amp;#34; anywhere in the line&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;logger-txt show -t WORK  &lt;span style=&#34;color:#6272a4&#34;&gt;# matches only entries tagged WORK&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;These now &lt;em&gt;look&lt;/em&gt; interchangeable, but &lt;code&gt;search&lt;/code&gt; matches the term anywhere in the&#xA;full log line, including the message body, so an entry like&#xA;&lt;code&gt;31/01/26 13:30 -0600 - PERSONAL - Followed up on WORK email&lt;/code&gt; shows up in the&#xA;first command and not the second.&lt;/p&gt;&#xA;&lt;p&gt;Asked to find my work entries, the agent reasonably picked &lt;code&gt;search&lt;/code&gt; and got&#xA;plausible results back. Nothing in the help text would have told it otherwise. A&#xA;human who wrote the tool would never make this mistake, which is exactly why I&#xA;couldn&amp;rsquo;t see it.&lt;/p&gt;&#xA;&lt;p&gt;The fix went into the help string, not into a document telling the agent what to&#xA;do:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&#xA;&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-1-1&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-1-1&#34;&gt;1&lt;/a&gt;&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-1-2&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-1-2&#34;&gt;2&lt;/a&gt;&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-1-3&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-1-3&#34;&gt;3&lt;/a&gt;&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-1-4&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-1-4&#34;&gt;4&lt;/a&gt;&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Note: &amp;#34;search TYPENAME&amp;#34; is not equivalent to &amp;#34;show -t TYPENAME&amp;#34;. The search&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;term matches anywhere in the full log line — including message text — so&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;entries that mention the type name in their message body will appear as false&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;positives. Use &amp;#34;show -t&amp;#34; to filter strictly by the type tag.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;It&amp;rsquo;s deliberately blunt, and it names the wrong choice explicitly, because a&#xA;disambiguation that only describes the correct option is useless to a reader who&#xA;already believes they&amp;rsquo;ve found it.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-designing-for-the-agent-produced&#34;&gt;What Designing for the Agent Produced&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#what-designing-for-the-agent-produced&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;A handful of patterns fell out of running that loop. Each one is here because of&#xA;a specific way an agent breaks without it.&lt;/p&gt;&#xA;&lt;h3 id=&#34;help-text-that-teaches-rather-than-lists&#34;&gt;Help Text That Teaches Rather Than Lists&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#help-text-that-teaches-rather-than-lists&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Without this, an agent works out the data format by running commands and&#xA;inferring from whatever comes back, which burns turns and produces a wrong&#xA;inference when the sample isn&amp;rsquo;t representative.&lt;/p&gt;&#xA;&lt;p&gt;So the root &lt;code&gt;--help&lt;/code&gt; embeds the log entry format and the full file-resolution&#xA;order. One read establishes where the data lives and what it looks like:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&#xA;&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-2-1&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-2-1&#34;&gt; 1&lt;/a&gt;&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-2-2&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-2-2&#34;&gt; 2&lt;/a&gt;&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-2-3&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-2-3&#34;&gt; 3&lt;/a&gt;&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-2-4&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-2-4&#34;&gt; 4&lt;/a&gt;&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-2-5&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-2-5&#34;&gt; 5&lt;/a&gt;&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-2-6&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-2-6&#34;&gt; 6&lt;/a&gt;&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-2-7&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-2-7&#34;&gt; 7&lt;/a&gt;&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-2-8&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-2-8&#34;&gt; 8&lt;/a&gt;&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-2-9&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-2-9&#34;&gt; 9&lt;/a&gt;&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-2-10&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-2-10&#34;&gt;10&lt;/a&gt;&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-2-11&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-2-11&#34;&gt;11&lt;/a&gt;&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Log entry format:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  DD/MM/YY HH:MM -0700 - Message&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  DD/MM/YY HH:MM -0700 - TYPE - Message&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  DD/MM/YY HH:MM -0700 - (PROJECT) - Message&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  DD/MM/YY HH:MM -0700 - TYPE (PROJECT) - Message&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;File resolution order:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  1. --file flag value&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  2. ./log.txt if it exists in the current directory&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  3. LOGGERTXT_PATH environment variable&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  4. ./log.txt (default)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;Subcommand examples show their expected output inline:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&#xA;&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-3-1&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-3-1&#34;&gt;1&lt;/a&gt;&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-3-2&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-3-2&#34;&gt;2&lt;/a&gt;&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-3-3&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-3-3&#34;&gt;3&lt;/a&gt;&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  # With both type and project&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  logger-txt add -t dev -p acme Fixed login bug&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  # =&amp;gt; 04/03/26 14:30 -0500 - DEV (ACME) - Fixed login bug&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;That &lt;code&gt;# =&amp;gt;&lt;/code&gt; line costs nothing and removes a round trip. It also makes the&#xA;uppercasing visible, since &lt;code&gt;-t dev&lt;/code&gt; comes back as &lt;code&gt;DEV&lt;/code&gt; and &lt;code&gt;-p acme&lt;/code&gt; as&#xA;&lt;code&gt;(ACME)&lt;/code&gt;, instead of leaving it as a surprise to be discovered later.&lt;/p&gt;&#xA;&lt;h3 id=&#34;documented-defaults-not-implied-ones&#34;&gt;Documented Defaults, Not Implied Ones&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#documented-defaults-not-implied-ones&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;An undocumented default gets guessed at, and a guess that happens to return rows&#xA;is indistinguishable from a correct answer. So the ones that could bite are&#xA;spelled out:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;A date-only &lt;code&gt;--start&lt;/code&gt; begins at &lt;code&gt;00:00&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;A date-only &lt;code&gt;--end&lt;/code&gt; ends at &lt;code&gt;23:59&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;An active date range &lt;strong&gt;overrides&lt;/strong&gt; the default count of 10&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;That last one especially. &lt;code&gt;show --start 01/03/26 --end 14/03/26&lt;/code&gt; returning&#xA;everything in the range rather than the last 10 is the behaviour I want, and it&#xA;flatly contradicts the count default documented a few lines above it in the same&#xA;help output. That contradiction is exactly why it has to be stated: left&#xA;implicit, an agent summarising two weeks of entries would confidently report on&#xA;ten of them.&lt;/p&gt;&#xA;&lt;h3 id=&#34;no-quoting-ceremony&#34;&gt;No Quoting Ceremony&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#no-quoting-ceremony&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Quoting is one of the most reliable ways to make a generated command fail:&#xA;nested quotes, shell escaping, an apostrophe in the message. So there&amp;rsquo;s nothing&#xA;to quote.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&#xA;&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-4-1&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-4-1&#34;&gt;1&lt;/a&gt;&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;logger-txt add Had coffee with the team&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;Arguments are joined into the message, which removes the whole class of failure&#xA;and is pretty nice to type besides.&lt;/p&gt;&#xA;&lt;h3 id=&#34;a-non-interactive-path-for-everything&#34;&gt;A Non-Interactive Path for Everything&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#a-non-interactive-path-for-everything&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;A blocking prompt is a hung agent. It sits there waiting on stdin that will&#xA;never come, and the turn dies on a timeout with no useful error. So &lt;code&gt;delete&lt;/code&gt;&#xA;asks for confirmation and &lt;code&gt;delete -y&lt;/code&gt; skips it. This is the oldest advice on the&#xA;list and the easiest to forget when you write the interactive path first.&lt;/p&gt;&#xA;&lt;h3 id=&#34;composable-greppable-output&#34;&gt;Composable, Greppable Output&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#composable-greppable-output&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;One entry per line on stdout, plain text on disk:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&#xA;&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-5-1&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-5-1&#34;&gt;1&lt;/a&gt;&#xA;&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f&#34; id=&#34;hl-5-2&#34;&gt;&lt;a style=&#34;outline:none;text-decoration:none;color:inherit&#34; href=&#34;#hl-5-2&#34;&gt;2&lt;/a&gt;&#xA;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&#xA;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;&#xA;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;logger-txt show | grep MEETING&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;logger-txt show -c &lt;span style=&#34;color:#bd93f9&#34;&gt;50&lt;/span&gt; | wc -l&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&lt;p&gt;The property I care about here is the escape hatch. When my CLI is the wrong&#xA;tool for the job, the agent can ignore it entirely and read the log file&#xA;directly. That was always the point of the plain-text format, though it pays off&#xA;differently with a consumer that&amp;rsquo;s perfectly happy to &lt;code&gt;grep&lt;/code&gt; a file when that&amp;rsquo;s&#xA;the shorter path.&lt;/p&gt;&#xA;&lt;h3 id=&#34;loud-failure-on-ambiguity&#34;&gt;Loud Failure on Ambiguity&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#loud-failure-on-ambiguity&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Passing &lt;code&gt;--start&lt;/code&gt; without &lt;code&gt;--end&lt;/code&gt; is an error with a clear message, not a&#xA;guessed range. The temptation is to be helpful: assume &amp;ldquo;until now,&amp;rdquo; assume &amp;ldquo;for&#xA;one day.&amp;rdquo; But a guessed range produces results that look correct, which puts us&#xA;straight back into plausible-but-wrong.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Erroring out converts a silent wrong answer into an immediate, self-correcting&#xA;retry.&lt;/strong&gt; For an agent, a good error message beats a permissive default, since&#xA;the error is information and the default is a coin flip.&lt;/p&gt;&#xA;&lt;h2 id=&#34;humans-get-it-for-free&#34;&gt;Humans Get It for Free&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#humans-get-it-for-free&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Here&amp;rsquo;s the part I didn&amp;rsquo;t expect: I never once had to trade one consumer against&#xA;the other. Every change on that list made the tool better for me at the terminal&#xA;too.&lt;/p&gt;&#xA;&lt;p&gt;I think the reason is structural, and it&amp;rsquo;s the most useful thing I&amp;rsquo;ve taken from&#xA;the exercise. &lt;strong&gt;An agent is the worst-case human.&lt;/strong&gt; No context, no accumulated&#xA;intuition about the tool, no memory of the last time it hit this flag, no way to&#xA;ask. Every affordance a human silently supplies from experience, the agent needs&#xA;supplied by the interface.&lt;/p&gt;&#xA;&lt;p&gt;Which means designing for the agent isn&amp;rsquo;t a parallel track to designing for&#xA;humans. It&amp;rsquo;s designing for the strictest possible human, the one on their first&#xA;day, at 2am, with no colleague to ask. Satisfy that reader and the experienced&#xA;one is covered by construction.&lt;/p&gt;&#xA;&lt;p&gt;So there&amp;rsquo;s no &amp;ldquo;agent version&amp;rdquo; of the interface to maintain, and I&amp;rsquo;d push back on&#xA;the idea that this is extra work. It&amp;rsquo;s one interface, held to a standard that&#xA;was always the right one, now with something that reliably tells me when I&amp;rsquo;ve&#xA;fallen short of it. I &lt;em&gt;knew&lt;/em&gt; &lt;code&gt;search WORK&lt;/code&gt; and &lt;code&gt;show -t WORK&lt;/code&gt; differed, since I wrote&#xA;both. My tool&amp;rsquo;s ambiguities were invisible to me precisely because I held all&#xA;the context that resolved them. The agent holds none of it, and it doesn&amp;rsquo;t have&#xA;the good grace to quietly work around my gaps. It takes exactly the wrong turn&#xA;my help text permits, immediately and repeatably, where I can watch it happen.&lt;/p&gt;&#xA;&lt;h2 id=&#34;why-this-is-worth-practising-now&#34;&gt;Why This Is Worth Practising Now&#xA;  &lt;a class=&#34;heading-link&#34; href=&#34;#why-this-is-worth-practising-now&#34; title=&#34;Click to copy link&#34;&gt;&lt;i class=&#34;fas fa-link&#34;&gt;&lt;/i&gt;&lt;/a&gt;&#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Logger-TXT is a personal tool. The stakes are that I mis-tally my hours one&#xA;week, which makes it a pretty good place to practise something that gets&#xA;expensive elsewhere.&lt;/p&gt;&#xA;&lt;p&gt;The ratio is shifting, though. More and more of the tools I build, at work and&#xA;otherwise, are going to spend most of their invocations being driven by&#xA;something that can&amp;rsquo;t ask a clarifying question in Slack, can&amp;rsquo;t absorb the tribal&#xA;knowledge in someone&amp;rsquo;s head, and can&amp;rsquo;t infer intent from a hallway&#xA;conversation. The interface is all it gets, and if the interface is ambiguous&#xA;then the tool is wrong in a way that looks right, at whatever scale the agent is&#xA;operating at.&lt;/p&gt;&#xA;&lt;p&gt;That&amp;rsquo;s a design discipline, and it&amp;rsquo;s worth practising deliberately before it&#xA;matters. Which flags are confusable. Which defaults are guessable. Which prompts&#xA;hang. Which errors teach. None of it is hard, it&amp;rsquo;s just invisible until you put&#xA;something in front of the tool that can&amp;rsquo;t paper over your omissions with&#xA;intuition.&lt;/p&gt;&#xA;&lt;p&gt;If you want to poke at the result, it&amp;rsquo;s on&#xA;&lt;a href=&#34;https://github.com/grantlucas/Logger-TXT&#34;&gt;GitHub&lt;/a&gt;, or&#xA;&lt;code&gt;brew install grantlucas/tap/logger-txt&lt;/code&gt; and run &lt;code&gt;--help&lt;/code&gt;. If you try it with an&#xA;agent and it takes a wrong turn I haven&amp;rsquo;t caught yet, let me know. Those are the&#xA;gaps I can&amp;rsquo;t find on my own, since I already know what my own tool means.&lt;/p&gt;&#xA;</description>
    </item>
  </channel>
</rss>
