Skip to content

About

Run open source models locally with the power of tool calling optimized for coding & also run cloud models for faster execution.

Resources

Security policy

Stars

5 stars

Watchers

2 watching

Forks

Repository files navigation

TermiCursor - Local Agentic Coding Assistant

TermiCursor is a GUI-based AI coding agent that runs on local models. Give it a goal: it plans the work (for anything non-trivial), then edits files, searches your codebase, and runs shell commands to accomplish it. It asks for your approval before running commands and streams its plan, thoughts and results live. Three modes: Ask (answers questions, read-only), Plan (you review the plan before any code is written) and Build (plans if needed, then codes). With the default Ollama setup, your code, embeddings and inference stay on your PC.

GUI Look

alt text

Terminal Look

alt text

🚀 Download

Download the latest Windows installer

  1. Install Ollama and make sure it's running.
  2. Install and open TermiCursor. The status bar turns green and shows ready with the model name once it can reach Ollama.
  3. If it shows Models missing, click Install in the status bar (or the download link on the welcome screen). The default models (qwen2.5-coder:3b and nomic-embed-text) are about 2.2 GB in total.
  4. Open a project folder and give the agent a task.

Requirements: Windows 10/11 and 8 GB RAM recommended. The browser tools use the Microsoft Edge that comes with Windows.

What goes over the network

  • Checking GitHub for TermiCursor updates.
  • Downloading models through Ollama (only when you click Install).
  • Web pages the agent opens with its browser tools.
  • Cloud models (optional): if you add and choose an OpenAI-compatible provider (Groq, Gemini, OpenAI…) in Settings, your prompts and the code the agent reads are sent to that provider.

Safety

  • The agent asks before every shell command and before deleting files or writing outside the project. Settings → Auto-approve shell commands skips the prompt, except for destructive commands (rm, del, git reset --hard…), which always ask.
  • The app's local backend only accepts requests carrying a secret generated at each launch, so other programs and web pages can't drive the agent.
  • Logs for bug reports: %APPDATA%\Termicursor\logs\main.log. See SECURITY.md to report a vulnerability.

Architecture

TermiCursor has two front ends, the desktop app and the terminal app, over one Python agent core (core/). The desktop app starts a local FastAPI backend and talks to it over HTTP and a WebSocket; the terminal app runs the same agent in-process. Models run in Ollama on your PC unless you opt into a cloud provider.

System overview

flowchart TB
    subgraph Clients["You use it from"]
        direction LR
        subgraph Desktop["Desktop app · Electron"]
            direction LR
            UI["React UI<br/>explorer · editor · terminal · agent panel"]
            MAIN["Electron main process<br/>starts backend · shell · file access"]
            UI <-->|"IPC · preload"| MAIN
        end
        CLI["Terminal app<br/>cli.py"]
    end

    subgraph Backend["Python backend · FastAPI · server.py"]
        direction TB
        API["REST + WebSocket<br/>per-launch token · origin check"]
        LOOP["Agent loop · core/agent<br/>router · planner · executor"]
        GATE{{"Trust gate<br/>asks you before risky actions"}}
        TOOLS["Tools · core/tools<br/>find · read · edit · run · browse"]
        RAG["Indexing + retrieval<br/>rag.py"]
        API --> LOOP
        LOOP -->|"one JSON tool call per turn"| GATE
        GATE -->|"safe or approved"| TOOLS
        TOOLS -->|"search_codebase"| RAG
    end

    subgraph PC["Stays on your PC"]
        direction LR
        OLLAMA["Ollama<br/>qwen2.5-coder:3b · nomic-embed-text"]
        FILES[("Your project")]
        QDRANT[("Qdrant<br/>vector index")]
        STORE[("SQLite sessions · project memory<br/>artifacts: logs, recordings")]
        BROWSER["Edge / Chromium<br/>via Playwright"]
    end

    CLOUD["OpenAI-compatible API<br/>optional, off by default"]

    MAIN -->|"starts it with a token"| API
    UI -->|"HTTP + WebSocket"| API
    CLI -->|"in-process"| LOOP
    LOOP -->|"chat"| OLLAMA
    LOOP -.->|"only if selected in Settings"| CLOUD
    LOOP --> STORE
    TOOLS --> FILES
    TOOLS --> BROWSER
    RAG -->|"embeddings"| OLLAMA
    RAG --> QDRANT
