---
title: "CLI Adapters"
description: "Give the agent structured access to any CLI tool (gh, ffmpeg, stripe) through a standard adapter interface — one of the two adapter seams covered in the Adapters guide."
---

# CLI Adapters

<Callout id="doc-block-clia1fit" tone="info">

**Where this fits:** CLI adapters are one of two adapter seams in the
framework. The canonical guide is [Adapters](/docs/sandbox-adapters), which
covers both this seam and the `run-code` sandbox seam — including the shared
edge/serverless constraint. This page is the quick reference for the CLI side.

</Callout>

A CLI adapter wraps a single command-line tool (`gh`, `ffmpeg`, `stripe`, `aws`) so the agent can discover it, check whether it's installed, and run it with a consistent stdout/stderr/exit-code result. Without this seam, every script reinvents how to invoke a CLI and parse its output.

<Diagram id="doc-block-pj17pq" title={"CLI adapter → registry → action surface"} summary={"ShellCliAdapter wraps a binary; CliRegistry collects adapters for discovery; defineAction exposes one call on the agent + UI action surface."}>

```html
<div class="diagram-cli">
  <div class="diagram-node" data-rough>
    gh · ffmpeg · stripe<br /><small class="diagram-muted"
      >command-line tools</small
    >
  </div>
  <div class="diagram-arrow diagram-muted" aria-hidden="true">&rarr;</div>
  <div class="diagram-box" data-rough>
    ShellCliAdapter<br /><small class="diagram-muted"
      >isAvailable · execute</small
    >
  </div>
  <div class="diagram-arrow diagram-muted" aria-hidden="true">&rarr;</div>
  <div class="diagram-panel center" data-rough>
    CliRegistry<br /><small class="diagram-muted"
      >describe() for discovery</small
    >
  </div>
  <div class="diagram-arrow diagram-muted" aria-hidden="true">&rarr;</div>
  <div class="diagram-pill accent">defineAction</div>
</div>
```

```css
.diagram-cli {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
.diagram-cli .diagram-arrow {
  font-size: 22px;
  line-height: 1;
}
.diagram-cli .center {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}
```

</Diagram>

## The interface {#the-interface}

Every CLI adapter implements `CliAdapter`:

```ts
import type { CliAdapter, CliResult } from "@agent-native/core/adapters/cli";

interface CliAdapter {
  name: string; // "gh", "stripe", "ffmpeg"
  description: string; // What the agent sees during discovery
  isAvailable(): Promise<boolean>;
  execute(args: string[]): Promise<CliResult>;
}

interface CliResult {
  stdout: string;
  stderr: string;
  exitCode: number;
}
```

## ShellCliAdapter {#shell-adapter}

For most CLIs you don't need a custom class — `ShellCliAdapter` wraps any binary with sensible defaults:

```ts
import { ShellCliAdapter } from "@agent-native/core/adapters/cli";

const gh = new ShellCliAdapter({
  command: "gh",
  description: "GitHub CLI — manage repos, PRs, issues, and releases",
});

const ffmpeg = new ShellCliAdapter({
  command: "ffmpeg",
  description: "Audio/video processing and transcoding",
  timeoutMs: 120_000, // 2 min for long encodes
  env: { STRIPE_API_KEY: process.env.STRIPE_SECRET_KEY! },
});
```

Options: `command` (required), `description` (required), `name` (defaults to `command`), `env` (merged with `process.env`), `cwd` (defaults to `process.cwd()`), and `timeoutMs` (default `30000`).

For custom auth, output parsing, or pre/post processing, implement `CliAdapter` directly instead of using `ShellCliAdapter`.

## Registry {#registry}

`CliRegistry` collects adapters so the agent can discover what's available at runtime:

```ts
import { CliRegistry, ShellCliAdapter } from "@agent-native/core/adapters/cli";

const cliRegistry = new CliRegistry();
cliRegistry.register(
  new ShellCliAdapter({ command: "gh", description: "GitHub CLI" }),
);

cliRegistry.list(); // all registered
await cliRegistry.listAvailable(); // only installed
await cliRegistry.describe(); // [{ name, description, available }] for discovery

const gh = cliRegistry.get("gh");
const result = await gh?.execute(["pr", "list", "--json", "title,url"]);
```

## Using from actions {#from-actions}

Wrap a CLI call in `defineAction` to expose it on the action surface — `defineAction` is required when the code runs inside the server action surface; use an adapter directly in a `scripts/` file otherwise. Never call `process.exit` in an action; throw an error instead.

```ts filename="actions/list-prs.ts"
import { defineAction } from "@agent-native/core/action";
import { ShellCliAdapter } from "@agent-native/core/adapters/cli";
import { z } from "zod";

const gh = new ShellCliAdapter({ command: "gh", description: "GitHub CLI" });

export default defineAction({
  description: "List open pull requests via the GitHub CLI.",
  schema: z.object({}),
  async run() {
    if (!(await gh.isAvailable())) {
      throw new Error("GitHub CLI not installed. Run: brew install gh");
    }
    const result = await gh.execute([
      "pr",
      "list",
      "--json",
      "title,url,state",
      "--limit",
      "10",
    ]);
    if (result.exitCode !== 0) {
      throw new Error(result.stderr || "gh pr list failed");
    }
    return JSON.parse(result.stdout);
  },
});
```

## Edge and serverless {#edge-serverless}

CLI adapters use `node:child_process`, which does not exist on edge/worker runtimes (Cloudflare Workers, Netlify Edge Functions). Run CLI adapter endpoints and tasks in a standard Node.js environment. This constraint is shared with the sandbox seam — see the full discussion in [Adapters](/docs/sandbox-adapters#edge-serverless).

## What's next

- [**Adapters**](/docs/sandbox-adapters) — the canonical guide to both adapter seams.
- [**Actions**](/docs/actions) — the action surface CLI adapters are usually wrapped in.
