Skip to main content

CLI Registry

The CLI Registry is AgentOS's detector for installed command-line tools. It scans the user's PATH for known binaries and detects their versions, and a host reads the results.

Overview​

AgentOS ships a JSON-based registry of 54 CLI descriptors across 8 categories. When a host calls scan(), the CLIRegistry runs which and the version flag for each registered binary in parallel and returns what is installed on the machine.

The registry is a standalone utility: nothing else in AgentOS calls it. A host uses it to report which tools are installed, to gate a feature on a binary, or to build capability descriptors for the discovery engine from the scan. The CLI bridges (ClaudeCodeCLIBridge, GeminiCLIBridge) check their own binary with checkBinaryInstalled(), not through the registry, and the cli-executor pack's shell_execute runs whatever binaries the host has.

Registry Categories​

The 54 bundled descriptors live in src/safety/sandbox/subprocess/registry/ as plain JSON files:

FileCategoryCountExamples
llm.jsonllm5claude, gemini, ollama, lmstudio, aichat
devtools.jsondevtools10git, gh, docker, docker-compose, kubectl, terraform, make, jq, yq, tmux
runtimes.jsonruntime8node, python3, deno, bun, ruby, go, rustc, java
package-managers.jsonpackage-manager7npm, pnpm, yarn, pip, uv, brew, cargo
cloud.jsoncloud9gcloud, aws, az, flyctl, vercel, netlify, railway, heroku, wrangler
databases.jsondatabase5psql, mysql, sqlite3, redis-cli, mongosh
media.jsonmedia5ffmpeg, ffprobe, magick, sox, yt-dlp
networking.jsonnetworking5curl, wget, ssh, rsync, scp

CLIDescriptor Shape​

Each JSON entry conforms to the CLIDescriptor interface:

interface CLIDescriptor {
/** Binary name on PATH (e.g. 'claude', 'docker', 'ffmpeg'). */
binaryName: string;
/** Human-readable display name. */
displayName: string;
/** What this CLI does. */
description: string;
/** Category for grouping (e.g. 'llm', 'media', 'devtools'). */
category: string;
/** How to install if missing. */
installGuidance: string;
/** Version flag override if not --version. */
versionFlag?: string;
/** Regex to parse version from output (default: /(\d+\.\d+\.\d+)/). */
versionPattern?: RegExp;
}

Example from cloud.json:

{
"binaryName": "gcloud",
"displayName": "Google Cloud SDK",
"description": "Google Cloud resource management",
"category": "cloud",
"installGuidance": "https://cloud.google.com/sdk/docs/install",
"versionFlag": "--version"
}

CLIRegistry API​

import { CLIRegistry, WELL_KNOWN_CLIS } from '@framers/agentos/sandbox/subprocess';

Constructor​

const registry = new CLIRegistry(); // loads bundled JSON descriptors
const empty = new CLIRegistry(false); // starts empty (no defaults)

Methods​

MethodReturnsDescription
register(descriptor)voidRegister a single CLI descriptor. Overwrites existing entry for same binaryName.
registerAll(descriptors)voidRegister multiple descriptors at once.
unregister(binaryName)booleanRemove a descriptor by binary name.
scan()Promise<CLIScanResult[]>Scan PATH for all registered CLIs (parallel which + --version).
check(binaryName)Promise<CLIScanResult>Check a single binary by name.
list()CLIDescriptor[]Get all registered descriptors (installed status unknown).
installed()Promise<CLIScanResult[]>Get only CLIs that are installed.
byCategory(category)Promise<CLIScanResult[]>Get CLIs by category (scans first).
categories()string[]Get all unique categories.
has(binaryName)booleanCheck if a binary is registered (not whether installed).
get(binaryName)CLIDescriptor | undefinedGet a descriptor by binary name.
sizenumberTotal number of registered descriptors.

CLIScanResult​

The result from scan() or check() extends CLIDescriptor:

interface CLIScanResult extends CLIDescriptor {
installed: boolean; // whether the binary was found on PATH
binaryPath?: string; // resolved absolute path (e.g. /usr/local/bin/node)
version?: string; // parsed version string (e.g. "22.4.0")
}

Adding Custom CLIs​

Option 1: Contribute a descriptor​

A descriptor that belongs in every install goes into AgentOS itself: add an entry to a category file, or a new *.json file, in src/safety/sandbox/subprocess/registry/. The build ships those files in dist/, and the registry loads every *.json in that folder:

[
{
"binaryName": "my-tool",
"displayName": "My Tool",
"description": "Internal deployment CLI",
"category": "devtools",
"installGuidance": "brew install my-tool"
}
]