Loading
Part Where What it does
Desktop app frontend/ (Electron, React, Vite, Tailwind) Editor (Monaco), file tree, terminal (xterm.js), agent panel, companion character (Mascot.tsx) and music bar (MusicBar.tsx). The main process starts the packaged backend with a per-launch token and guards navigation.
Terminal app cli.py (termicursor), main.py (headless) Same agent, in the terminal. tab cycles Ask / Plan / Build.
Backend server.py (FastAPI) Sessions, plan review, approvals, artifacts, model downloads; streams agent events over WS /ws/agent/{session_id}.
Agent core/agent/ loop.py runs requests; router.py (simple vs multi-step), planner.py, context.py (grounding), prompts.py, json_protocol.py, llm_client.py (Ollama or OpenAI-compatible).
Tools core/tools/ grep, find_files, list_dir, read_file, search_codebase, edit_file, write_file, delete_file, run_shell_command, browser tools; trust_gate.py decides what needs approval.
Retrieval rag.py Indexes a folder into a local Qdrant collection and serves semantic search.
Storage core/persistence, core/memory, core/artifacts SQLite sessions and tool calls, per-folder project memory (JSON), command logs and browser recordings.
Settings core/config.py Reads settings.json on each run, so changes apply without a restart.

How a request runs

flowchart TD
    REQ(["Your request"]) --> MODE{"Mode"}
    MODE -->|"Ask"| ONE_ASK["One step<br/>read-only tools"]
    MODE -->|"Build"| ROUTE{"Router<br/>question or task?"}
    ROUTE -->|"question or small task"| ONE["One step<br/>all tools"]
    ROUTE -->|"multi-part task"| PLAN_B["Planner writes steps"]
    MODE -->|"Plan"| PLAN_P["Planner writes steps"]
    PLAN_P --> REVIEW{"You review the plan"}
    REVIEW -->|"ask for changes"| PLAN_P
    REVIEW -->|"reject"| STOPPED(["Stopped, nothing written"])
    REVIEW -->|"approve, steps editable"| STEPS["Run the steps in order"]
    PLAN_B --> STEPS
    ONE_ASK --> CTX
    ONE --> CTX
    STEPS --> CTX

    subgraph STEP["Each step: a bounded ReAct loop"]
        CTX["Context: files named in the request,<br/>project file list, project memory"] --> THINK["Model returns one JSON tool call<br/>or a final answer"]
        THINK -->|"tool call"| CHECK["Validate args · trust gate"]
        CHECK -->|"safe"| RUN["Run the tool"]
        CHECK -->|"risky"| APPROVE{"You approve?"}
        APPROVE -->|"allow"| RUN
        APPROVE -->|"deny"| FEEDBACK
        RUN --> FEEDBACK["Feedback to the model<br/>tool result · syntax check · precise errors"]
        FEEDBACK --> THINK
        THINK -->|"final answer"| GROUND{"Grounded?<br/>looked at the code,<br/>made the change"}
        GROUND -->|"no: nudge once"| FEEDBACK
    end

    GROUND -->|"yes"| DONE(["Answer or summary, streamed live"])
