For Agents, --help Has to Be Enough

Designing an AI-native CLI: usable by humans, and especially usable by coding agents

Logger-TXT started life as a shell script over a decade ago. It does one small thing: append a timestamped line to a text file so I can remember what I did on that Tuesday two weeks ago. I rewrote it in Go recently, and the surface reason was capabilities. Date ranges, type and project filters, reverse scanning so a decade-deep log file stays fast.

That’s not really why I did it, though. The rewrite was an excuse to practise designing an AI-native command-line tool: one a coding agent can pick up and drive correctly, with no help from me, on the first try.

Every loop in the process that follows (the constraint, the testing, the rewrites of my own help text) was for the agent. Humans get a better tool out of it, and I’ll get to why that happens, but that’s the dividend rather than the motivation.

What “AI-Native” Means Here

Worth defining, since the phrase gets used for something else entirely.

An AI-native CLI isn’t a CLI with AI inside it. There’s no model in Logger-TXT, no natural-language parsing, no logger-txt ask "what did I do Tuesday". It’s a tool that treats a coding agent as a first-class consumer of its interface, alongside the human at the terminal.

A human explores. They run the command, squint at the output, try a flag, read the error, and converge. They carry context between attempts, and they can go ask someone or read the source when they’re stuck.

An agent commits. It reads the interface once, picks the command that best matches its intent, and runs it. An error it will read and route around, so a command that fails outright is no problem. A plausible-looking result it simply accepts, because it has no independent sense that the number should have been 40 instead of 10.

So an agent is functionally a user with no prior context, no accumulated intuition about your tool, and no way to ask a clarifying question, who nonetheless has to get it right immediately.

The Constraint: --help Has to Be Enough

Here’s the rule I gave myself: an agent handed nothing but this binary should reach the right command on the first try.

No instruction file to load. No skill to install. No MCP server wrapping the CLI. No prose in a repo telling the agent which flag to prefer. Everything it needs to use the tool correctly ships inside the tool.

Why Not a CLAUDE.md, a Skill, or an MCP Server

Those are the normal answers to “make this tool legible to an agent,” and each one puts the knowledge in the wrong place:

  • A CLAUDE.md section lives in your repo, not mine. It drifts out of date the moment I ship a flag, and it only helps agents that happen to read that file, in that repo.
  • An MCP server is a second artifact to build, version, release, and keep in sync with the binary. Now there are two things that can disagree about how the tool behaves. It does buy something help text can’t, in fairness: a typed schema constrains arguments structurally, where help text only asks nicely. I went the other way regardless, for the reason below.
  • A skill has to be installed before it can help, so the tool doesn’t work until someone has already done setup on the agent’s side.

Help text ships with the binary, versions with the binary, and is already the first place both consumers look. It’s the only documentation surface that can’t drift from the thing it documents. That matters most in the case the tool actually gets used in: someone runs brew install grantlucas/tap/logger-txt and there is no repo, no README, and no source to fall back on. There’s a binary and its help output, and nothing else.

Though the better reason I picked it is that it’s a forcing function. If --help is the only channel I’m allowed to use, every ambiguity in the tool has to get sorted out in the help text or in the design itself. I can’t paper over a confusing interface with a paragraph somewhere else.

The Design Loop

The constraint makes the workflow concrete, and it ends up looking like usability testing with an agent as the test subject:

  1. Hand an agent the binary and nothing but --help
  2. Watch for the spot where it picks a plausible-but-wrong command
  3. Fix the help text, or the design, until a cold read leads somewhere better

Step 2 is the whole game, because plausible-but-wrong is the failure mode agents produce and humans mostly don’t. An agent that picks a command which errors out is fine, since it reads the error, adjusts, and gets there. An agent that picks a command which succeeds and returns believable-but-wrong results has no signal that anything went wrong, and neither do I until I notice the numbers look off weeks later.

The Trap I Built Myself

The best example came from a change that looked purely additive.

I added -t (type) and -p (project) filters to both show and search. Nice and consistent: same flags, both commands. Except that quietly created a trap.

1
2
logger-txt search WORK   # matches "WORK" anywhere in the line
logger-txt show -t WORK  # matches only entries tagged WORK

These now look interchangeable, but search matches the term anywhere in the full log line, including the message body, so an entry like 31/01/26 13:30 -0600 - PERSONAL - Followed up on WORK email shows up in the first command and not the second.

Asked to find my work entries, the agent reasonably picked search and got plausible results back. Nothing in the help text would have told it otherwise. A human who wrote the tool would never make this mistake, which is exactly why I couldn’t see it.

The fix went into the help string, not into a document telling the agent what to do:

1
2
3
4
Note: "search TYPENAME" is not equivalent to "show -t TYPENAME". The search
term matches anywhere in the full log line — including message text — so
entries that mention the type name in their message body will appear as false
positives. Use "show -t" to filter strictly by the type tag.

It’s deliberately blunt, and it names the wrong choice explicitly, because a disambiguation that only describes the correct option is useless to a reader who already believes they’ve found it.

What Designing for the Agent Produced

A handful of patterns fell out of running that loop. Each one is here because of a specific way an agent breaks without it.

Help Text That Teaches Rather Than Lists

