The missing CLI for LLMs. One command, any provider, structured output, intelligent routing.
npm install -g @felipematos/ain-cliAIN turns any OpenAI-compatible API into a scriptable, shell-safe command. No boilerplate. No SDKs. Just prompts in, answers out.
$ ain What is the capital of Brazil?
Brasilia
$ ain Is Brad Pitt an actor? --bool
true
$ ain Explain quantum entanglement briefly --stream
Quantum entanglement is a phenomenon where two particles become...
$ ain List 3 programming languages --json
{
"output": ["Python", "TypeScript", "Rust"]
}-
Any provider, one interface. OpenAI, Anthropic, Groq, Mistral, DeepSeek, Ollama, LM Studio, vLLM — AIN works with all of them through a single, consistent CLI. Switch providers by changing one flag.
-
Built for automation. Clean stdout for piping, diagnostics to stderr, JSON/JSONL/boolean output modes, schema validation, field extraction, and proper exit codes. AIN is a first-class citizen in shell scripts, CI pipelines, and cron jobs.
-
Intelligent routing. Don't hardcode models. AIN classifies your prompt across 6 tiers (ultra-fast, fast, general, reasoning, coding, creative) using multilingual heuristics or an optional LLM classifier — then routes to the right model via policies. Supports 7 languages, a built-in model catalog, and
ain routing catalog --updateto fetch the latest from OpenRouter. -
Agent-ready. AIN integrates with AI agent frameworks like OpenClaw via the
openclaw-plugin-ainplugin. Your AIN providers, routing engine, and execution layer become tools that agents can call directly. Available on ClawHub. -
Zero friction. No quotes needed. Command aliases (
ain r,ain p,ain d). Option abbreviations (--st,--ro). A 3-phase onboarding wizard on first run (providers → classifier → routing policy). Templates for 14 providers. Re-runain wizardanytime.
# Use it in scripts
result=$(ain r Extract the sentiment --json --field output)
# Pipe content through it
cat article.txt | ain Summarize this in 3 bullet points
# Route intelligently across models
ain Classify this support ticket --route --tier fast
# Use as a Node.js library
import { run, stream, route } from '@felipematos/ain-cli';
const result = await run({ prompt: 'Hello', provider: 'openai' });- Features
- Installation
- Quick Start
- CLI Reference
- Provider Templates
- Onboarding Wizard
- Configuration
- Routing & Policies
- Output Modes
- Library API
- TypeScript Types
- Docker
- Architecture
- Development
- Exit Codes
- License
- Provider-agnostic — works with any OpenAI-compatible API endpoint
- Shell-safe — clean stdout, diagnostics to stderr, proper exit codes
- Structured output — JSON, JSONL, JSON Schema validation, field extraction
- Streaming — progressive token output with think-block filtering
- Intelligent routing — automatic model selection by task type and policy
- Fallback chains — automatic retry on alternate models when a provider fails
- Retry with backoff — configurable retry logic for transient failures
- Project overlays — per-project
ain.yamloverrides checked into repos - Dual-use — works as a CLI tool and as a Node.js library
- Zero runtime bloat — only 3 production dependencies (commander, yaml, zod)
- No quotes needed —
ain What is the capital of Brazil?just works - Command aliases —
a(sk),r(un),p(roviders),m(odels),c(onfig),d(octor),rt(routing) - Option abbreviations —
--stfor--stream,--rofor--route, and more - Provider templates — one-command setup for OpenAI, Anthropic, Groq, and 11 more
- Onboarding wizard — interactive first-run setup guides you through provider configuration
npm install -g @felipematos/ain-cligit clone https://github.com/felipematos/ain.git
cd ain
npm install
npm run build
npm linkdocker build -t ain .
docker run --rm ain ask "Hello, world"ain --version
ain doctor# First run? The wizard will guide you. Or set up manually:
ain providers add --template openai --set-default
# Or: ain providers add --template ollama --set-default
# Ask a question (no quotes needed!)
ain What is the capital of Brazil?
# Stream a response
ain Explain quantum entanglement briefly --stream
# Get structured JSON output
ain r Get info about France --json
# Schema-validated output
ain r Extract company info --schema company.schema.json
# Extract a single field
ain r Return facts about Japan --schema facts.json --field capital
# Use intelligent routing (picks the best model automatically)
ain Classify this email as spam or not --routeIf no command is given, AIN treats everything as a prompt (using ask under the hood). No quotes required — bare words are joined automatically, and options are parsed from the end:
ain What is the capital of Brazil?
ain Explain quantum entanglement briefly --stream
ain Translate this to Portuguese --model gpt-4o --skip-thinkEvery command has a short alias:
| Command | Alias | Example |
|---|---|---|
ask |
a |
ain a Hello world |
run |
r |
ain r Get info --json |
providers |
p |
ain p list |
models |
m |
ain m list --live |
config |
c |
ain c show |
doctor |
d |
ain d --json |
routing |
rt |
ain rt policies |
Long options can be abbreviated to their shortest unambiguous prefix:
| Abbreviation | Expands to | Notes |
|---|---|---|
--st |
--stream |
|
--ro |
--route |
|
--sk |
--skip-think |
|
--dr |
--dry-run |
|
--sc |
--schema |
|
--po |
--policy |
|
--tie |
--tier |
--ti is ambiguous with --timeout |
--tim |
--timeout |
|
--ret |
--retry |
--re is ambiguous with --remove-tag |
--ma |
--max-tokens |
|
--li |
--live |
|
--bo |
--bool |
|
--fo |
--force |
Ambiguous prefixes (matching multiple options) are left as-is and will produce an error. Use a longer prefix to disambiguate.
Human-friendly prompt execution. Defaults to plain text output. This is the default command when no command is specified.
ain ask "<prompt>" [options]
ain <prompt words> [options] # equivalent (default command)| Option | Description |
|---|---|
--stream |
Stream tokens progressively |
--json |
Output as pretty-printed JSON envelope |
--jsonl |
Output as compact single-line JSON |
--bool |
Output as boolean true/false |
--model <id> |
Override model (ID or alias) |
--provider <name> |
Override provider |
--system <text> |
System prompt |
--file <path> |
Append file content to prompt |
--temperature <n> |
Sampling temperature |
--max-tokens <n> |
Maximum tokens in response |
--timeout <ms> |
Request timeout in milliseconds |
--retry <n> |
Max retry attempts |
--skip-think |
Suppress reasoning/thinking preamble |
--route |
Use intelligent routing |
--tier <tier> |
Force a specific tier (ultra-fast, fast, general, reasoning, coding, creative) |
--policy <name> |
Use a named routing policy |
--dry-run |
Preview routing decision without executing |
--verbose |
Show extra diagnostics on stderr |
Examples:
# No quotes needed
ain Summarize this text --file ./article.txt
# Translate with a specific model, suppress thinking
ain Translate to Portuguese --model qwen-reason --sk
# Stream with routing
ain Write a haiku about coding --st --ro
# Force fast tier
ain Is this spam --ro --tie fast
# Preview routing without running
ain Explain relativity --dr
# Pipe stdin
echo "some text" | ain ask "Summarize this:"
# With quotes (also works)
ain ask "Tell me about France" --stream --verboseMachine-oriented execution with structured output modes. Designed for scripts and pipelines. Accepts a positional prompt (no --prompt needed):
ain run <prompt words> [options]
ain r <prompt words> [options] # using alias
ain run --prompt "<prompt>" [options] # explicit flag (also works)Accepts the same options as ain ask, plus:
| Option | Description |
|---|---|
--schema <path> |
Validate output against a JSON Schema file |
--field <key> |
Extract a single field from JSON output (supports dot notation) |
Examples:
# JSON envelope output (no quotes needed)
ain r List 3 colors --json
# Compact JSONL for piping
ain r Analyze this log --jsonl
# Schema validation
ain r Extract user info --sc user.schema.json
# Field extraction with dot notation
ain r City facts --sc city.json --field location.coordinates
# Boolean output
ain r Is this a question --bool
# Policy-based routing with fallback
ain r Classify this ticket --po local-first
# Reliability options
ain r Process data --ret 5 --tim 30000
# With --prompt flag (still works)
ain run --prompt "Complex prompt with --dashes" --jsonManage LLM provider connections. Alias: ain p.
# Add from a template (recommended)
ain p add --template openai --set-default
ain p add --template ollama --set-default
# Add manually
ain p add <name> --base-url <url> [--api-key <key>] [--timeout <ms>] [--set-default]
# List available templates
ain p templates
# List configured providers
ain p list
ain p list --json # Machine-readable output
# Show provider details
ain p show <name>
# Remove a provider
ain p remove <name>
# Set default provider
ain p set-default <name>Examples:
# One-liner setup from templates
ain p add --template openai --api-key env:OPENAI_API_KEY --set-default
ain p add --template groq --set-default
ain p add --template anthropic --set-default
ain p add --template ollama --set-default
# Custom provider
ain p add my-server --base-url http://gpu-server:8080/v1 --timeout 120000
# Custom name with template base
ain p add my-openai --template openai --api-key sk-abc123Manage model catalog and metadata.
# List cached models (* = default)
ain models list
# Fetch live from provider API
ain models list --live
# Machine-readable JSON output
ain models list --json
# Refresh model cache
ain models refresh
ain models refresh <provider> # Specific provider only
# Set model metadata
ain models set <provider> <model-id> [--alias <alias>] [--tag <tag>...] [--context <size>]Examples:
# Tag models for routing
ain models set local liquid/lfm2.5-1.2b --alias liquid-fast --tag fast --tag local
ain models set local qwen3.5-4b-mlx --alias qwen-reason --tag reasoning --tag local
ain models set local google/gemma-3n-e4b --context 32768Run health checks on configuration and provider connectivity.
ain doctor # Check everything
ain doctor --provider <name> # Check specific provider
ain doctor --json # Machine-readable outputChecks performed:
- Config file exists and is valid YAML
- Project overlay detection
- Provider count
- API key resolution (including
env:references) - Endpoint reachability and latency
Inspect and manage the routing system.
# Simulate a routing decision
ain routing simulate "<prompt>"
ain routing simulate "<prompt>" --json
# List available policies
ain routing policies
ain routing policies --verbose # Show tier details
# Browse built-in model catalog
ain routing catalog
ain routing catalog --tier reasoning --json
ain routing catalog --local # Local-only models
# Update catalog from OpenRouter API
ain routing catalog --update
# Scaffold a policies.yaml file
ain routing init-policiesManage AIN configuration.
# Create initial config
ain config init
# Show config file path
ain config path
# Display current configuration
ain config show
# Set defaults
ain config set-default --provider <name> --model <id>
ain config set-default --temperature 0.3 --max-tokens 2048AIN ships with 14 built-in templates for quick provider setup. List them with ain providers templates:
| Template | Provider | Base URL | API Key Env Var |
|---|---|---|---|
openai |
OpenAI | api.openai.com/v1 |
OPENAI_API_KEY |
anthropic |
Anthropic | api.anthropic.com/v1 |
ANTHROPIC_API_KEY |
openrouter |
OpenRouter | openrouter.ai/api/v1 |
OPENROUTER_API_KEY |
xai |
xAI (Grok) | api.x.ai/v1 |
XAI_API_KEY |
zai |
Z.ai (Zhipu) | api.z.ai/api/paas/v4 |
ZAI_API_KEY |
groq |
Groq | api.groq.com/openai/v1 |
GROQ_API_KEY |
together |
Together AI | api.together.xyz/v1 |
TOGETHER_API_KEY |
mistral |
Mistral AI | api.mistral.ai/v1 |
MISTRAL_API_KEY |
deepseek |
DeepSeek | api.deepseek.com/v1 |
DEEPSEEK_API_KEY |
fireworks |
Fireworks AI | api.fireworks.ai/inference/v1 |
FIREWORKS_API_KEY |
ollama |
Ollama (local) | localhost:11434/v1 |
— |
lmstudio |
LM Studio (local) | localhost:1234/v1 |
— |
vllm |
vLLM (local) | localhost:8000/v1 |
— |
custom |
Any OpenAI-compatible | (you provide) | — |
Each cloud template includes pre-configured default models with aliases and routing tags. Use templates with:
ain providers add --template openai --set-default
ain providers add --template groq --api-key env:GROQ_API_KEY --set-defaultOn first use (or when no providers are configured), AIN launches a 3-phase interactive wizard:
Phase 1: Providers — Add as many providers as you want (loop). Each is tested on the spot. Providers with ultra-fast inference (Groq, Together, Fireworks) are tagged [classifier].
Phase 2: Classifier — Choose an LLM classifier for intelligent routing. AIN recommends ultra-low-latency providers like Groq with Llama 4 Scout (93% accuracy, ~230ms). Or skip and use the built-in heuristic classifier.
Phase 3: Routing Policy — Map each tier (fast, general, reasoning, coding, creative) to a specific model from your configured providers, with tag-based suggestions.
The wizard only runs in interactive terminals (TTY). For non-interactive environments, use ain providers add --template <id> instead.
ain wizard # run or re-run the wizard anytime
ain setup # alias for ain wizardLocated at ~/.ain/config.yaml. Created with ain config init.
version: 1
providers:
local:
kind: openai-compatible
baseUrl: http://localhost:1234/v1
timeoutMs: 60000
models:
- id: liquid/lfm2.5-1.2b
alias: liquid-fast
tags: [local, fast, cheap]
contextWindow: 8192
- id: google/gemma-3n-e4b
alias: gemma-general
tags: [local, general]
- id: qwen3.5-4b-mlx
alias: qwen-reason
tags: [local, reasoning]
openai:
kind: openai-compatible
baseUrl: https://api.openai.com/v1
apiKey: env:OPENAI_API_KEY
models: []
defaults:
provider: local
model: google/gemma-3n-e4b
temperature: 0.7
maxTokens: 2048| Field | Type | Required | Description |
|---|---|---|---|
kind |
"openai-compatible" |
Yes | Provider adapter type |
baseUrl |
string (URL) | Yes | API base URL |
apiKey |
string | No | API key or env:VAR_NAME reference |
defaultHeaders |
Record<string, string> |
No | Custom HTTP headers |
timeoutMs |
number | No | Request timeout (default: 60000) |
models |
ModelConfig[] |
No | Model catalog |
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Model identifier (e.g., gpt-4o) |
alias |
string | No | Short alias for --model flag |
contextWindow |
number | No | Context window size |
maxTokens |
number | No | Max output tokens |
capabilities |
string[] |
No | Model capabilities |
tags |
string[] |
No | Tags for routing (e.g., fast, reasoning, local) |
Place an ain.yaml file in your project directory to override settings per-project:
# ./ain.yaml — checked into the repo
version: 1
providers:
team-server:
kind: openai-compatible
baseUrl: http://team-gpu:1234/v1
defaults:
provider: team-server
model: specific-model
temperature: 0.3Project config merges over ~/.ain/config.yaml. Project providers and defaults take precedence. ain config show and ain doctor indicate when a project overlay is active.
API keys can reference environment variables using the env: prefix:
providers:
openai:
kind: openai-compatible
baseUrl: https://api.openai.com/v1
apiKey: env:OPENAI_API_KEYAIN resolves env:OPENAI_API_KEY to process.env.OPENAI_API_KEY at runtime. The ain doctor command verifies that referenced environment variables are set.
Values are resolved in this order (first wins):
- CLI arguments —
--model,--temperature, etc. - Environment variables — resolved via
env:references - Project config —
./ain.yaml - User config —
~/.ain/config.yaml - Built-in defaults — timeout 60s, etc.
AIN can automatically select the best model for a given task based on prompt analysis:
# Auto-route (uses default policy or heuristic fallback)
ain ask "Classify this email as spam" --route
# → Routes to 'fast' tier (classification task)
ain ask "Write a detailed essay about AI ethics" --route
# → Routes to 'general' tier (generation task)
ain ask "Analyze step by step why this algorithm fails" --route
# → Routes to 'reasoning' tierDefine routing policies in ~/.ain/policies.yaml:
version: 1
defaultPolicy: local-first
policies:
local-first:
description: Route all tasks locally, use fast models for simple tasks
localFirst: true
tiers:
fast:
provider: local
model: liquid/lfm2.5-1.2b
temperature: 0.1
maxTokens: 512
general:
provider: local
model: google/gemma-3n-e4b
temperature: 0.7
maxTokens: 2048
reasoning:
provider: local
model: qwen3.5-4b-mlx
temperature: 0.6
maxTokens: 4096
fallbackChain:
- local/google/gemma-3n-e4b
cloud-premium:
description: Use cloud models for maximum quality
tiers:
fast:
provider: openai
model: gpt-4o-mini
general:
provider: openai
model: gpt-4o
reasoning:
provider: openai
model: o1Use a specific policy:
ain run --prompt "Complex analysis task" --policy cloud-premiumScaffold a starter policies file:
ain routing init-policiesAIN classifies prompts using multilingual keyword patterns (EN, PT, ES, FR, DE, ZH, JA) or an optional LLM classifier for higher accuracy.
| Task Type | Example Keywords | Model Tier |
|---|---|---|
| Classification | classify, categorize, label, is this, spam or | fast (ultra-fast for trivial) |
| Extraction | extract, parse, find all, list all, retrieve | fast |
| Coding | function, implement, debug, refactor, algorithm | coding |
| Creative | poem, story, song, brainstorm, screenplay | creative |
| Reasoning | analyze, prove, solve, step by step, calculate | reasoning |
| Generation | summarize, translate, rewrite, explain, draft | general |
| Unknown | (no match) | general |
Enable an LLM classifier for 93% accuracy (vs 81% heuristic) with multilingual support:
# In ~/.ain/config.yaml
routing:
llmClassifier:
enabled: true
provider: groq # ultra-low latency
model: meta-llama/llama-4-scout-17b-16e-instruct # 93% accuracy, ~230ms
timeoutMs: 3000The LLM classifier determines the tier, then the routing policy maps it to a specific model. On failure, it falls back to the heuristic classifier silently.
AIN includes a built-in catalog of ~30 curated models with tier, cost, and locality data:
ain routing catalog # browse all models
ain routing catalog --tier coding # filter by tier
ain routing catalog --local # local-only models
ain routing catalog --update # fetch latest from OpenRouter APIWhen a provider fails after retries, AIN automatically tries each entry in the fallback chain:
# In policies.yaml
policies:
resilient:
tiers:
general:
provider: local
model: gemma-3n-e4b
fallbackChain:
- local/liquid/lfm2.5-1.2b # try a different local model
- openai/gpt-4o-mini # fall back to cloudFallback chains work with both run() and stream() in the library API.
| Mode | Flag | Output |
|---|---|---|
| Text | (default) | Plain text to stdout |
| Bool | --bool |
Boolean true or false to stdout |
| JSON | --json |
Pretty-printed JSON envelope |
| JSONL | --jsonl |
Compact single-line JSON (pipe-friendly) |
| Schema | --schema file.json |
JSON envelope with schema-validated output field |
| Field | --field key |
Single extracted value (supports dot notation: --field address.city) |
| Stream | --stream |
Tokens written progressively to stdout |
{
"ok": true,
"provider": "local",
"model": "google/gemma-3n-e4b",
"mode": "text",
"output": "The capital of Brazil is Brasília.",
"usage": {
"prompt_tokens": 15,
"completion_tokens": 12,
"total_tokens": 27
}
}When using --json mode or --schema, the mode field is "json" and output contains the parsed object instead of a string.
The --bool flag instructs the model to answer with true or false, and normalizes the output:
ain Is Brad Pitt an actor? --bool
# true
ain Is the Earth flat? --bool
# false
# Combine with --json for structured envelope
ain Is Python dynamically typed? --bool --json
# { "ok": true, ..., "mode": "bool", "output": true }
# Use in shell conditionals
if [ "$(ain Is this valid JSON --bool)" = "true" ]; then
echo "Valid!"
fiBoolean mode accepts true/yes as truthy and false/no as falsy from the model. Any other response produces an error. Combinable with --json/--jsonl for structured output.
AIN can be used as a Node.js/TypeScript library:
npm install @felipematos/ain-cliExecute a prompt and get a structured result.
import { run } from '@felipematos/ain-cli';
const result = await run({
prompt: 'What is the capital of France?',
provider: 'local',
model: 'gemma-general',
});
console.log(result.output); // "Paris"
console.log(result.ok); // true
console.log(result.usage); // { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 }interface RunOptions {
prompt: string; // The prompt to send
provider?: string; // Provider name (uses default if omitted)
model?: string; // Model ID or alias (uses default if omitted)
system?: string; // System prompt
temperature?: number; // Sampling temperature
maxTokens?: number; // Max output tokens
jsonMode?: boolean; // Request JSON output
boolMode?: boolean; // Request boolean true/false output
schema?: object; // JSON Schema for validation
noThink?: boolean; // Suppress reasoning blocks
maxRetries?: number; // Retry attempts (default: 3)
timeoutMs?: number; // Timeout in milliseconds
fallbackChain?: Array<{ // Alternate models if primary fails
provider: string;
model: string;
}>;
}interface RunResult {
ok: boolean; // Whether the call succeeded
provider: string; // Provider that handled the request
model: string; // Model that was used
output: string; // Raw text output
parsedOutput?: unknown; // Parsed JSON/boolean (when jsonMode, boolMode, or schema is set)
usage?: { // Token usage (if reported by provider)
prompt_tokens: number;
completion_tokens: number;
total_tokens: number;
};
error?: string; // Error message (when ok is false)
}Stream tokens as an async generator.
import { stream } from '@felipematos/ain-cli';
for await (const token of stream({ prompt: 'Tell me a story' })) {
process.stdout.write(token);
}Streaming supports fallback chains — if the primary provider fails, AIN transparently retries with the next candidate.
Get an intelligent routing decision without executing.
import { route, run } from '@felipematos/ain-cli';
const decision = route({ prompt: 'Classify this email as spam' });
// {
// provider: 'local',
// model: 'liquid/lfm2.5-1.2b',
// tier: 'fast',
// rationale: 'Policy routing: tier=fast, policy tier config',
// fallbackChain: [{ provider: 'local', model: 'google/gemma-3n-e4b' }]
// }
// Execute with the routing decision
const result = await run({
prompt: 'Classify this email as spam',
provider: decision.provider,
model: decision.model,
fallbackChain: decision.fallbackChain,
});import {
loadConfig, // Load merged config (user + project overlay)
loadUserConfig, // Load ~/.ain/config.yaml only
saveConfig, // Write config to ~/.ain/config.yaml
initConfig, // Create initial config
addProvider, // Add a provider to config
removeProvider, // Remove a provider
resolveModel, // Resolve model ID or alias to actual model ID
resolveProvider, // Resolve provider by name
configExists, // Check if config file exists
getConfigPath, // Get config file path
getConfigDir, // Get config directory (~/.ain/)
} from '@felipematos/ain-cli';import {
createAdapter, // Create an OpenAI-compatible provider adapter
classifyTask, // Classify a prompt into a task type
estimateComplexity, // Estimate prompt complexity (low/medium/high)
runDoctorChecks, // Run health checks programmatically
withRetry, // Retry a function with exponential backoff
isTransientError, // Check if an error is retryable
stripMarkdownFences, // Remove ```json fences from model output
cleanModelOutput, // Remove think blocks and EOS tokens
loadPolicies, // Load routing policies from disk
simulateRoute, // Dry-run routing decision
listProviders, // Get configured provider names
} from '@felipematos/ain-cli';All types are exported for use in TypeScript projects:
import type {
// Configuration
AinConfig,
ProviderConfig,
ModelConfig,
DefaultsConfig,
// Execution
RunOptions,
RunResult,
// Routing
RoutingRequest,
RoutingDecision,
RoutingPolicy,
PolicyFile,
TierConfig,
ModelTier, // 'fast' | 'general' | 'reasoning' | 'premium'
TaskType, // 'classification' | 'extraction' | 'generation' | 'reasoning' | 'unknown'
// Doctor
CheckResult,
} from '@felipematos/ain-cli';AIN includes a multi-stage Dockerfile for containerized usage:
# Build the image
docker build -t ain .
# Run a command
docker run --rm ain ask "Hello, world"
# With environment variables for API keys
docker run --rm -e OPENAI_API_KEY="sk-..." ain ask "Hello" --provider openai
# Mount a config directory
docker run --rm -v ~/.ain:/root/.ain ain ask "Hello"
# Mount a project directory with ain.yaml overlay
docker run --rm -v $(pwd):/work -w /work -v ~/.ain:/root/.ain ain run --prompt "Analyze" --jsonThe Docker image uses node:20-alpine and includes only production dependencies.
AIN follows a six-layer architecture that separates concerns cleanly:
┌─────────────────────────────────────────────┐
│ CLI Interface │ ← argument parsing, stdin/file input
├─────────────────────────────────────────────┤
│ Config / Registry │ ← providers, models, aliases, defaults
├─────────────────────────────────────────────┤
│ Provider Adapters │ ← OpenAI-compatible normalization
├─────────────────────────────────────────────┤
│ Execution Engine │ ← request building, API calls, retry
├─────────────────────────────────────────────┤
│ Output Renderer │ ← text, JSON, JSONL, schema validation
├─────────────────────────────────────────────┤
│ Routing Engine │ ← task classification, policy routing
└─────────────────────────────────────────────┘
- Routing is separate from execution — "which model" is a different concern from "how to call it"
- OpenAI-compatible adapter first — a single adapter unlocks LM Studio, Ollama, vLLM, OpenAI, and many more
- Shell-safe by default — clean stdout for piping, diagnostics to stderr, meaningful exit codes
- Minimal dependencies — only 3 production deps: commander, yaml, zod
src/
├── cli/ # Command handlers, preprocessor, templates, wizard
├── config/ # Config loading, Zod schemas, secret resolution, project overlays
├── providers/ # OpenAICompatibleAdapter (chat, stream, health check, model listing)
├── execution/ # run(), stream(), schema validation, think-block filtering
├── output/ # Output formatting (text, JSON envelope, JSONL)
├── routing/ # route(), simulateRoute(), classifyTask(), policy loading
├── doctor/ # Health checks (config, auth, connectivity)
├── shared/ # Retry logic with exponential backoff
└── index.ts # Library exports
- Node.js >= 18
- npm
git clone https://github.com/felipematos/ain.git
cd ain
npm install
npm link # Install CLI globally for development| Command | Description |
|---|---|
npm run build |
Compile TypeScript to dist/ |
npm run test |
Run all tests (Vitest) |
npm run test:watch |
Run tests in watch mode |
npm run lint |
Lint source code |
npm run typecheck |
Type-check without emitting |
npm run dev -- <args> |
Run CLI from source (via tsx) |
# Run all unit tests
npm test
# Watch mode
npm run test:watch
# Skip integration tests (require a running provider)
SKIP_INTEGRATION=1 npm testain/
├── src/ # TypeScript source
├── test/ # Test suite (Vitest)
├── dist/ # Compiled output (generated)
├── docs/ # Examples and plans
│ └── examples/ # Sample config and policy files
├── packages/ # Related packages
│ └── openclaw-plugin-ain/ # OpenClaw integration
├── package.json
├── tsconfig.json
├── vitest.config.ts
├── Dockerfile
├── LICENSE
└── README.md
| Code | Meaning |
|---|---|
0 |
Success |
1 |
Error (invalid config, schema validation failure, provider unreachable, etc.) |
Errors are written to stderr. When using --json or --jsonl, errors are also formatted as JSON on stderr:
{ "ok": false, "error": "Provider returned an empty response" }MIT License
Copyright (c) 2025 Felipe Matos
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Created by Felipe Matos, CEO at 10K Digital — an A.I. Accelerator for Businesses.