xcli-ai
v0.1.0
Published
Config-driven CLI proxy — a single entry point for AI agents to discover and execute commands
Maintainers
Readme
xcli
A config-driven CLI proxy that provides a single unified entry point for AI agents to discover and execute commands across any tool.
Why
MCP (Model Context Protocol) tools are powerful, but each one consumes context window tokens just for its description. When you need Docker, Kubernetes, GitHub, databases, and more, the tool definitions alone eat a significant chunk of your context.
CLI is inherently more AI-friendly — commands are structured, parameters are enumerable, and output is parseable. But there's a catch: there are too many tools, and the AI needs to know each one by name, its flags, and its arguments.
xcli solves this by being the one tool the AI needs to know about. Everything else is discovered progressively through YAML configs:
AI → xcli # What domains are available?
AI → xcli docker # What commands does docker have?
AI → xcli docker ps -h # How do I use this command?
AI → xcli docker ps # Execute it
AI → xcli docker ps -o json # Get structured output for parsingNo MCP servers to install. No prior knowledge of whether the underlying tool is docker, kubectl, or curl. All commands are self-described in ~/.xcli/ YAML configs, loaded lazily on demand.
Install
npm install -g xcli-aiQuick Start
1. Create a domain
A domain is a logical grouping of related commands (e.g. github, docker, kubectl).
mkdir -p ~/.xcli/github# ~/.xcli/github/config.yaml
name: github
description: GitHub REST API operations2. Create a command
Each command lives in its own subdirectory and is fully self-contained — no inheritance, no shared state.
mkdir -p ~/.xcli/github/repos# ~/.xcli/github/repos/config.yaml
name: repos
description: List repositories for a user
type: curl
params:
- name: user
type: string
description: GitHub username
required: true
- name: per_page
type: number
description: Results per page
default: 30
curl:
method: GET
url: "https://api.github.com/users/{{user}}/repos?per_page={{per_page}}"
headers:
Accept: application/vnd.github+json
output:
digest:
- label: Repos
field: $count
columns:
- header: Name
field: full_name
width: 30
- header: Language
field: language
width: 14
- header: Stars
field: stargazers_count
width: 8
- header: Updated
field: updated_at
width: 223. Use it
# Raw output (default — same as running curl directly)
xcli github repos --user vuejs
# Table output (formatted using output.columns)
xcli github repos --user vuejs -o table
# Structured JSON (only digest + fields defined in output.columns)
xcli github repos --user vuejs -o jsonCommand Types
cli — Wrap local commands
Use {{paramName}} for raw value substitution, {{paramName_flag}} to auto-generate CLI flags:
name: pods
description: List pods in a namespace
type: cli
params:
- name: namespace
short: n
type: string
description: Kubernetes namespace
default: default
command: kubectl get pods {{namespace_flag}}The {{namespace_flag}} template automatically produces -n default (or -n <value> if overridden). Boolean params produce just the flag (--verbose) or nothing.
curl — Wrap HTTP APIs
Define the request declaratively — no need to write curl commands by hand:
name: issues
description: List issues for a repository
type: curl
params:
- name: owner
type: string
required: true
- name: repo
type: string
required: true
curl:
method: GET
url: "https://api.github.com/repos/{{owner}}/{{repo}}/issues"
headers:
Accept: application/vnd.github+json
output:
columns:
- header: "#"
field: number
width: 8
- header: Title
field: title
width: 60
- header: State
field: state
width: 8Headers with unresolved template variables (e.g. empty token) are automatically skipped.
Output Modes
| Mode | Description |
|------|-------------|
| (default) | Raw pass-through — identical to running the underlying command directly |
| -o table | Formatted table using output.columns, with output.digest shown as summary above |
| -o json | Structured JSON containing only digest + fields defined in output.columns |
Example -o json output:
{
"digest": {
"Repos": 30
},
"data": [
{
"Name": "vuejs/core",
"Language": "TypeScript",
"Stars": 48000,
"Updated": "2024-01-15T00:00:00Z"
}
]
}Config Structure
~/.xcli/
├── github/
│ ├── config.yaml # Domain descriptor (name + description only)
│ ├── repos/
│ │ └── config.yaml # Command definition (fully self-contained)
│ ├── issues/
│ │ └── config.yaml
│ └── repo-info/
│ └── config.yaml
├── docker/
│ ├── config.yaml
│ ├── ps/
│ │ └── config.yaml
│ └── logs/
│ └── config.yaml
└── kubectl/
├── config.yaml
├── pods/
│ └── config.yaml
└── describe/
└── config.yamlSee the examples/ directory for complete working configs covering GitHub API (curl), Docker (cli), and Kubernetes (cli).
Parameter Types
| Type | Description | Example |
|------|-------------|---------|
| string | Text value | --name foo |
| number | Numeric value | --count 42 |
| boolean | Flag, no value needed | --verbose |
| array | Comma-separated list | --tags a,b,c |
Optional fields: short (single-char alias), default, choices, required, placeholder, description.
Development
git clone <repo>
cd xcli
npm install
# Run in dev mode (no build step)
npm run dev -- github repos --user vuejs
# Build
npm run build
# Link globally for local testing
npm linkProject Structure
src/
├── cli.ts # Entry point, arg parsing, Docker-style help rendering
├── config.ts # YAML config loader with lazy/shallow loading
├── executor.ts # Command execution engine, template substitution
├── table.ts # Table (cli-table3) and structured JSON formatting
└── types.ts # TypeScript type definitionsAdding a New Domain
- Create
~/.xcli/<domain>/config.yamlwithnameanddescription - Create command subdirectories, each with its own
config.yaml - Run
xcli <domain>to verify — no restart needed, configs are loaded on demand
License
MIT