Option 2: Register at runtime (dynamic)​

const registry = new CLIRegistry();

registry.register({
binaryName: 'my-tool',
displayName: 'My Tool',
description: 'Internal deployment CLI',
category: 'devtools',
installGuidance: 'brew install my-tool',
});

const result = await registry.check('my-tool');
if (result.installed) {
console.log(`my-tool v${result.version} at ${result.binaryPath}`);
}

Option 3: Full scan with custom CLIs​

const registry = new CLIRegistry();

// Add several custom CLIs
registry.registerAll([
{ binaryName: 'tsc', displayName: 'TypeScript', description: 'TS compiler', category: 'devtools', installGuidance: 'npm i -g typescript' },
{ binaryName: 'eslint', displayName: 'ESLint', description: 'JS linter', category: 'devtools', installGuidance: 'npm i -g eslint' },
]);

// Scan everything (bundled + custom)
const results = await registry.scan();
for (const r of results) {
const status = r.installed ? `v${r.version}` : 'not installed';
console.log(`${r.displayName.padEnd(24)} ${status}`);
}

// Filter by category
const llmClis = await registry.byCategory('llm');
console.log(`LLM CLIs found: ${llmClis.filter(c => c.installed).length}/${llmClis.length}`);

Integration with CLISubprocessBridge​

The CLISubprocessBridge is an abstract base class for managing CLI subprocesses. It handles spawning, stdin piping, NDJSON stream parsing, timeouts, and abort signals. Subclasses implement CLI-specific flag assembly and error classification.

Two production bridges extend it:

BridgeBinaryPurpose
ClaudeCodeCLIBridgeclaudeAnthropic Claude via Max subscription (no API key needed)
GeminiCLIBridgegeminiGoogle Gemini via Google account login (no API key needed)

The claude-code-cli and gemini-cli providers call their bridge's checkBinaryInstalled() (which, then --version) when they initialize, and initialize() throws BINARY_NOT_FOUND when the binary is missing (CLI Error Codes).

Creating a custom bridge​

import { CLISubprocessBridge, CLISubprocessError, CLI_ERROR } from '@framers/agentos/sandbox/subprocess';
import type { BridgeOptions, OutputFormat, StreamEvent } from '@framers/agentos/sandbox/subprocess';

class MyToolBridge extends CLISubprocessBridge {
protected readonly binaryName = 'mytool';

protected buildArgs(options: BridgeOptions, format: OutputFormat): string[] {
return ['--prompt', options.prompt, '--format', format];
}

protected classifyError(error: any): CLISubprocessError {
if (error.code === 'ENOENT') {
return new CLISubprocessError(
'mytool not found',
CLI_ERROR.BINARY_NOT_FOUND,
'mytool',
'Install: brew install mytool',
false,
);
}
return new CLISubprocessError(
error.message,
CLI_ERROR.CRASHED,
'mytool',
'Check mytool logs',
true,
);
}

// The declared return type keeps each `type` a literal, as StreamEvent requires.
protected parseStreamEvent(raw: any): StreamEvent | null {
if (raw.text) return { type: 'text_delta', text: raw.text };
if (raw.done) return { type: 'result', result: raw.output };
return null;
}
}

Integration with cli-executor Extension​

The cli-executor extension pack (@framers/agentos-ext-cli-executor) provides tools that let agents execute arbitrary shell commands on the host. While it does not import CLIRegistry directly, the two systems are complementary:

  • CLIRegistry answers "what binaries exist?" -- discovery and detection.
  • cli-executor answers "can the agent run this command?" -- execution with security guardrails.

The cli-executor pack takes its policy from its options: filesystem (read and write roots), its own command checks, and dangerouslySkipSecurityChecks. The host that loads the pack sets them.

Security Considerations​

The CLI Registry runs nothing beyond which and each binary's version flag. It applies no security policy, and neither does the rest of AgentOS for CLI execution: SecurityTier ('dangerous' | 'permissive' | 'balanced' | 'strict' | 'paranoid') is a type AgentOS declares and does not enforce. Which commands an agent may run, and where it may write, is set by the host through the cli-executor pack's options.

Error Handling​

The CLISubprocessError class provides structured errors with actionable guidance:

import { CLISubprocessError, CLI_ERROR } from '@framers/agentos/sandbox/subprocess';