Loading
  1. Route. Ask answers directly with read-only tools. Plan always writes a plan and waits for you to approve, edit or revise it. Build lets core/agent/router.py decide: questions and small tasks run as one step, multi-part tasks get a plan first. Heuristics resolve most cases without an LLM call.
  2. Execute. Each step is a bounded ReAct loop: the model emits one JSON tool call per turn, which is validated against the tool's schema, checked by the trust gate, run, and fed back, until it returns a final answer. The tools follow how Cursor and Claude Code work:
    • find: grep (exact text or regex, with context lines), find_files (glob), list_dir, search_codebase (semantic; falls back to text search when the project isn't indexed)
    • read: read_file (numbered lines, 200-line pages)
    • change: edit_file (exact old_string → new_string, must match once; an empty old_string appends), write_file (new files), delete_file
    • run and verify: run_shell_command, browser_navigate / browser_click / browser_get_text
  3. Grounding (core/agent/context.py, loop.py), so a small model looks instead of guessing: files named in the request are read up front (like an @-mention); the prompt includes the project's file list; a project question answered without looking gets one nudge, and so does a change request that ends without an edit. write_file over an unread file, or one that would erase most of it, is refused. Edits keep the file's line endings and report a syntax check.
  4. Trust gate. Every shell command (unless auto-approve is on; destructive ones always), deletes, and writes outside the project pause for your approval.
  5. Stream and stop. Every step streams live over the WebSocket, and a run can be stopped at any time (POST /sessions/{id}/cancel).
  6. Browser checks (text only). qwen2.5-coder:3b can't see images, so the browser tools return page text and console/network errors, never pixels. Separately, and only for you, the whole browser session is recorded to a .webm that appears in the Artifacts list.

Indexing and search

flowchart LR
    OPEN["Open a folder"] --> WALK["Walk project files<br/>~25 file types · skips .git, node_modules, dist"]
    WALK --> CHUNK["Language-aware chunks<br/>500 chars · 50 overlap"]
    CHUNK --> EMBED["nomic-embed-text<br/>via Ollama"]
    EMBED --> COL[("Qdrant collection<br/>one per folder")]
    COL --> SEARCH["search_codebase<br/>top-k chunks with file paths"]
    NOIDX["No index yet<br/>e.g. the terminal app"] -.-> GREP["search_codebase falls back<br/>to grep text search"]
Loading

When you open a folder (Settings → Index project on open), rag.py walks the project, splits each file with LangChain's language-aware splitter so functions and classes stay together, embeds the chunks with nomic-embed-text, and stores them in a local Qdrant collection named after the folder's path hash, so projects never mix. Reopening a folder reuses its index. Exact-match search (grep) works without any index.

Skills and MCP servers (connectors)

Skills are instruction packs, one folder each with a SKILL.md:

<project>/.termicursor/skills/<name>/SKILL.md      (this project only)
<app data>/skills/<name>/SKILL.md                  (all projects)
---
name: api-style
description: How this repo writes FastAPI endpoints
---
Use pydantic request models, return dicts, ...

Project rules: put standing instructions in AGENTS.md (project root) and/or .termicursor/rules.md. Both are read at the start of every run (first 4000 characters) and added to the agent's prompt.

@-mentions: type @ in the agent input to pick a project file. Mentioned files are read for the agent before it starts.

MCP servers go in settings.json under mcpServers, in the same format Claude Desktop uses. Each entry is either a command (stdio) or a url (streamable HTTP):

"mcpServers": {
  "github": {"command": "npx", "args": ["-y", "@modelcontextprotocol/server-github"],
             "env": {"GITHUB_PERSONAL_ACCESS_TOKEN": "..."}},
  "docs":   {"url": "https://example.com/mcp"}
}

Only skill names, skill descriptions and server names go in the prompt. The agent reads a skill's text with load_skill, finds an MCP tool with mcp_find, and runs it with mcp_call, which asks for your approval first. So adding more skills or servers barely grows the prompt. Tool results do take context, so set OLLAMA_NUM_CTX to 16384–32768 when you use MCP. Multi-step skill/MCP work needs a model of about 14B parameters or more (for example qwen2.5-coder:14b or devstral). 3B models manage basic file edits only.


Features

  • Private by Default: With Ollama, code, embeddings and inference stay on your machine (see What goes over the network above).
  • Syntax-Aware Parsing: Code-aware chunking ensures logical components (functions, classes) are kept intact.
  • Database Workspace Isolation: Every project folder receives a unique path-hashed Qdrant collection to completely avoid data mix-ups.
  • Companion Character: A small animated character that roams while the agent works, calls you when it needs approval and celebrates when a run finishes (optionally speaking a phrase). A built-in terminal character is included; Settings → Companion lets you drop in your own .gif for walk, idle, done and error (any art style, max 10 MB each, GIF contents are verified) and Remove background cuts a solid background out of a GIF. Click to pet it, drag to move it.
  • Music Bar: Plays the songs in a folder on your PC (MP3, M4A, AAC, WAV, OGG, OPUS, FLAC) from a bar above the status bar, with cover art and title from the file's tags, play/pause, next/previous, seek, volume, shuffle, repeat, a queue and keyboard media keys. The default colors match the app theme; the accent and background are customizable in Settings → Music.
  • Your Own Models and Shortcuts: Switch between Ollama models and OpenAI-compatible API providers from the chat, and rebind keyboard shortcuts in Settings.
  • Inline Completions: Tab-to-accept ghost-text suggestions in the editor from a small local Ollama model (default qwen2.5-coder:1.5b; Ollama provider only, toggle in Settings).
  • Proactive Checks: Validates model availability and connectivity to prevent standard connection tracebacks.
  • Windows Terminal Safe: Integrated UTF-8 reconfiguration protects against command-line character rendering crashes.

Development

Command Options

Requires Python 3.10+ (releases are built with 3.13) and Node 22. Dependencies are pinned in requirements.txt.

# 1. Create a venv
python -m venv .venv

# 2. Activate the virtual environment
.venv\Scripts\activate

# 3. Install requirement file
pip install -r requirements.txt

# 3b. One-time: install the Chromium binary the browser tools drive
python -m playwright install chromium

# 4. Install frontend files
cd frontend
npm install

# 5. Open 2 terminals: one for frontend, one for backend
cd frontend
npm run dev

# Open another terminal and run
uvicorn server:app --reload

# 6. Ingest (Index) the current directory codebase
python main.py ingest .

# 7. Run the agent on a goal (headless CLI; anything needing approval is auto-denied,
#    including shell commands unless "autoApproveShell": true is in settings.json)
python main.py run . "Create a Flask hello-world app and run it"

# 8. Interactive terminal UI (asks you before running commands)
python cli.py

# Tests (offline; tests/live_* need a running Ollama)
python -m pytest -q tests --ignore-glob="tests/live_*"

# Agent eval against your Ollama: does it use its tools and get the right answer?
python tests/live_agent_eval.py            # 11 tasks x 2 reps, prints a pass table

# End-to-end GUI test: drives the real Electron app (own backend + Vite on free ports)
cd frontend && node e2e/run-e2e.mjs        # screenshots in frontend/e2e/screenshots
node e2e/settings-e2e.mjs                  # providers, shortcuts, companion character, GIF background removal
node e2e/music-e2e.mjs                     # music bar; set E2E_MUSIC_DIR=<folder> to also play your own songs
# (set E2E_PYTHON to the venv's python.exe if `python` on your PATH can't start the backend)

# Rebuild the built-in companion GIFs (frontend/public/mascot) after editing the drawing script
node scripts/make-mascot.mjs

Security checks

Dependencies are audited on every push (the audit job in .github/workflows/ci.yml fails on high or critical npm findings and on any pip-audit finding), and Dependabot opens grouped weekly update PRs. Run the same checks before pushing:

cd frontend; npm audit; cd ..\landing-page; npm audit; cd ..
python -m pip_audit -r requirements.txt

Fix a vulnerable package by bumping it. For a transitive one, add an overrides entry to package.json instead of npm audit fix --force. In the desktop app, the renderer is untrusted: the main process validates everything it is asked to read or write (for example it re-checks that companion uploads are GIFs and that music files have an audio extension).

In dev, the backend runs without the per-launch token (only the packaged app sets TERMICURSOR_TOKEN); it still rejects requests from any origin except the Vite dev server.

The GUI (npm run dev in frontend/) is the primary way to use the agent — it shows the live plan, tool calls, approval prompts, and artifacts, and lets you stop a run mid-flight.


References

Official documentation and sources used or referenced to build TermiCursor.

Models and local inference

Agent design

Backend and retrieval

Terminal app

Desktop app

License

MIT

About

Run open source models locally with the power of tool calling optimized for coding & also run cloud models for faster execution.

Resources

Security policy

Stars

5 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages