post://a-local-first-ai-coding-cli

A local-first AI coding CLI

read: 6 min words: 1,113
A local-first AI coding CLI
toc://sections
outline

    There is a pattern to AI coding assistants. You install one, log in, and over the first week you notice three things. The provider is fixed, so the model you actually want is not available. The telemetry is on, and turning it off is somewhere between a setting and a request. The commands you use most are implemented in a fork, which means they break on the next upstream merge. Each of those is a small tax, and together they add up to a tool that owns your workflow more than you do.

    Mervelas came from deciding that the constraints would be the design, not the marketing. Repo: swadhinbiswas/Mervelas

    Provider independence is the first constraint

    The assistant supports OpenAI, OpenRouter, NVIDIA NIM, Qwen, DeepSeek, and locally hosted models out of the box, and switching is configuration rather than a migration. That list is not a feature checklist. It is a statement that the model you want changes every few weeks, and a tool that hardcodes a provider turns every model upgrade into work.

    For headless setups and CI, the same choice is expressible in the environment:

    MERVELAS_API_PROVIDER=openai
    OPENAI_API_KEY=your_key_here
    OPENAI_BASE_URL=https://api.openai.com/v1
    

    The /login and /config commands do the same thing interactively. What matters is that the provider is a value, not a fork.

    The commands belong to the event loop

    The commands people miss when they switch tools are the ones that feel native: a way to see the context window, a way to create a specialized agent, a way to reach an MCP server. Mervelas implements those directly against the local event loop instead of patching them in.

    /context shows exact token usage and the shape of the window, which turns a vague feeling of running out of room into a number. /agents creates and switches between local coding agents. /mcp wires in Model Context Protocol servers so the model can reach other tools. /status reports local development state and resource use, and /config manages providers and models. None of these are exotic. The point is that they are native, so they keep working.

    Lightweight is an architectural decision

    The CLI is compiled with Bun and rendered through a custom React Ink abstraction. That abstraction is enforced in the code review rules, and the reason is instructive: Mervelas does not import Ink directly from npm. Layout primitives route through a local src/ink.ts, so the rendering layer is something the project controls and can change without chasing a dependency.

    The rule that shaped the runtime most is about startup. Nothing is allowed to instantiate blocking state or read files at module load. Tools and execute loops use a delayed dynamic import, so the footprint stays idle until something is actually invoked. For an assistant that lives in a terminal all day, idle cost is not a rounding error. A background process peeking at the CPU between prompts is the difference between a tool you keep open and one you close.

    <svg viewBox="0 0 900 260" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="A terminal with streaming output and a blinking cursor">
      <rect x="70" y="30" width="760" height="200" rx="14" fill="none" stroke="currentColor" stroke-opacity="0.45" stroke-width="2"/>
      <circle cx="100" cy="58" r="6" fill="#f38ba8"/><circle cx="122" cy="58" r="6" fill="#f9e2af"/><circle cx="144" cy="58" r="6" fill="#a6e3a1"/>
      <rect x="100" y="92" width="60" height="13" rx="6" fill="#89b4fa" fill-opacity="0.7"/>
      <g fill="currentColor">
        <rect x="176" y="92" width="300" height="13" rx="6">
          <animate attributeName="width" values="40;340;180;420;40" dur="6s" repeatCount="indefinite"/>
        </rect>
        <rect x="100" y="128" width="420" height="13" rx="6" opacity="0.45">
          <animate attributeName="width" values="120;460;260;500;120" dur="6s" repeatCount="indefinite"/>
        </rect>
        <rect x="100" y="164" width="360" height="13" rx="6" opacity="0.3">
          <animate attributeName="width" values="200;520;320;560;200" dur="6s" repeatCount="indefinite"/>
        </rect>
      </g>
      <rect x="100" y="198" width="14" height="16" fill="#a6e3a1">
        <animate attributeName="opacity" values="1;0;1" dur="1s" repeatCount="indefinite"/>
      </rect>
    </svg>
    

    Where the session lives

    Conversational history is written locally to ~/.mervelas/projects/, one file per project, as JSONL. Nothing is uploaded as telemetry. That single decision changes how the tool can be used. You can point it at a private repository without wondering where the prompts end up, and the session files are ordinary text you can read, diff, grep, or delete. There is no server component that needs to stay available for your history to exist.

    Zero telemetry is easy to promise and easy to quietly break, so it is worth being concrete about what it means here. The session store is local, the provider calls go straight from your machine to the provider you configured, and there is no first-party backend in between that observes either.

    Agents, MCP, and the context window

    The commands that felt least like table stakes turned out to shape the architecture most.

    /mcp connects Model Context Protocol servers, which lets the assistant reach tools and data outside the terminal without baking those integrations into the CLI. That matters for a local-first tool, because the number of possible integrations is unbounded and the number the maintainer wants to own is not. MCP is the seam that keeps the tool small while leaving it extensible.

    /agents handles something different. A single assistant with one system prompt is asked to be a careful reviewer, a fast prototyper, and a documentation writer, and it does none of them especially well. Named local agents let the same CLI hold several configured personas, each with its own instructions, and switch between them without restart. It is a small amount of state and a large change in how the tool is used.

    /context is the one I open most. AI coding sessions fail at the boundary of the window, and a tool that will not tell you how close you are to it turns every long session into a guess. Seeing the token count and the shape of the remaining room changes the decision about when to start a fresh session, which is exactly the kind of quiet, unglamorous affordance a daily driver needs.

    The honest state of it

    Mervelas is in active development and not yet published to npm. Today you clone the repository and build it with Bun, which for now is the price of the privacy model. Type checking runs against strict ES2023 through tsc --noEmit, and the contribution rules exist to keep the architecture from regressing toward the thing it was built to avoid.

    There is a trade-off worth naming. Building your own assistant means you maintain it, and an established tool will always have more integrations. What a local-first tool gives back is that the constraints are yours. If the provider list is wrong for you, you change a value. If the session format is wrong, you change a file. That is a different kind of product from the one that treats your workflow as the integration surface.

    The build steps and the agent configuration are in the repository above.

    Reach me at swadhinbiswas.cse@gmail.com or on GitHub and LinkedIn.

    react://a-local-first-ai-coding-cli
    comments://a-local-first-ai-coding-cli

    No comments yet.