// Common error codes:
CLI_ERROR.BINARY_NOT_FOUND // Binary not found on PATH
CLI_ERROR.NOT_AUTHENTICATED // Binary installed but not logged in
CLI_ERROR.VERSION_OUTDATED // Version too old for required features
CLI_ERROR.SPAWN_FAILED // Process failed to start
CLI_ERROR.TIMEOUT // Process exceeded timeout
CLI_ERROR.CRASHED // Non-zero exit code
CLI_ERROR.RATE_LIMITED // Rate limit / quota exceeded
CLI_ERROR.PERMISSION_DENIED // EACCES
CLI_ERROR.CONTEXT_TOO_LONG // Input too long for the CLI

Each error carries a guidance string with fix instructions and a recoverable flag, both set by the code that builds the error. CLI Error Codes lists which of these codes the Claude Code and Gemini CLI bridges raise and when, and what AgentOS does with the error.

Exports​

Everything is exported from the barrel at @framers/agentos/sandbox/subprocess:

export { CLISubprocessBridge } from './CLISubprocessBridge';
export { CLIRegistry, WELL_KNOWN_CLIS } from './CLIRegistry';
export { CLISubprocessError, CLI_ERROR } from './errors';
export type {
BridgeOptions,
BridgeResult,
StreamEvent,
StreamParseContext,
OutputFormat,
InstallCheckResult,
CLIDescriptor,
CLIScanResult,
} from './types';

Troubleshooting​

CLI not detected​

The registry reports a binary as not installed when which <binary> fails during scan() or check().

Common causes:

  • Binary not installed. Follow the installGuidance field from the descriptor. Run the install command, then re-scan.
  • PATH does not include the binary's directory. GUI-launched processes (IDEs, Electron apps) may inherit a different PATH than your terminal. Verify with echo $PATH and add the directory to your shell profile (~/.bashrc, ~/.zshrc, ~/.profile).
  • Binary has an unexpected name. Some CLIs differ across platforms — python vs python3, docker-compose (v1) vs docker compose (v2 plugin). The registry tracks specific binary names; check the binaryName field.
  • Windows with no which program on PATH. scan(), check() and the CLI bridges' checkBinaryInstalled() run which on every platform, Windows included. Where no which program is on PATH, the lookup fails, so every binary reads as not installed and the claude-code-cli and gemini-cli providers throw BINARY_NOT_FOUND when they initialize. Provider auto-detection looks for the claude and gemini binaries with where on Windows (provider-defaults.ts), so it can pick one of those providers that then fails to initialize.

Fix: Install the binary, ensure its directory is on PATH, or register a custom descriptor with the correct binaryName. On Windows, put a which program on PATH as well.

Version shows "unknown"​

The binary was found on PATH (installed: true) but the version string could not be parsed.

Common causes:

  • The binary's version output does not match the default regex /(\d+\.\d+\.\d+)/. For example, a CLI that outputs v2.1 (two segments) or Build 20240315 (no semver) will not match.
  • The version output goes to stderr instead of stdout.
  • The version flag fails (some CLIs use -v, -V, or version instead of --version): the binary still reads as installed, with version unknown. Provide a custom versionFlag in the descriptor.

Fix: Register the CLI with a custom versionPattern regex that matches its actual output format:

registry.register({
binaryName: 'my-tool',
displayName: 'My Tool',
description: 'Custom tool',
category: 'devtools',
installGuidance: 'brew install my-tool',
versionPattern: /v?(\d+\.\d+)/, // match two-segment versions
});

Custom CLI not persisted​

Runtime registrations via registry.register() or registry.registerAll() are per-process only. They do not survive process restarts.

Fix: Register your descriptors each time the process starts, or contribute them to AgentOS (src/safety/sandbox/subprocess/registry/): the CLIRegistry constructor loads every *.json file the package ships in that folder.

scan() is slow​

The scan() method runs which for every registered descriptor, and the version flag for each binary which finds. With the 54 bundled descriptors, that is 54 which processes and one more process for each installed binary.

Common causes:

  • Network-attached PATH entries. If PATH includes NFS or CIFS mounts, each which call may incur network latency. Remove network paths from PATH or move them to the end.
  • Slow binary startup. Some CLIs (e.g., Java, gcloud) have slow startup times for --version. The registry runs all checks in parallel, but a single slow binary can delay the overall result.
  • Antivirus scanning. Antivirus that scans each new process adds that scan to every which and version-flag process the scan starts.

Fix: The registry already runs checks in parallel. To reduce scan time further, use check(binaryName) for the registered binaries you need instead of scan() (for a binary with no descriptor, check() reports installed: false without looking). byCategory(category) saves no time: it runs the full scan() and filters its results.

Complete CLI Reference​