Without this, an agent works out the data format by running commands and inferring from whatever comes back, which burns turns and produces a wrong inference when the sample isn’t representative.

So the root --help embeds the log entry format and the full file-resolution order. One read establishes where the data lives and what it looks like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Log entry format:
  DD/MM/YY HH:MM -0700 - Message
  DD/MM/YY HH:MM -0700 - TYPE - Message
  DD/MM/YY HH:MM -0700 - (PROJECT) - Message
  DD/MM/YY HH:MM -0700 - TYPE (PROJECT) - Message

File resolution order:
  1. --file flag value
  2. ./log.txt if it exists in the current directory
  3. LOGGERTXT_PATH environment variable
  4. ./log.txt (default)

Subcommand examples show their expected output inline:

1
2
3
  # With both type and project
  logger-txt add -t dev -p acme Fixed login bug
  # => 04/03/26 14:30 -0500 - DEV (ACME) - Fixed login bug

That # => line costs nothing and removes a round trip. It also makes the uppercasing visible, since -t dev comes back as DEV and -p acme as (ACME), instead of leaving it as a surprise to be discovered later.

Documented Defaults, Not Implied Ones

An undocumented default gets guessed at, and a guess that happens to return rows is indistinguishable from a correct answer. So the ones that could bite are spelled out:

  • A date-only --start begins at 00:00
  • A date-only --end ends at 23:59
  • An active date range overrides the default count of 10

That last one especially. show --start 01/03/26 --end 14/03/26 returning everything in the range rather than the last 10 is the behaviour I want, and it flatly contradicts the count default documented a few lines above it in the same help output. That contradiction is exactly why it has to be stated: left implicit, an agent summarising two weeks of entries would confidently report on ten of them.

No Quoting Ceremony

Quoting is one of the most reliable ways to make a generated command fail: nested quotes, shell escaping, an apostrophe in the message. So there’s nothing to quote.

1
logger-txt add Had coffee with the team

Arguments are joined into the message, which removes the whole class of failure and is pretty nice to type besides.

A Non-Interactive Path for Everything

A blocking prompt is a hung agent. It sits there waiting on stdin that will never come, and the turn dies on a timeout with no useful error. So delete asks for confirmation and delete -y skips it. This is the oldest advice on the list and the easiest to forget when you write the interactive path first.

Composable, Greppable Output

One entry per line on stdout, plain text on disk:

1
2
logger-txt show | grep MEETING
logger-txt show -c 50 | wc -l

The property I care about here is the escape hatch. When my CLI is the wrong tool for the job, the agent can ignore it entirely and read the log file directly. That was always the point of the plain-text format, though it pays off differently with a consumer that’s perfectly happy to grep a file when that’s the shorter path.

Loud Failure on Ambiguity

Passing --start without --end is an error with a clear message, not a guessed range. The temptation is to be helpful: assume “until now,” assume “for one day.” But a guessed range produces results that look correct, which puts us straight back into plausible-but-wrong.

Erroring out converts a silent wrong answer into an immediate, self-correcting retry. For an agent, a good error message beats a permissive default, since the error is information and the default is a coin flip.

Humans Get It for Free

Here’s the part I didn’t expect: I never once had to trade one consumer against the other. Every change on that list made the tool better for me at the terminal too.

I think the reason is structural, and it’s the most useful thing I’ve taken from the exercise. An agent is the worst-case human. No context, no accumulated intuition about the tool, no memory of the last time it hit this flag, no way to ask. Every affordance a human silently supplies from experience, the agent needs supplied by the interface.

Which means designing for the agent isn’t a parallel track to designing for humans. It’s designing for the strictest possible human, the one on their first day, at 2am, with no colleague to ask. Satisfy that reader and the experienced one is covered by construction.

So there’s no “agent version” of the interface to maintain, and I’d push back on the idea that this is extra work. It’s one interface, held to a standard that was always the right one, now with something that reliably tells me when I’ve fallen short of it. I knew search WORK and show -t WORK differed, since I wrote both. My tool’s ambiguities were invisible to me precisely because I held all the context that resolved them. The agent holds none of it, and it doesn’t have the good grace to quietly work around my gaps. It takes exactly the wrong turn my help text permits, immediately and repeatably, where I can watch it happen.

Why This Is Worth Practising Now

Logger-TXT is a personal tool. The stakes are that I mis-tally my hours one week, which makes it a pretty good place to practise something that gets expensive elsewhere.

The ratio is shifting, though. More and more of the tools I build, at work and otherwise, are going to spend most of their invocations being driven by something that can’t ask a clarifying question in Slack, can’t absorb the tribal knowledge in someone’s head, and can’t infer intent from a hallway conversation. The interface is all it gets, and if the interface is ambiguous then the tool is wrong in a way that looks right, at whatever scale the agent is operating at.

That’s a design discipline, and it’s worth practising deliberately before it matters. Which flags are confusable. Which defaults are guessable. Which prompts hang. Which errors teach. None of it is hard, it’s just invisible until you put something in front of the tool that can’t paper over your omissions with intuition.

If you want to poke at the result, it’s on GitHub, or brew install grantlucas/tap/logger-txt and run --help. If you try it with an agent and it takes a wrong turn I haven’t caught yet, let me know. Those are the gaps I can’t find on my own, since I already know what my own tool means.

See also