Entole is a multi-provider AI CLI for chat and embeddings by Metisse. It provides a unified interface to interact with multiple AI providers including OpenAI, Anthropic, Ollama, and OpenRouter through a simple command-line interface.
- 🔄 Multi-provider: Support for OpenAI, Anthropic, Ollama, and OpenRouter
- 🎯 Unified interface: Single CLI for chat and embeddings across providers
- ⚙️ Flexible configuration: Environment variables, config files, and CLI flags
- 📊 Built-in observability: Optional timing collection and structured output
- 💻 Developer-friendly: JSON output mode for scripting and automation
- 🛡️ Open source: Apache 2.0 licensed with upstream attribution
- Node.js version 18 or higher
- macOS, Linux, or Windows
# Clone the repository
git clone https://github.com/StellarSk8board/Entole.git
cd entole
# Install dependencies
npm install
# Build the CLI
npm run build
# Install globally
npm install -g .
# Install dependencies
npm install
# Run in development mode
npm run dev
# Run tests
npm test
# Run with coverage
npm run test:coverage
- OpenAI: GPT-4, GPT-3.5-turbo, and embedding models
- Anthropic: Claude 3.5 Sonnet, Claude 3 Opus, and other Claude models
- Ollama: Local models including Llama 2, Code Llama, and custom models
- OpenRouter: Access to multiple providers through a single API
- Chat: Interactive conversations with AI models
- Embeddings: Generate vector embeddings for text
- Provider Management: List available providers and their capabilities
- Configuration Doctor: Validate and troubleshoot configuration
- Flexible Configuration: Environment variables, JSON/YAML config files, CLI flags
- Multiple Output Formats: Human-friendly text or structured JSON envelopes
- Streaming Support: Real-time streaming responses for chat operations
- Error Handling: Normalized error responses with helpful hints
Entole supports multiple configuration methods with the following precedence (highest to lowest):
- Environment Variables (highest priority)
- Configuration Files (middle priority)
- CLI Flags (lowest priority)
# Provider API Keys
export OPENAI_API_KEY="sk-..."
export ANTHROPIC_API_KEY="sk-ant-..."
export OPENROUTER_API_KEY="sk-or-..."
# Ollama Configuration
export OLLAMA_HOST="http://localhost:11434"
export OLLAMA_MODEL="llama2"
# Default Providers
export ENTOLE_DEFAULT_CHAT_PROVIDER="openai"
export ENTOLE_DEFAULT_EMBEDDINGS_PROVIDER="openai"
# Output Configuration
export ENTOLE_OUTPUT_FORMAT="human" # or "json"
export ENTOLE_STREAMING="true"
# Observability
export ENTOLE_TIMINGS="false"
Create entole.config.json
or entole.config.yaml
in your project root or ~/.entole/
:
{
"providers": {
"default": {
"chat": "openai",
"embeddings": "openai"
},
"openai": {
"apiKey": "sk-...",
"baseUrl": "https://api.openai.com/v1",
"defaultModel": "gpt-4o-mini"
},
"anthropic": {
"apiKey": "sk-ant-...",
"defaultModel": "claude-3-5-sonnet-20241022"
},
"ollama": {
"host": "http://localhost:11434",
"defaultModel": "llama2"
},
"openrouter": {
"apiKey": "sk-or-...",
"defaultModel": "openai/gpt-4o-mini"
}
},
"output": {
"format": "human",
"streaming": true
},
"observability": {
"timings": false
}
}
# Basic chat
entole chat "Hello, how are you?"
# Use specific provider and model
entole chat "Explain quantum computing" --provider anthropic --model claude-3-5-sonnet-20241022
# Stream response
entole chat "Write a story" --stream
# Read prompt from file
entole chat --file prompt.txt
# JSON output for scripting
entole chat "What is 2+2?" --json
# Basic embedding
entole embed "Hello world"
# Multiple texts
entole embed "Text 1" "Text 2" "Text 3"
# From file
entole embed @document.txt
# Specific provider
entole embed "Sample text" --provider openai --model text-embedding-3-large
# List available providers
entole providers list
# Check configuration
entole config doctor
# Simple question
entole chat "What is the capital of France?"
# Code generation
entole chat "Write a Python function to calculate fibonacci numbers"
# File-based prompt
echo "Explain this code" > prompt.txt
entole chat --file prompt.txt
# Streaming with specific model
entole chat "Tell me a story" --provider anthropic --stream
# Single text embedding
entole embed "Machine learning is fascinating"
# Multiple texts
entole embed "First text" "Second text"
# From file
entole embed @README.md
# JSON output for processing
entole embed "Sample text" --json | jq '.data.vectors[0] | length'
Chat with AI models using natural language.
Arguments:
prompt
(optional) - The text prompt to send to the AI
Options:
-p, --provider <provider>
- AI provider (openai, anthropic, ollama, openrouter)-m, --model <model>
- Specific model to use-f, --file <file>
- Read prompt from file-s, --stream
- Stream the response in real-time-t, --temperature <temp>
- Temperature for response generation (0.0-2.0)--top-p <top_p>
- Top-p for response generation (0.0-1.0)--max-tokens <tokens>
- Maximum tokens to generate--json
- Output in JSON format
Examples:
entole chat "Hello world"
entole chat --file prompt.txt --provider anthropic --stream
entole chat "Explain AI" --json --max-tokens 100
Generate embeddings for text input.
Arguments:
input
- Text to embed (or @filename to read from file)
Options:
-p, --provider <provider>
- AI provider to use-m, --model <model>
- Model to use for embeddings--json
- Output in JSON format
Examples:
entole embed "Hello world"
entole embed @document.txt --provider openai
entole embed "Sample text" --json
List available providers and their capabilities.
Options:
--json
- Output in JSON format
Validate configuration and provider setup.
Options:
--json
- Output results in JSON format
Entole can also be used as a Node.js library:
import {
ProviderRegistry,
OpenAIAdapter,
AnthropicAdapter,
loadConfig
} from 'entole';
// Load configuration
const { config } = await loadConfig();
// Create provider registry
const registry = new ProviderRegistry();
registry.register(new OpenAIAdapter(config.providers?.openai));
registry.register(new AnthropicAdapter(config.providers?.anthropic));
// Use providers
const provider = registry.resolve('openai', 'chat');
const response = await provider.invokeChat({
messages: [{ role: 'user', content: 'Hello!' }]
});
console.log(response.text);
Base interface that all AI providers implement:
interface ProviderAdapter {
key: string; // Provider identifier
label: string; // Human-readable name
capabilities: CapabilityFlags; // Supported features
// Core methods
invokeChat(params: ChatParams): Promise<ChatResponse>;
invokeChatStream(params: ChatParams): AsyncIterable<ChatStreamChunk>;
invokeEmbeddings(params: EmbeddingParams): Promise<EmbeddingResponse>;
getModels(): Promise<ModelInfo[]>;
normalizeError(error: unknown): NormalizedError;
}
Parameters for chat operations:
interface ChatParams {
model?: string;
messages: Array<{
role: 'system' | 'user' | 'assistant';
content: string;
}>;
temperature?: number;
top_p?: number;
max_tokens?: number;
}
Response from chat operations:
interface ChatResponse {
text: string;
meta?: {
model?: string;
usage?: {
prompt_tokens?: number;
completion_tokens?: number;
total_tokens?: number;
};
finish_reason?: string;
};
}
Parameters for embedding operations:
interface EmbeddingParams {
model?: string;
input: string | string[];
}
Response from embedding operations:
interface EmbeddingResponse {
vectors: number[][];
meta?: {
model?: string;
usage?: {
prompt_tokens?: number;
total_tokens?: number;
};
};
}
Main configuration interface:
interface EntoleConfig {
providers?: {
default?: {
chat?: string;
embeddings?: string;
};
openai?: {
apiKey?: string;
baseUrl?: string;
defaultModel?: string;
};
anthropic?: {
apiKey?: string;
defaultModel?: string;
};
ollama?: {
host?: string;
defaultModel?: string;
};
openrouter?: {
apiKey?: string;
defaultModel?: string;
};
};
output?: {
format?: 'human' | 'json';
streaming?: boolean;
};
observability?: {
timings?: boolean;
};
}
Default human-friendly output with colors and formatting:
Hello! How can I help you today?
─── Model: gpt-4o-mini • Usage: 10 prompt, 9 completion, 19 total tokens ───
Structured JSON envelope for programmatic use:
{
"ok": true,
"command": "chat",
"provider": "openai",
"model": "gpt-4o-mini",
"data": {
"text": "Hello! How can I help you today?",
"meta": {
"model": "gpt-4o-mini",
"usage": {
"prompt_tokens": 10,
"completion_tokens": 9,
"total_tokens": 19
},
"finish_reason": "stop"
}
},
"meta": {
"requestId": "req_1703123456789_abc123def",
"timingsMs": {
"total": 1500,
"provider": 1200,
"config": 50
}
}
}
All errors are normalized to a consistent format:
interface NormalizedError {
provider: string;
type: 'auth' | 'rate_limit' | 'network' | 'bad_request' | 'not_implemented' | 'internal';
message: string;
hint?: string;
httpStatus?: number;
retryable: boolean;
}
Example error output:
{
"ok": false,
"command": "chat",
"data": null,
"meta": {
"error": {
"provider": "openai",
"type": "auth",
"message": "Invalid API key",
"hint": "Check your OPENAI_API_KEY environment variable. Get your API key from https://platform.openai.com/api-keys",
"httpStatus": 401,
"retryable": false
}
}
}
See examples/basic-usage.ts for comprehensive usage examples including:
- Basic chat operations
- Streaming responses with timing
- Embedding generation
- Provider registry usage
- Configuration loading
Run the examples:
# Install dependencies and build
npm install && npm run build
# Run examples (requires API keys)
node examples/basic-usage.ts
When using Entole as a library, the following APIs are available:
import {
CapabilityFlags,
ChatParams,
ChatResponse,
ChatStreamChunk,
EmbeddingParams,
EmbeddingResponse,
ModelInfo,
NormalizedError,
ProviderAdapter,
OutputEnvelope
} from 'entole';
import {
ProviderRegistry,
BaseProviderAdapter,
registerProvider,
getProvider,
resolveProvider,
getProviderInfo
} from 'entole';
import {
OpenAIAdapter,
AnthropicAdapter,
OllamaAdapter,
OpenRouterAdapter,
MistralAdapter,
CohereAdapter
} from 'entole';
import {
loadConfig,
EntoleConfig,
ConfigValidationError,
ConfigFileError
} from 'entole';
import {
formatChatResponse,
formatEmbeddingResponse,
formatProviderList,
formatError,
StreamingOutput,
createSuccessEnvelope,
createErrorEnvelope,
getOutputFormat,
outputContent,
outputError
} from 'entole';
import {
initializeTimings,
withTiming,
closeTimings,
TimingCollector,
TimingRecord
} from 'entole';
This project includes Kiro Agent Hooks for automated development workflows. These hooks help maintain code quality and assist with development tasks:
- Trigger: When TypeScript files in
src/
are saved - Actions:
- Formats the saved file with Prettier
- Lints the saved file with ESLint
- Runs related test files if they exist
- Auto-approved: Yes (runs automatically)
- Trigger: When new TypeScript files are created in
src/
- Actions: Creates matching
.test.ts
files with basic Vitest structure - Auto-approved: No (requires review)
- Trigger: When
package.json
is saved - Actions:
- Analyzes dependency changes
- Fetches release notes for updated packages
- Updates CHANGELOG.md with dependency changes
- Auto-approved: No (requires review)
- Trigger: Manual button click
- Actions:
- Scans codebase for TODO and FIXME comments
- Identifies items added today
- Categorizes by priority and complexity
- Generates actionable summary report
- Auto-approved: No (requires review)
To use these hooks, you need:
- Kiro IDE with Agent Hooks support
- Node.js 18+ with npm scripts configured
- Git repository for change tracking
- Brave MCP server configured (for dependency tracking)
Hooks are defined in .kiro/hooks/
and use YAML configuration format. Each hook specifies:
- Trigger conditions (file patterns, manual buttons)
- Execution steps (commands or AI agent prompts)
- Settings (auto-approval, timeouts, etc.)
Entole includes comprehensive test coverage:
# Run all tests
npm test
# Run with coverage
npm run test:coverage
# Run unit tests only
npm run test:unit
# Run integration tests (requires Ollama for some tests)
npm run test:integration
# Watch mode for development
npm run test:watch
Some integration tests require external services:
- Ollama tests: Require Ollama running at
OLLAMA_HOST
(default:http://localhost:11434
) - Provider tests: May require API keys for full testing (use environment variables)
Tests will automatically skip if required services are unavailable.
We welcome contributions! Entole is fully open source (Apache 2.0), and we encourage the community to:
- Report bugs and suggest features
- Improve documentation
- Submit code improvements
- Add new provider adapters
See our Contributing Guide for development setup, coding standards, and how to submit pull requests.
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature
- Make your changes and add tests
- Run the test suite:
npm test
- Run linting:
npm run lint
- Format code:
npm run format
- Submit a pull request
To add a new AI provider:
- Create a new adapter class extending
BaseProviderAdapter
- Implement all required methods (
invokeChat
,invokeEmbeddings
, etc.) - Add comprehensive tests
- Register the provider in
src/providers/index.ts
- Update documentation
- GitHub Repository - Source code and issues
- NPM Package - Package registry
- GitHub Issues - Report bugs or request features
- Security Advisories - Security updates
- License: Apache License 2.0
- Attribution: This project contains substantial changes by Metisse to the original work by Google LLC
- Security: Security Policy
This project is based on the Gemini CLI by Google LLC, with substantial modifications by Metisse. The original work is licensed under the Apache License 2.0. See NOTICE for full attribution details.
Built with ❤️ by Metisse