All 54 bundled CLI descriptors, organized by category. Data sourced from src/safety/sandbox/subprocess/registry/*.json.

LLM (5)​

#BinaryDisplay NameCategoryInstall
1claudeClaude Codellmnpm install -g @anthropic-ai/claude-code
2geminiGemini CLIllmnpm install -g @google/gemini-cli
3ollamaOllamallmhttps://ollama.com/download
4lmstudioLM Studio CLIllmhttps://lmstudio.ai/
5aichatAIChatllmcargo install aichat or brew install aichat

Dev Tools (10)​

#BinaryDisplay NameCategoryInstall
6gitGitdevtoolshttps://git-scm.com/downloads
7ghGitHub CLIdevtoolshttps://cli.github.com/
8dockerDockerdevtoolshttps://docs.docker.com/get-docker/
9docker-composeDocker ComposedevtoolsIncluded with Docker Desktop or pip install docker-compose
10kubectlkubectldevtoolshttps://kubernetes.io/docs/tasks/tools/
11terraformTerraformdevtoolshttps://developer.hashicorp.com/terraform/install
12makeMakedevtoolsPre-installed on macOS/Linux; Windows: choco install make
13jqjqdevtoolsbrew install jq or apt install jq
14yqyqdevtoolsbrew install yq or pip install yq
15tmuxtmuxdevtoolsbrew install tmux or apt install tmux

Runtimes (8)​

#BinaryDisplay NameCategoryInstall
16nodeNode.jsruntimehttps://nodejs.org/
17python3Python 3runtimehttps://www.python.org/downloads/
18denoDenoruntimecurl -fsSL https://deno.land/install.sh | sh
19bunBunruntimecurl -fsSL https://bun.sh/install | bash
20rubyRubyruntimehttps://www.ruby-lang.org/en/documentation/installation/
21goGoruntimehttps://go.dev/doc/install
22rustcRustruntimecurl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
23javaJavaruntimehttps://adoptium.net/ or brew install openjdk

Package Managers (7)​

#BinaryDisplay NameCategoryInstall
24npmnpmpackage-managerInstalled with Node.js
25pnpmpnpmpackage-managernpm install -g pnpm or corepack enable
26yarnYarnpackage-managernpm install -g yarn or corepack enable
27pippippackage-managerInstalled with Python: python3 -m ensurepip
28uvuvpackage-managercurl -LsSf https://astral.sh/uv/install.sh | sh
29brewHomebrewpackage-managerhttps://brew.sh/
30cargoCargopackage-managerInstalled with Rust: https://rustup.rs/

Cloud (9)​

#BinaryDisplay NameCategoryInstall
31gcloudGoogle Cloud SDKcloudhttps://cloud.google.com/sdk/docs/install
32awsAWS CLIcloudhttps://aws.amazon.com/cli/
33azAzure CLIcloudhttps://learn.microsoft.com/en-us/cli/azure/install-azure-cli
34flyctlFly.io CLIcloudcurl -L https://fly.io/install.sh | sh
35vercelVercel CLIcloudnpm install -g vercel
36netlifyNetlify CLIcloudnpm install -g netlify-cli
37railwayRailway CLIcloudnpm install -g @railway/cli
38herokuHeroku CLIcloudhttps://devcenter.heroku.com/articles/heroku-cli
39wranglerCloudflare Wranglercloudnpm install -g wrangler

Databases (5)​

#BinaryDisplay NameCategoryInstall
40psqlPostgreSQLdatabasebrew install postgresql or apt install postgresql-client
41mysqlMySQLdatabasebrew install mysql-client or apt install mysql-client
42sqlite3SQLitedatabasePre-installed on macOS; apt install sqlite3
43redis-cliRedis CLIdatabasebrew install redis or apt install redis-tools
44mongoshMongoDB Shelldatabasebrew install mongosh or npm install -g mongosh

Media (5)​

#BinaryDisplay NameCategoryInstall
45ffmpegFFmpegmediahttps://ffmpeg.org/download.html
46ffprobeFFprobemediaInstalled with FFmpeg
47magickImageMagickmediabrew install imagemagick or apt install imagemagick
48soxSoXmediabrew install sox or apt install sox
49yt-dlpyt-dlpmediabrew install yt-dlp or pip install yt-dlp

Networking (5)​

#BinaryDisplay NameCategoryInstall
50curlcURLnetworkingPre-installed on macOS/Linux
51wgetWgetnetworkingbrew install wget or apt install wget
52sshSSHnetworkingPre-installed on macOS/Linux
53rsyncrsyncnetworkingPre-installed on macOS; apt install rsync
54scpSCPnetworkingPre-installed with SSH