<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Marta Karaś</title>
    <description>The latest articles on DEV Community by Marta Karaś (@martakar).</description>
    <link>https://dev.to/martakar</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3859272%2Fe2271f71-db72-4e1f-b357-372de9aec1c4.jpeg</url>
      <title>DEV Community: Marta Karaś</title>
      <link>https://dev.to/martakar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/martakar"/>
    <language>en</language>
    <item>
      <title>Building a CLI for Humans and AI Agents</title>
      <dc:creator>Marta Karaś</dc:creator>
      <pubDate>Fri, 03 Apr 2026 10:27:25 +0000</pubDate>
      <link>https://dev.to/martakar/building-a-cli-for-humans-and-ai-agents-1lpj</link>
      <guid>https://dev.to/martakar/building-a-cli-for-humans-and-ai-agents-1lpj</guid>
      <description>&lt;p&gt;Most CLIs are built for humans. Some are built for scripts. We built one for both — and for AI agents too.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/deployhq/deployhq-cli" rel="noopener noreferrer"&gt;DeployHQ CLI&lt;/a&gt; (&lt;code&gt;dhq&lt;/code&gt;) is a Go command-line tool for the DeployHQ deployment platform. It manages projects, servers, deployments, and releases. But what makes it different is that it was designed from day one to work equally well when a human is typing, when a shell script is piping, or when an AI agent is orchestrating.&lt;/p&gt;

&lt;p&gt;Here's what that looks like in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Output Contract
&lt;/h2&gt;

&lt;p&gt;The single most important design decision: &lt;strong&gt;stdout is always data, stderr is always human text.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Human sees a nice table on stderr, data goes to stdout&lt;/span&gt;
dhq projects list

&lt;span class="c"&gt;# Pipe it and get JSON automatically — no flag needed&lt;/span&gt;
dhq projects list | jq &lt;span class="s1"&gt;'.data[].name'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When stdout is a TTY, you get a formatted table. When it's piped, you get JSON. No &lt;code&gt;--json&lt;/code&gt; flag required (though you can use it explicitly).&lt;/p&gt;




&lt;h2&gt;
  
  
  Field Selection with &lt;code&gt;--json&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Most CLIs give you &lt;code&gt;--json&lt;/code&gt; and dump everything. We give you &lt;code&gt;--json &amp;lt;fields&amp;gt;&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Only get what you need&lt;/span&gt;
dhq deployments list &lt;span class="nt"&gt;-p&lt;/span&gt; my-app &lt;span class="nt"&gt;--json&lt;/span&gt; identifier,status,timestamps.duration

&lt;span class="c"&gt;# An agent requesting just the deployment ID and status&lt;/span&gt;
dhq deploy &lt;span class="nt"&gt;-p&lt;/span&gt; my-app &lt;span class="nt"&gt;-s&lt;/span&gt; prod &lt;span class="nt"&gt;--use-latest&lt;/span&gt; &lt;span class="nt"&gt;--json&lt;/span&gt; identifier,status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matters for agents because tokens cost money. A full deployment object might be 50+ fields. An agent checking status needs two.&lt;/p&gt;




&lt;h2&gt;
  
  
  Breadcrumbs: Teaching Agents What To Do Next
&lt;/h2&gt;

&lt;p&gt;Every JSON response includes a &lt;code&gt;breadcrumbs&lt;/code&gt; array — suggested next commands based on what just happened:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"identifier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abc123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"completed"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"breadcrumbs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"dhq deployments logs abc123 -p my-app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"dhq deployments show abc123 -p my-app --json"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An AI agent doesn't need to memorize your API. It reads the breadcrumbs and knows what's possible next. This turns a stateless CLI into a guided workflow.&lt;/p&gt;




&lt;h2&gt;
  
  
  Agent Detection
&lt;/h2&gt;

&lt;p&gt;The CLI detects when it's being used by an agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DEPLOYHQ_AGENT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1      &lt;span class="c"&gt;# Explicit agent mode&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CLAUDE_CODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1          &lt;span class="c"&gt;# Auto-detected&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;                &lt;span class="c"&gt;# Auto-detected&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In agent mode, the CLI adjusts its behavior — suppressing interactive prompts, defaulting to structured output, and including richer metadata.&lt;/p&gt;




&lt;h2&gt;
  
  
  JSONL Capture
&lt;/h2&gt;

&lt;p&gt;Set &lt;code&gt;DEPLOYHQ_OUTPUT_FILE=log.jsonl&lt;/code&gt; and every command's output gets appended as a JSONL line. Useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Agent audit trails&lt;/li&gt;
&lt;li&gt;CI pipeline debugging&lt;/li&gt;
&lt;li&gt;Replaying what happened during an automated workflow&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Non-Interactive Auth
&lt;/h2&gt;

&lt;p&gt;Three env vars, no browser popup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DEPLOYHQ_ACCOUNT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;myaccount
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DEPLOYHQ_EMAIL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;me@example.com
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DEPLOYHQ_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;abc123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is table stakes for CI, but it matters even more for agents. An AI agent can't click "Authorize" in a browser.&lt;/p&gt;




&lt;h2&gt;
  
  
  Exit Codes That Mean Something
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;0&lt;/code&gt; — success&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;1&lt;/code&gt; — failure (with structured error in JSON)&lt;/li&gt;
&lt;li&gt;Empty results return &lt;code&gt;0&lt;/code&gt; with empty &lt;code&gt;data&lt;/code&gt; — that's not an error&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Simple, but surprisingly rare. Many CLIs return 0 on errors or don't include error details in their JSON output.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Go SDK Inside
&lt;/h2&gt;

&lt;p&gt;The CLI wraps a public Go SDK at &lt;code&gt;pkg/sdk/&lt;/code&gt; with 97 methods. The SDK has zero internal imports — it's designed to be extractable as a standalone module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;sdk&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"account"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"apikey"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListProjects&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're building Go tooling around DeployHQ, you can use the SDK directly without shelling out to the CLI.&lt;/p&gt;




&lt;h2&gt;
  
  
  What We Learned
&lt;/h2&gt;

&lt;p&gt;Building for agents changed how we think about CLIs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Structured output isn't optional&lt;/strong&gt; — if your CLI can't produce JSON, agents can't use it reliably&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Field selection saves tokens&lt;/strong&gt; — agents pay per token, don't make them parse 50 fields to get 2&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Breadcrumbs beat documentation&lt;/strong&gt; — agents don't read man pages, but they read JSON responses&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-interactive is non-negotiable&lt;/strong&gt; — if it needs a human to click something, agents can't use it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Piped detection is free UX&lt;/strong&gt; — detect TTY vs pipe and adjust automatically&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The future of CLIs isn't human OR machine. It's both.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/deployhq/deployhq-cli" rel="noopener noreferrer"&gt;https://github.com/deployhq/deployhq-cli&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Install: &lt;code&gt;brew install deployhq/tap/dhq&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;DeployHQ: &lt;a href="https://www.deployhq.com" rel="noopener noreferrer"&gt;https://www.deployhq.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>go</category>
      <category>devops</category>
      <category>cli</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
