@dreamtree-org/dhruthi
v0.3.9
Published
CLI for DigitalOcean Serverless Inference and Inference Router
Maintainers
Readme
dhruthi (dhruthi)
A multi-provider AI CLI that works instantly with free models — no key, no setup, no friction.
npm install -g @dreamtree-org/dhruthi
dhruthi # starts instantly, uses free Pollinations model
dhruthi "Explain closures in TypeScript" # one-shotdhruthi started as a DigitalOcean inference CLI and is now a provider-agnostic tool that talks to OpenAI, Anthropic, Google Gemini, Groq, DeepSeek, Ollama, OpenRouter, and 25+ more — with automatic wire-protocol translation so --format json and sessions behave the same regardless of which provider answered.
Why dhruthi?
| | dhruthi | opencode | aider | Claude Code |
|---|---|---|---|---|
| Zero-config start | ✅ free Pollinations | ✅ via Copilot | ❌ needs key | ❌ needs key |
| Built-in providers | 33 + gateways | 75+ via models.dev | many via litellm | 1 (Anthropic) |
| Wire translation | OpenAI / Anthropic / Gemini | OpenAI shape | via litellm | Anthropic |
| Multi-model routing | 6 strategy kinds, fallback, round-robin | multi-session | ❌ | ❌ |
| Parallel agents | ✅ /parallel | multi-session | ❌ | ❌ |
| Goal & plan tracking | ✅ /goal + /plan | ❌ | ❌ | /todo |
| Hybrid context recall | SQLite graph + vector | linear history | repomap | project context |
| Hooks / plugins | JS/TS at 5 lifecycle pts | skills + MCP | ❌ | hooks |
| Dev commands | 11 built-in (/exec, /grep, /diff, …) | via shell tool | via /run | via bash |
| Image input | ✅ all 3 wire standards | ✅ | ✅ | ✅ |
| Auto-slash | detects intent, injects context | ❌ | ❌ | auto-tools |
| Dependencies | 2 (commander + better-sqlite3) | 100+ | 30+ | N/A (binary) |
Quick start
Zero configuration (free, no key)
npm install -g @dreamtree-org/dhruthi
dhruthi # interactive REPL, uses free Pollinations model
dhruthi "What is a monad?" # one-shotThat's it. Pollinations' openai-fast model (GPT-OSS 20B) serves every prompt for free, rate-limited. No account, no key, no setup.
With a provider key
Already have a key in your environment? No configuration needed:
export OPENAI_API_KEY=sk-…
dhruthi --provider openai "explain closures"
export GROQ_API_KEY=gsk_…
dhruthi --provider groq "write a haiku about SQL"Or add one interactively:
dhruthi provider add anthropic # pastes key, auto-activates
dhruthi provider use groq # switch between configured providersNo model configured?
The CLI picks one, tells you, and remembers it:
$ dhruthi "Bonjour, ça va?"
auto-selected model: openai-gpt-oss-20b (tier gpt-oss, smallest ≥20B)
saved as default_model — change it with: dhruthi config set default_model <id>
Ça va bien, merci !It chooses the cheapest capable model rather than the largest — this is spending money on a decision you did not make. The choice is deterministic, announced on stderr, and persisted. For CI: --no-auto-model or DHRUTHI_NO_AUTO_MODEL=1.
Interactive REPL
$ dhruthi
dhruthi 0.3.5 — interactive. /help for commands, /exit or Ctrl-D to leave.
provider pollinations · model (auto) · session repl · 0 turns
›All slash commands
Session & model:
| command | |
|---|---|
| /model [id] | show or switch model (/model auto to clear) |
| /router [name] | route through DO inference router |
| /session [name] | show, switch, or start a conversation |
| /sessions | list saved conversations |
| /clear | forget conversation history |
| /system <text> | set the system prompt |
| /strategy [name] | route turns across several models |
| /switch <model> | hand conversation to another model |
| /compact [n] | compress older turns, keeping last n exchanges |
| /retry | re-send the last message |
| /parallel [on\|off] | query all strategy candidates simultaneously |
| /goal [text] | set or show the high-level objective |
| /plan [cmd] | step-by-step plan (generate|add|done|undo|edit|clear) |
| /recall | search past context across all sessions |
Development:
| command | |
|---|---|
| /file <path> | read a file into the conversation context |
| /dir [path] | list directory tree |
| /grep <pattern> | search codebase (requires ripgrep) |
| /exec <cmd> | run a shell command, capture output |
| /diff [--staged] | show git diff |
| /log [-n 5] | show recent git log |
| /lint | run the project linter |
| /test [name] | run tests (filters by test name) |
| /review [file] | ask the model to review code |
| /explain <code> | ask the model to explain code |
| /undo | revert all unstaged file changes |
| /save [file] | write transcript to a file |
Meta:
| command | |
|---|---|
| /help, /exit | |
Ctrl-C cancels the reply and returns to the prompt. Ctrl-D exits. All chrome goes to stderr.
Goal-driven execution
dhruthi supports structured, goal-oriented workflows — set an objective and let the model build a step-by-step plan:
› /goal Build a REST API for user management
Goal set.
› /plan generate
Generating plan…
⤷ openai-fast
Plan generated (5 items):
1. Set up Express project structure
2. Define user model and database schema
3. Implement CRUD endpoints
4. Add input validation and error handling
5. Write integration tests
Mark complete with /plan done <n>
› /plan done 1
✓ 1. Set up Express project structureThe goal and plan are composed into the system prompt so the model always sees them. The status line updates: goal "Build a REST API" · plan 1/5.
Parallel agents
With an active strategy, /parallel on fires all candidates at once and displays every reply:
› /strategy code-review
› /parallel on
Parallel mode on — all strategy candidates will be queried simultaneously.
› Review this diff for bugs.
⤷ groq/llama-3.3-70b …
⤷ cerebras/llama3.1-8b …
⤷ openai/gpt-4o-mini …
… all three replies stream simultaneously …Sequential fallback (/parallel off) is the default — one candidate at a time, falling back on failure.
Auto-slash
dhruthi detects intent from your prompt and automatically injects context before the model sees it:
| Prompt pattern | Auto-injected context |
|---|---|
| "the tests are failing" | npm test output |
| "refactor the auth module" | project file listing |
| "where is the login handler?" | ripgrep search results |
| "commit and push" | git diff --stat |
| "how does the token validation work?" | relevant source file contents |
| long prompt referencing multiple files | auto-goal + plan suggestion |
Toggle: dhruthi config set auto_slash off
Providers
One profile is one connection — base URL, wire standard, credential, and defaults. 33 presets built in.
dhruthi provider catalog # show all 33 built-in presets
dhruthi provider list # what you have configured
dhruthi provider add openai # paste key, auto-activates
dhruthi provider use groq # switch active
dhruthi provider status # active provider details
dhruthi provider test # GET /models + 1-token smoke test
dhruthi provider remove groqThree wire protocols
Translation is transparent — --format json, streaming, sessions, and AGENTS.md behave the same regardless of which standard answered:
| Standard | Endpoint | System prompt | Auth header |
|---|---|---|---|
| openai | /v1/chat/completions | system message role | Authorization: Bearer |
| anthropic | /v1/messages | top-level system field | x-api-key + anthropic-version |
| gemini | :generateContent | systemInstruction | x-goog-api-key |
Image input (multimodal)
--attach works across all three standards — base64 data URLs and HTTP URLs are auto-translated to each provider's native image format:
dhruthi --attach screenshot.png "describe this UI"
dhruthi --provider anthropic --attach photo.jpg "what's in this image?"
dhruthi --provider gemini --attach diagram.png "explain this architecture"Environment keys
Each preset knows its provider's conventional variable, so an exported key needs no config:
export OPENAI_API_KEY=sk-…
export GROQ_API_KEY=gsk_…
export ANTHROPIC_API_KEY=sk-ant-…
dhruthi --provider openai "hello" # picks up OPENAI_API_KEY automaticallyPrecedence: --api-key → DHRUTHI_API_KEY → provider's own env var → stored profile key.
Gateways
Three gateway presets reach hundreds of upstreams through one endpoint:
npx omniroute@latest # local, 290+ providers
dhruthi provider add omniroute
dhruthi provider add openrouter # 400+ models, :free suffix for free ones
dhruthi provider add litellm # local proxy, any providerFree models (zero cost)
dhruthi works with free models across multiple providers. Pick your level:
Zero configuration — start instantly
dhruthi # uses Pollinations' openai-fast (GPT-OSS 20B), no keyFree API key — no payment required
| Provider | Models | Key env var | Setup |
|----------|--------|-------------|-------|
| Groq | llama-3.3-70b, qwen-coder, gemma, deepseek, mixtral | GROQ_API_KEY | console.groq.com |
| Cerebras | llama3.1-8b, llama-4, mixtral | CEREBRAS_API_KEY | cloud.cerebras.ai |
| Google Gemini | gemini-2.0-flash, gemini-2.5-pro, gemini-3.5-flash | GEMINI_API_KEY | aistudio.google.com |
| NVIDIA NIM | nemotron-3, llama variants | NVIDIA_API_KEY | build.nvidia.com |
| DeepSeek | deepseek-chat, deepseek-reasoner, deepseek-v4 | DEEPSEEK_API_KEY | platform.deepseek.com |
| DeepInfra | llama, qwen, mistral, gemma | DEEPINFRA_API_KEY | deepinfra.com |
| OpenRouter | 400+ models, 30+ :free suffix (zero billing) | OPENROUTER_API_KEY | openrouter.ai |
| IO.NET | 17 models, free tier | IONET_API_KEY | ai.io.net |
| Cloudflare AI | llama, gemma, mistral on global edge | CLOUDFLARE_API_KEY | developers.cloudflare.com |
# One key, one provider — instant setup
export GROQ_API_KEY=gsk_…
dhruthi --provider groq "hello"
# OpenRouter :free models — zero billing
export OPENROUTER_API_KEY=sk-or-…
dhruthi --provider openrouter --model google/gemini-2.0-flash-001:free "explain TypeScript generics"
dhruthi --provider openrouter --model deepseek/deepseek-chat:free "write a haiku about Rust"OpenCode Zen — subscription, 87 models, many free
OpenCode Zen exposes an OpenAI-compatible API at https://opencode.ai/zen/v1 with 87 models including Claude, GPT, Gemini, DeepSeek, Kimi, GLM, Qwen, and many with -free suffix:
dhruthi provider add opencode-zen
# Paste your OpenCode Zen API key from https://opencode.ai/auth
# Free suffix models — zero additional cost:
dhruthi --provider opencode-zen --model deepseek-v4-flash-free "hello"
dhruthi --provider opencode-zen --model glm-5-free "explain this"
dhruthi --provider opencode-zen --model qwen3.6-plus-free "write a function"
dhruthi --provider opencode-zen --model kimi-k2.5-free "review this code"
dhruthi --provider opencode-zen --model hy3-free "summarize this"
dhruthi --provider opencode-zen --model minimax-m3-free "translate"All OpenCode Zen free models: deepseek-v4-flash-free, glm-5-free, glm-4.7-free, hy3-free, hy3-preview-free, kimi-k2.5-free, ling-2.6-flash-free, ling-3.0-flash-free, ling-3.0-tiny-free, longcat-2.0-free, mimo-v2-flash-free, mimo-v2-omni-free, mimo-v2-pro-free, mimo-v2.5-free, minimax-m2.1-free, minimax-m2.5-free, minimax-m3-free, nemotron-3-super-free, nemotron-3-ultra-free, north-mini-code-free, qwen3.6-plus-free, ring-2.6-1t-free, trinity-large-preview-free, laguna-s-2.1-free
OpenCode Go — subscription, 24 curated models
dhruthi provider add opencode-go
# Paste your OpenCode API key
dhruthi --provider opencode-go --model qwen3.7-max "explain this architecture"Local models — always free
dhruthi provider add ollama # Ollama: localhost:11434/v1
dhruthi provider add lmstudio # LM Studio: localhost:1234/v1
dhruthi provider add vllm # vLLM: localhost:8000/v1
dhruthi provider add llamacpp # llama.cpp: localhost:8080/v1Free model strategies — combine for resilience
# Fire-and-forget across 3 free providers
dhruthi strategy add resilient --preset resilient
# OpenRouter :free only — 100% free, zero billing
dhruthi strategy add oss --preset oss-champion
# OpenCode Zen free models only
dhruthi strategy add zen-free -k cost-optimized \
-c opencode-zen/deepseek-v4-flash-free \
-c opencode-zen/glm-5-free \
-c opencode-zen/qwen3.6-plus-free
# Activate and use
dhruthi strategy use oss
dhruthi "refactor this function"
# Automatic fallback if any model rate-limitsStrategies — multi-model routing
A strategy is a named set of candidate models plus a rule for ordering them. It makes free tiers usable: try the free one, and when it rate-limits, continue elsewhere.
dhruthi strategy add resilient -c groq/llama-3.3-70b -c cerebras/llama3.1-8b
dhruthi strategy use resilient
dhruthi "explain closures"⤷ groq/llama-3.3-70b failed (429); continuing on cerebras/llama3.1-8b
→ strategy "resilient" → cerebras/llama3.1-8b (attempt 2 of 2)| kind | ordering |
|---|---|
| priority | as declared; fall back only on failure |
| cost-optimized | free → free-tier → paid |
| round-robin | rotate per turn — stretches several free tiers past rate limits |
| least-used | fewest recent requests first |
| weighted | proportional, -c groq/model:7 |
| random | shuffled |
Two billing protections: fallback never re-issues a request that may already have run, and automatic fallback will not escalate to a paid provider unless --allow-paid is set.
Built-in strategy presets
Six presets ship with dhruthi, each optimized for a specific coding workflow with free models:
dhruthi strategy add review --preset redundant # install a built-in preset
dhruthi strategy use review # activate it
# or in the REPL:
/strategies redundant # list, then activate| Preset | Kind | Candidates | Use case |
|--------|------|-----------|----------|
| resilient | cost-optimized | groq/llama-3.3-70b → cerebras/llama3.1-8b → openrouter :free | Never fail — rotate through 3 free providers |
| redundant | priority | groq/llama + cerebras/llama + gemini-flash | Code review: fire multiple models, compare results |
| fast-first | least-used | groq/llama + cerebras/llama | Stretches two free tier quotas across turns |
| heavy-code | priority | gemini-2.5-pro → groq/llama → cerebras/llama | Complex tasks: strongest free models first |
| oss-champion | cost-optimized | openrouter :free models only | 100% free — no API key needed on any provider |
| multi-review | priority | groq/llama + cerebras/llama + gemini-flash | /parallel on — all three models review simultaneously |
# Redundant code review workflow
dhruthi strategy add review --preset redundant
dhruthi strategy use review
/parallel on
/diff
"Review this diff for bugs, style, and security"
→ groq, cerebras, and gemini review simultaneously
# Zero-key development (just OpenRouter API key)
dhruthi strategy add oss --preset oss-champion
dhruthi strategy use oss
→ All models are :free — no billing on any providerHybrid context recall
dhruthi uses a SQLite database with entity graphs and vector embeddings to retrieve relevant context from all past sessions — not just the current one.
User: "fix the JWT expiry bug in auth.ts"
→ Entity extraction: "JWT", "auth.ts", "bug"
→ Graph traversal: find turns referencing "auth.ts" across sessions
→ Vector search: cosine similarity on prompt embedding
→ Hybrid score: 0.5×vector + 0.3×graph + 0.2×freshness
→ Inject "Relevant past context" into system promptEmbeddings are generated via the active provider's embedding API (text-embedding-3-small) and stored in the SQLite DB at ~/.local/state/dhruthi/context.db. Entity extraction is regex-based (file paths, function names, error codes, URLs, tech terms). The DB survives across REPL sessions and grows automatically.
Manual recall: /recall "JWT authentication"
Hook injection system
Extend dhruthi with plugins at five lifecycle points. Create a file in ~/.config/dhruthi/hooks/ and enable it:
dhruthi config set hooks my-hook,slack-notifyHook types:
.ts/.js— ESM modules exporting lifecycle handlers.sh— spawned shell scripts (simpler, no JS needed)
Lifecycle events:
// ~/.config/dhruthi/hooks/my-hook.ts
export function onStartup(ctx: HookContext) { ctx.log("REPL started"); }
export function onPreTurn(ctx: HookContext) {
// modify context, inject data, run external tools before model call
}
export function onPostTurn(ctx: HookContext) {
// post-process reply, send notifications, log metrics
}
export function onSlashCommand(ctx: HookContext) {
// intercept or extend any slash command
if (ctx.slashCommand === "/review") { /* custom logic */ }
}
export function onShutdown(ctx: HookContext) { /* cleanup */ }Hook failures never crash the REPL — each handler is wrapped in try/catch.
Switching models mid-conversation
When a conversation moves to a different model (by fallback or /switch), the new model would otherwise read the previous model's replies as its own. The handoff system prevents this:
› /switch claude-haiku-4
Switched to claude-haiku-4 (from llama-3.3-70b). 12 message(s) handed over.The incoming model sees attributed history — previous assistant turns are marked [assistant · llama-3.3-70b] — and a handoff preamble that prevents claiming work it didn't do. User turns are never rewritten, code is never summarized.
Authentication (DigitalOcean)
DigitalOcean issues two credentials that are not interchangeable. Commands that only exist on DO (routers, auth, setup) gate on the active provider:
| Credential | Format | Works on |
|---|---|---|
| Model access key | doo_v1_… or sk-do-… | inference.do-ai.run |
| Personal access token | dop_v1_… | api.digitalocean.com |
export MODEL_ACCESS_KEY=doo_v1_…
export DIGITALOCEAN_TOKEN=dop_v1_…
dhruthi auth login --type pat # stores in config (mode 0600)Credentials are never in argv, never in output — including under --debug-http.
Project awareness
AGENTS.md becomes the system prompt
Any AGENTS.md from the repo root down to your working directory is concatenated into the system prompt. The cross-tool convention — a repo with an AGENTS.md for another tool needs nothing new.
dhruthi context print # exactly what would be sent
dhruthi context files # which files were found
dhruthi --no-context "…" # ignore for one callPrompt templates
Markdown files in .dhruthi/prompts/ become commands:
---
description: Review code for bugs
model: openai-gpt-oss-20b
---
Review the following for correctness bugs. Focus areas: $ARGUMENTSdhruthi review null checks
dhruthi template list
dhruthi template render review null checksProject settings
.dhruthi/config.json (committed) and .dhruthi/config.local.json (personal) layer under your user profile. Only default_model, default_router, and format are allowed — endpoints, transport, and credentials are refused outright in shared files.
Commands
setup one-time onboarding (DigitalOcean)
repl (shell) interactive chat
doctor (check) diagnose configuration
context (ctx) show·files·print what applies here
template(s) list·show·render·trust·untrust
provider(s) catalog·add·list·use·status·test·remove
strategy(s) list·show·add·use·remove
auth login·logout·status·whoami (DigitalOcean only)
config get·set·unset·list·path·fix-permissions
models (m) list·get GET /v1/models
chat (c) create POST /v1/chat/completions
responses create POST /v1/responses
messages create POST /v1/messages
embeddings create POST /v1/embeddings
images generate POST /v1/images/generations
audio (tts) speech POST /v1/audio/speech
async submit·status·get·wait
video create·get·wait·download (experimental)
batch files·create·list·get·results·cancel
routers list·get·create·update·delete·presets (DigitalOcean only)
session list·show·rm·pathOutput
--format json|jsonl|table|text. --format json always emits exactly one valid JSON document. Streaming happens over the wire for fast first byte, but output is assembled before printing.
| Code | | Code | | |---|---|---|---| | 0 | success | 5 | rate limited | | 1 | unexpected | 6 | server error | | 2 | usage / validation | 7 | network / timeout | | 3 | auth | 8 | job failed | | 4 | not found | 130 | interrupted |
Reliability
- Retries on transient errors with exponential backoff and jitter;
Retry-Afterhonored up to 60s. - Billable POSTs are not blindly retried. An inference request returning 500 may have executed and been charged — only retried when the server confirms it wasn't processed.
- Config files written atomically, mode 0600. Credentials never in argv or output.
- Session transcripts at
~/.local/state/dhruthi/sessions/, mode 0600, JSONL format.
Development
npm install
npm run build # tsc → dist/
npm run typecheck # src + test
npm run lint # biome
npm test # builds, then unit + e2e (549 + 210 tests)
npm run verify:providers # sweep the catalog against live hostsRequires Node ≥ 22.12. Two conventions: relative imports carry .js extensions (NodeNext module resolution), and unit tests import from dist/ (type-stripping doesn't map .js to .ts).
Architecture
src/core/context-store.ts SQLite DB: turns, entities, embeddings, hybrid search
src/core/context-graph.ts Entity extraction (regex) + graph edge insertion
src/core/context-retrieve.ts Hybrid retrieval: graph + vector with combined scoring
src/core/hooks.ts Plugin system: 5 lifecycle events, JS/TS/sh hooks
src/repl/auto-slash.ts Intent detection: auto-runs context commands before turns
src/repl/commands.ts All 30 slash commands: dev, goal/plan, session, recall
src/repl/engine.ts Conversation loop with hooks, auto-slash, recall
src/repl/turn.ts Turn dispatch: single, strategy-fallback, parallel agents
src/api/chat.ts Standard-aware dispatch + image translation for 3 protocols
src/providers/catalog.ts 33 provider presets with base URLs, env vars, costsLicense
MIT
