{"id":50250302,"url":"https://github.com/josharsh/otterly","last_synced_at":"2026-05-28T02:00:41.365Z","repository":{"id":359394697,"uuid":"1245972376","full_name":"josharsh/otterly","owner":"josharsh","description":"Ollama for Claude. Use your Claude Code subscription as a local OpenAI-compatible API. No API keys, no per-token billing.","archived":false,"fork":false,"pushed_at":"2026-05-26T14:59:34.000Z","size":906,"stargazers_count":3,"open_issues_count":2,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-27T01:33:08.130Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://otterly.josharsh.com","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/josharsh.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-21T18:40:41.000Z","updated_at":"2026-05-25T13:55:55.000Z","dependencies_parsed_at":null,"dependency_job_id":"2df43561-5054-426b-98de-c7d44542f3b3","html_url":"https://github.com/josharsh/otterly","commit_stats":null,"previous_names":["josharsh/otterly"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/josharsh/otterly","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/josharsh%2Fotterly","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/josharsh%2Fotterly/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/josharsh%2Fotterly/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/josharsh%2Fotterly/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/josharsh","download_url":"https://codeload.github.com/josharsh/otterly/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/josharsh%2Fotterly/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33590884,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-28T02:00:06.440Z","response_time":99,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-27T01:32:59.565Z","updated_at":"2026-05-28T02:00:41.342Z","avatar_url":"https://github.com/josharsh.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003ch1 align=\"center\"\u003eotterly\u003c/h1\u003e\n  \u003cp align=\"center\"\u003e\u003cstrong\u003eOllama for Claude.\u003c/strong\u003e Use your Claude Code subscription as a local AI API. For free.\u003c/p\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://otterly.josharsh.com\"\u003eotterly.josharsh.com\u003c/a\u003e · \u003ca href=\"https://www.npmjs.com/package/otterly\"\u003enpm\u003c/a\u003e · \u003ca href=\"https://github.com/josharsh/otterly\"\u003egithub\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://www.npmjs.com/package/otterly\"\u003e\u003cimg src=\"https://img.shields.io/npm/v/otterly.svg\" alt=\"npm version\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://www.npmjs.com/package/otterly\"\u003e\u003cimg src=\"https://img.shields.io/npm/dm/otterly.svg\" alt=\"npm downloads\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/josharsh/otterly/blob/main/LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/npm/l/otterly.svg\" alt=\"license\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://nodejs.org\"\u003e\u003cimg src=\"https://img.shields.io/node/v/otterly.svg\" alt=\"node version\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/playground-light.png\" alt=\"otterly playground — interactive API explorer with JSON syntax highlighting, line numbers, and indent guides\" width=\"900\"\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\u003csub\u003e\u003ccode\u003eotterly serve\u003c/code\u003e → \u003ca href=\"http://localhost:11434/playground\"\u003e\u003ccode\u003elocalhost:11434/playground\u003c/code\u003e\u003c/a\u003e to poke the API, or \u003ca href=\"http://localhost:11434/dashboard\"\u003e\u003ccode\u003e/dashboard\u003c/code\u003e\u003c/a\u003e for live spend, tokens, latency, and tool usage.\u003c/sub\u003e\u003c/p\u003e\n\n---\n\n## The pitch in one breath\n\nYou already pay **\\$20–\\$200/month** for Claude Code. Then you build a side project, a script, a tiny agent, and Anthropic charges you **again** per token via the API. That's double-paying for the same brain.\n\n**Otterly fixes that.** It turns your existing Claude Code subscription into a local, OpenAI-compatible API, exactly like Ollama does for open-source models, powered by the Claude that you're already paying for.\n\n```bash\nnpx otterly serve\n```\n\n```\n  otterly: local inference server\n  ──────────────────────────────────────\n  OpenAI compat : http://localhost:11434/v1/chat/completions\n  Playground    : http://localhost:11434/playground\n  Ready. Point any OpenAI client at it.\n```\n\nSame port as Ollama. Same OpenAI-compatible API. Zero per-token cost. No API keys.\n\n---\n\n## Three ways to use it\n\n| | **Library** | **CLI server** | **Embedded server** |\n|---|---|---|---|\n| How | `import { claude } from \"otterly\"` | `npx otterly serve` | `await startApiServer({ port })` |\n| Best for | Node.js scripts \u0026 agents | Any language, any tool | Bundling the server into your app |\n| Network | None, in-process | localhost HTTP/WS | localhost HTTP/WS |\n| Analogy | Direct function call | `ollama serve` | `ollama` as a library |\n\nPick one. Mix all three.\n\n---\n\n## The without / with comparison\n\n```typescript\n// Without otterly: pay Anthropic twice\nimport Anthropic from \"@anthropic-ai/sdk\";\nconst client = new Anthropic({ apiKey: \"sk-ant-...\" });          // $3 / MTok in\nconst res = await client.messages.create({                        // $15 / MTok out\n  model: \"claude-sonnet-4-20250514\",\n  messages: [{ role: \"user\", content: \"Fix the failing tests\" }],\n});\n```\n\n```typescript\n// With otterly (library): direct, in-process, $0\nimport { claude } from \"otterly\";\nconst result = await claude.run(\"Fix the failing tests\", { cwd: \"./app\" });\nconsole.log(result.text, result.cost);\n```\n\n```typescript\n// With otterly (server): your existing OpenAI SDK, any language, $0\nimport OpenAI from \"openai\";\nconst ai = new OpenAI({ baseURL: \"http://localhost:11434/v1\", apiKey: \"unused\" });\nconst res = await ai.chat.completions.create({\n  model: \"claude-sonnet-4-20250514\",\n  messages: [{ role: \"user\", content: \"Fix the failing tests\" }],\n});\n```\n\nThat's the whole product. Keep reading if you want details.\n\n---\n\n## Install\n\n```bash\nnpm install otterly\n```\n\nYou also need Claude Code installed and signed in. Otterly auto-detects npm installs, Homebrew, and standalone binaries. Anything that gives you a working `claude` command.\n\n```bash\n# Verify\nclaude --version\n```\n\n---\n\n## Get your subscription back.\n\n\u003e *\"Claude Code subscribers can no longer use their Claude subscription limits for third-party harnesses including OpenClaw.\"*\n\u003e [TechCrunch](https://techcrunch.com/2026/04/04/anthropic-says-claude-code-subscribers-will-need-to-pay-extra-for-openclaw-support/), April 4, 2026\n\nIf you use **OpenClaw**, this is the section that pays for otterly.\n\nOn **April 4, 2026**, Anthropic severed the direct path between OpenClaw and your Claude Code subscription. Overnight, OpenClaw users had three new (worse) options: buy extra usage bundles, supply a separate Anthropic API key at full pay-per-token rates, or drain a small parallel \"Agent SDK credit\" pool. Heavy users reported bills jumping up to **50× their previous monthly outlay**. The OpenClaw author publicly called it a betrayal.\n\notterly is the routing layer. OpenClaw talks to `localhost:11434`. otterly spawns your authenticated `claude` CLI, which is Anthropic's own product and still allowed to use your subscription, and the request arrives at the exact destination OpenClaw was locked out of. No extra bundles, no API key, no credit-pool draining. Same model, same brain, same subscription.\n\n### Three steps. Then OpenClaw is yours again.\n\n```bash\n# 1. install Claude Code on the same machine, sign in once\nnpm i -g @anthropic-ai/claude-code\nclaude   # browser-based login, persists after\n\n# 2. install + run otterly\nnpm i -g otterly\notterly serve   # listens on localhost:11434\n```\n\n```json5\n// 3. add otterly as a model provider in your OpenClaw config\n{\n  agents: {\n    defaults: { model: { primary: \"otterly/claude-sonnet-4-20250514\" } },\n  },\n  models: {\n    providers: {\n      otterly: {\n        baseUrl: \"http://localhost:11434/v1\",\n        apiKey: \"unused\",\n        api: \"openai-completions\",\n        timeoutSeconds: 600,\n        models: [{\n          id: \"claude-sonnet-4-20250514\",\n          name: \"Claude Sonnet 4 (via subscription)\",\n          input: [\"text\"],\n          cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n          contextWindow: 200000,\n          maxTokens: 8192,\n        }],\n      },\n    },\n  },\n}\n```\n\n```bash\nopenclaw models set otterly/claude-sonnet-4-20250514\n```\n\nThat's the whole patch. Every OpenClaw call now lands on your Claude Code subscription via the local `claude` CLI. **The Agent SDK credit pool never moves, no API-key meter ticks, no extra-usage bundles get consumed.** Watch your Anthropic dashboard. The only thing that moves is your normal Claude Code subscription usage.\n\n\u003e **Running headless?** (Raspberry Pi, EC2, a container.) Run `claude` once over an interactive SSH session to complete the browser login. The token persists. Then run `otterly serve` as a systemd user service. Same pattern as any long-lived daemon.\n\n### Best configuration for OpenClaw\n\nThe defaults work fine on a developer laptop. On a Pi or any small machine, four dials make Friday feel instant instead of flaky.\n\n**Match concurrency to the box.** Each `claude` spawn uses about 1–2 GB of RAM and a full CPU core while it runs. On a Pi or a 2-vCPU VPS, set `--max-concurrent 2`. On a 16 GB laptop the default of `5` is fine. Going higher than your real parallelism mostly creates queue contention.\n\n**Raise the queue timeout for slow first spawns.** Claude Code's first cold spawn can take 20–30 seconds when the OpenClaw prompt is large (workspace files, skills, tool schemas). Set `--queue-timeout 300` so a burst of concurrent OpenClaw cron jobs does not 408 itself into a death loop while waiting for a worker slot.\n\n**Use otterly 0.5.0 or newer.** Earlier versions silently dropped streaming response text when otterly was driving the `claude` CLI binary (the common install path). OpenClaw uses `stream: true`. If you see *\"Agent couldn't generate a response\"* on a turn that should obviously work, you are on an older otterly. Upgrade.\n\n**Run otterly as a system service, not a tmux pane.** Drop this into `~/.config/systemd/user/otterly.service`:\n\n```ini\n[Unit]\nDescription=otterly local inference server\nAfter=network-online.target\n\n[Service]\nExecStart=/usr/bin/node /home/youruser/.npm-global/bin/otterly serve --port 11434 --max-concurrent 2 --queue-timeout 300\nRestart=always\nRestartSec=5\nEnvironment=HOME=/home/youruser\nEnvironment=PATH=/usr/bin:/home/youruser/.npm-global/bin\n\n[Install]\nWantedBy=default.target\n```\n\nThen:\n\n```bash\nsystemctl --user enable --now otterly.service\nloginctl enable-linger youruser     # survives reboot without a login\n```\n\n**Sanity check before flipping the default model.** Once `openclaw.json` is patched, test the new provider before pointing all of OpenClaw at it:\n\n```bash\nopenclaw capability model run --model otterly/claude-sonnet-4-20250514 --prompt \"ping\"\n```\n\nIf that returns text in a few seconds, you are good: `openclaw models set otterly/claude-sonnet-4-20250514`. If it hangs, `curl http://localhost:11434/api/status` tells you exactly which queue slot is stuck.\n\n---\n\n## Plug otterly into your coding tool\n\nAnything that speaks the OpenAI Chat Completions API can use otterly as a provider. Run `otterly serve` once, then drop one of these snippets in.\n\n### Cline (VS Code extension)\n\nIn Cline's settings (Cmd-Shift-P → *Cline: Open Settings*), pick **OpenAI Compatible** as the API provider, then:\n\n| Field | Value |\n|---|---|\n| Base URL | `http://localhost:11434/v1` |\n| API Key | `unused` (any non-empty string) |\n| Model ID | `claude-sonnet-4-20250514` |\n\nCline streams by default — otterly handles SSE with keepalives so cold spawns don't time out the extension.\n\n### Cursor\n\nSettings → **Models** → toggle **Override OpenAI Base URL**:\n\n```\nhttp://localhost:11434/v1\n```\n\nSet API Key to anything (e.g. `unused`), then add `claude-sonnet-4-20250514` to *Model Names*. Cursor will call otterly for chat, autocomplete, and Composer.\n\n### Continue (`~/.continue/config.json`)\n\n```json\n{\n  \"models\": [\n    {\n      \"title\": \"Claude Sonnet 4 (via otterly)\",\n      \"provider\": \"openai\",\n      \"model\": \"claude-sonnet-4-20250514\",\n      \"apiBase\": \"http://localhost:11434/v1\",\n      \"apiKey\": \"unused\"\n    }\n  ]\n}\n```\n\n`provider: \"openai\"` is intentional — Continue's OpenAI provider speaks the same chat-completions shape otterly serves.\n\n### Aider\n\n```bash\nexport OPENAI_API_BASE=http://localhost:11434/v1\nexport OPENAI_API_KEY=unused\naider --model openai/claude-sonnet-4-20250514\n```\n\nThe `openai/` prefix tells Aider to use the OpenAI-compatible path. Add to your shell profile to make it permanent.\n\n### LiteLLM / LangChain / anything else\n\nSame pattern everywhere: point the base URL at `http://localhost:11434/v1`, send any non-empty API key, name the model `claude-sonnet-4-20250514`. If your client talks to OpenAI today, it talks to otterly with one config change.\n\n---\n\n## Mode 1: Library, in-process, no server\n\nJust import and call. Perfect for Node scripts, custom agents, cron jobs, build tools.\n\n```typescript\nimport { claude } from \"otterly\";\n\n// One-shot\nconst result = await claude.run(\"Add validation to user.ts\", { cwd: \"./app\" });\nconsole.log(result.text);   // assistant reply\nconsole.log(result.cost);   // USD cost for this turn (tracked from Claude Code)\nconsole.log(result.tools);  // every tool call Claude made\n\n// Stream tokens\nfor await (const event of claude.stream(\"Refactor auth\", { cwd: \".\" })) {\n  if (event.type === \"text_delta\") process.stdout.write(event.delta);\n  if (event.type === \"tool_use\") console.log(`\\n[using ${event.tool}]`);\n}\n\n// Multi-turn session. Context persists in-memory, no server.\nconst session = claude.session({ cwd: \"./app\" });\nawait session.send(\"Create a REST API\");\nawait session.send(\"Now add auth to it\");   // remembers the API you just built\nsession.close();\n```\n\nSessions run entirely in-process. No WebSocket. No HTTP. The async generator stays alive between `send()` calls and keeps Claude's working context warm. As lightweight as a function call.\n\n---\n\n## Mode 2: CLI server, the Ollama experience\n\nWhen you want one running daemon that **any tool, any language, any framework** can talk to.\n\n```bash\nnpx otterly serve\n```\n\nThat's it. Now you have an OpenAI-compatible API on `localhost:11434`. Point anything at it:\n\n```typescript\n// TypeScript with the OpenAI SDK\nimport OpenAI from \"openai\";\nconst ai = new OpenAI({ baseURL: \"http://localhost:11434/v1\", apiKey: \"unused\" });\nconst res = await ai.chat.completions.create({\n  model: \"claude-sonnet-4-20250514\",\n  messages: [{ role: \"user\", content: \"Write a haiku about otters\" }],\n});\n```\n\n```python\n# Python with the OpenAI SDK\nfrom openai import OpenAI\nai = OpenAI(base_url=\"http://localhost:11434/v1\", api_key=\"unused\")\nres = ai.chat.completions.create(\n  model=\"claude-sonnet-4-20250514\",\n  messages=[{\"role\": \"user\", \"content\": \"Write a haiku about otters\"}],\n)\n```\n\n```bash\n# Anything that speaks HTTP. curl, your shell, your dog.\ncurl -X POST http://localhost:11434/v1/chat/completions \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"model\": \"claude-sonnet-4-20250514\",\n    \"messages\": [{\"role\": \"user\", \"content\": \"Hello\"}]\n  }'\n```\n\nIt works in Cursor, Continue, Aider, Open WebUI, LiteLLM, LangChain, llamafile UIs, your own clients. Anything with a `baseURL` field. If it talks to OpenAI, it talks to otterly.\n\nOpen the **playground** at [http://localhost:11434/playground](http://localhost:11434/playground) to poke the API from your browser, or the **dashboard** at [http://localhost:11434/dashboard](http://localhost:11434/dashboard) to see live spend, latency, and tool usage.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/dashboard-light.png\" alt=\"otterly dashboard — today's spend, tokens, average latency, recent runs, top tools, and queue stats\" width=\"900\"\u003e\n\u003c/p\u003e\n\n---\n\n## Mode 3: Embedded server, programmatic, no CLI\n\nRun the full HTTP + WebSocket server **inside your own Node app**. Same endpoints, same playground, same WebSocket sessions, but no separate process to babysit.\n\n```typescript\nimport { startApiServer } from \"otterly\";\n\nconst handle = await startApiServer({\n  port: 11434,\n  workingDir: \"./my-project\",\n  maxConcurrent: 5,\n});\n\n// handle.server   → Node http.Server\n// handle.wss      → WebSocketServer\n// handle.port     → bound port\n// handle.shutdown → graceful drain + close\n\n// later, on app exit\nawait handle.shutdown(10_000);\n```\n\nThis is exactly what `npx otterly serve` runs under the hood. Bundle it inside an Electron app, an internal dev tool, a Tauri sidecar, a Cloudflare-style edge worker. Anything that benefits from an AI endpoint without managing a second process.\n\n---\n\n## What you get with the server\n\n| Endpoint | Format | What it's for |\n|---|---|---|\n| `POST /v1/chat/completions` | OpenAI | Drop-in for any OpenAI client/SDK |\n| `POST /api/run` | JSON | Native one-shot with cost + tool logs |\n| `POST /api/stream` | NDJSON | Streaming with rich events |\n| `WS /ws` | WebSocket | Persistent multi-turn sessions |\n| `GET /dashboard` | HTML | Live spend, latency, recent runs, tool usage |\n| `GET /playground` | HTML | Interactive API explorer in your browser |\n| `GET /api/status` | JSON | Health + queue stats |\n| `GET /api/metrics` | JSON | Today + lifetime usage, recent runs, top tools |\n| `GET /swagger.json` | OpenAPI 3.0 | Full spec. Generate a client in any language. |\n\nPlus all the boring-but-essential stuff:\n\n- **Concurrency control.** Request queue prevents fork-bombing your machine.\n- **Rate limiting.** Per-IP token bucket, configurable.\n- **Circuit breaker.** Bails out on cascading Claude Code failures.\n- **Auth.** Set `OTTERLY_API_KEY` to require `Bearer` tokens.\n- **Graceful shutdown.** Drains in-flight requests before exiting.\n- **CORS.** Works straight from the browser.\n\n---\n\n## Server config\n\n```bash\nnpx otterly serve --port 11434 --dir ./project --max-concurrent 3\n```\n\n| Flag | Default | |\n|---|---|---|\n| `-p, --port` | `11434` | Port to listen on |\n| `-d, --dir` | cwd | Working directory Claude runs in |\n| `--max-concurrent` | `5` | Parallel Claude processes |\n| `--max-queue` | `50` | Max queued requests |\n| `--queue-timeout` | `120` | Seconds a request may wait in the queue before 408 |\n| `--rate-limit` | `60` | Requests/min per client |\n\nProgrammatic options on `startApiServer()` mirror the flags, plus `requestTimeoutMs`, `streamTimeoutMs`, and `queueTimeoutMs`. Set `OTTERLY_API_KEY` in the environment to require Bearer auth.\n\n---\n\n## Library API at a glance\n\n```typescript\nimport { claude, ClaudeEngine, READONLY, AUTOPILOT } from \"otterly\";\n\n// One-shot\nconst result = await claude.run(prompt, options);\n// → { text, cost, duration, sessionId, usage, tools }\n\n// Streaming\nfor await (const event of claude.stream(prompt, options)) {\n  // event.type: text_delta | tool_use | tool_result | system | result | error\n}\n\n// Multi-turn session (in-process)\nconst session = claude.session(options);\nawait session.send(message);   // → AgentResult\nsession.close();\n\n// Custom engine with defaults\nconst engine = new ClaudeEngine({ model: \"claude-sonnet-4-20250514\", maxTurns: 10 });\n\n// Permission modes\nawait claude.run(prompt, { permissionMode: READONLY });   // read-only\nawait claude.run(prompt, { permissionMode: AUTOPILOT });  // full auto\n\n// Embedded server\nimport { startApiServer } from \"otterly\";\nconst handle = await startApiServer({ port: 11434 });\n```\n\n---\n\n## Why this is allowed to exist\n\nOtterly is a **transport layer**. It does not jailbreak Claude, does not bypass any usage limits, does not redistribute model weights, does not store your prompts. Every request flows through your **own** authenticated Claude Code installation, subject to your subscription's normal limits.\n\nWhat you save is the *second meter*: the per-token API bill on top of the subscription you already pay.\n\nWhy is it called \"otterly\"? Because otters carry their tools with them, and your local Claude already has all the tools it needs. Also because the domain was available.\n\n---\n\n## Requirements\n\n- **Node.js 18+**\n- **Claude Code** installed and signed in (`claude --version` to confirm)\n- An active Claude subscription (Pro, Max, or Team)\n\n---\n\n## Contributing\n\nPRs and issues welcome at [github.com/josharsh/otterly](https://github.com/josharsh/otterly). Especially interested in:\n\n- More language SDK examples (Go, Rust, Elixir)\n- Integration recipes for Cursor / Continue / Aider / Open WebUI\n- Bugs from running it in production-ish environments\n\n---\n\n## License\n\nMIT. Use it for anything, ship it anywhere, don't blame me when an otter gets in your codebase.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjosharsh%2Fotterly","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjosharsh%2Fotterly","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjosharsh%2Fotterly/lists"}