Metadata-Version: 2.4
Name: letscode
Version: 0.6.1
Summary: A minimal coding agent for the terminal.
Author-email: Stefane Fermigier <sf@abilian.com>
Requires-Python: >=3.12
Requires-Dist: aiofiles>=24.0
Requires-Dist: cyclopts>=4.14.0
Requires-Dist: httpx>=0.27
Requires-Dist: openai>=1.0
Requires-Dist: pluggy>=1.5
Requires-Dist: prompt-toolkit>=3.0
Requires-Dist: pydantic>=2.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: rich>=13.0
Description-Content-Type: text/markdown

# letscode

A minimal, OpenAI-compatible coding agent for the terminal — written in Python. Point it at any OpenAI-API-compatible endpoint (Ollama, Fireworks, OpenRouter, vLLM, llama.cpp's `llama-server`, …) and get a streaming agent loop with the four tools that cover 95% of coding sessions — `read`, `write`, `edit`, `bash` — plus skills, slash commands, and a plugin system you can extend.

**Status:** v0.6, alpha.

The full docs live in [`docs/`](docs/) and build via [Zensical](https://zensical.org) — run `make docs-serve` for a live preview, or `make docs` for a static build.

Or go to <https://letscode.hop3.abilian.com> for the online doc.

## What it does

- **Streaming agent loop** with parallel tool execution.
- **Four built-in tools**: `read`, `write`, `edit`, `bash`. Restrictable with `--tools` / `--no-tools`.
- **Skills** in the [agentskills.io](https://agentskills.io) format (`SKILL.md` + YAML frontmatter). Drop a file in `~/.letscode/skills/<name>/` and the agent picks it up — the model invokes it as a tool, or you invoke it as `/skill:<name>`.
- **Sessions** persisted as JSONL under `~/.letscode/sessions/`. `-c` continues the most recent for the current directory; `/tree`, `/fork`, `/clone` branch and duplicate them.
- **Type while it's working.** **Enter** stops the run; **type then Enter** stops it and redirects to the new instruction. **Ctrl+C** also stops; press it twice to exit.
- **Slash commands**: `/help`, `/quit`, `/reset`, `/model`, `/tools`, `/skills`, `/plugins`, `/reload`, `/tree`, `/fork`, `/clone`, `/compact`, `/steer`, `/follow-up`, `/queue`, `/verbose`, `/footer`, plus `/skill:<name>`. Plugins can add more.
- **TOML config** in `~/.letscode/config.toml` (user) + `.letscode/config.toml` (project, overrides). Run `letscode --init` to scaffold a commented starter. API keys come from env vars only.
- **First-run nice**: `letscode --version`, `letscode --init`, and an actionable message if you forget the API key — never a traceback.
- **Plugin system** based on [`pluggy`](https://pluggy.readthedocs.io): tools, commands, skills, frontends, plus lifecycle hooks. See [`docs/plugins/`](docs/plugins/).
- **RPC mode** (`letscode --mode rpc`) — NDJSON over stdio, for IDE / host-app integration.
- **Rich terminal** — Markdown-rendered assistant text, syntax-highlighted `read` panels, per-turn footer with model / tokens / cost / context-pct (and cache-hit count when the provider reports one).

The deliberate non-goals — native Anthropic / Bedrock / Vertex provider modules, MCP in core, sub-agents in core, plan mode, built-in todos — are kept outside core on purpose; each can be a plugin. Full list in the [roadmap](docs/about/roadmap.md).

## Install

```sh
uv tool install letscode       # or: pipx install letscode
```

Then `letscode --version` and `letscode --init` to scaffold a config.

To hack on it instead:

```sh
git clone https://github.com/abilian/letscode
cd letscode
uv sync
uv run letscode --version
```

## Quickstart

### Option A — local with Ollama (free)

```sh
brew install ollama
ollama serve &
ollama pull qwen2.5-coder:7b

uv run letscode \
  --base-url http://localhost:11434/v1 \
  --api-key ollama \
  --model qwen2.5-coder:7b \
  "create hello.py that prints 'hi from letscode' and run it"
```

The first call downloads the model (~4.5 GB for the 7B). After that, everything runs locally; any non-empty string works as the API key.

### Option B — Fireworks (hosted)

The reference test endpoint is [Fireworks](https://fireworks.ai/) running MiniMax M2.5:

```sh
export OPENAI_BASE_URL=https://api.fireworks.ai/inference/v1
export OPENAI_API_KEY=$FIREWORKS_API_KEY
export LETSCODE_MODEL=accounts/fireworks/models/minimax-m2p5

uv run letscode "explain what this directory contains"
```

Any OpenAI-API-compatible endpoint works the same way — OpenRouter, vLLM, llama.cpp's `llama-server`, deepinfra, together.ai, etc. Set `OPENAI_BASE_URL`, `OPENAI_API_KEY`, and `LETSCODE_MODEL` (or pass `--base-url`, `--api-key`, `--model`).

## Modes

```sh
letscode                                  # interactive — streaming TUI
letscode "what does main.py do?"          # one-shot, rich rendering, then exit
letscode -p "summarize this directory"    # print mode — plain stdout, pipe-friendly
letscode -c "now do step 2"               # continue most recent session for cwd
letscode --version                        # print the version (no key needed)
letscode --init                           # scaffold ~/.letscode/config.toml (no key needed)
letscode --mode rpc                       # NDJSON over stdio, for IDE/host-app integration
```

Print-mode exit codes: `0` (ok), `1` (provider error), `130` (Ctrl+C), `2` (usage error).

## Tools

The four built-ins, enabled by default:

| Tool | What it does |
|---|---|
| `read` | Read a file (with optional line range). |
| `write` | Create or overwrite a file atomically. |
| `edit` | Apply a search-and-replace edit. |
| `bash` | Run a shell command with a timeout. |

Restrict with `--tools=read,bash` (comma-separated), or disable all with `--no-tools`.

## Skills

Drop a `SKILL.md` file in any of these paths:

```text
~/.letscode/skills/<name>/SKILL.md     # user-global
~/.agents/skills/<name>/SKILL.md       # agentskills.io standard
~/.pi/agent/skills/<name>/SKILL.md     # additional path
~/.claude/skills/<name>/SKILL.md       # additional path
.letscode/skills/<name>/SKILL.md       # project-local (walked up from cwd)
.agents/skills/<name>/SKILL.md
.pi/skills/<name>/SKILL.md
.claude/skills/<name>/SKILL.md
```

Format:

```markdown
---
name: code-review
description: Use when reviewing code for correctness, style, or security.
---

# Code Review

When invoked:
1. Read the changed files.
2. Look for off-by-one errors, race conditions, missing input validation.
3. Suggest improvements with concrete examples.
```

Skills written in this format for other agentskills.io-format tools load unchanged. Two ways to invoke:

- **The model calls it as a tool.** Every skill is auto-wrapped as `skill_<normalised-name>` (e.g. `write-a-prd` → `skill_write_a_prd`). The model invokes via native tool-calling.
- **The user types `/skill:<name> [message]`.** Same effect; the slash form keeps the original skill name with hyphens preserved.

Full reference in [docs/user-guide/skills.md](docs/user-guide/skills.md).

## Slash commands

Type at the prompt during an interactive session. Full reference in [docs/user-guide/slash-commands.md](docs/user-guide/slash-commands.md). The most common:

- `/help` — list every registered command.
- `/skill:<name> [message]` — prepend a skill's body to the next user message.
- `/tree` · `/fork [n]` · `/clone` — navigate / branch the session.
- `/compact [bias]` — collapse older history into a summary; optional text biases the summarisation prompt.
- `/reload` — re-read TOML config + reload skills mid-session.
- `/verbose` — toggle full vs. truncated tool-result panels.
- `/plugins` — list loaded plugins (built-in + entry-point) with version.

## Configuration

User-global at `~/.letscode/config.toml`, project-local at `.letscode/config.toml`:

```toml
model = "accounts/fireworks/models/minimax-m2p5"
base_url = "https://api.fireworks.ai/inference/v1"
frontend = "basic"
timeout = 300

[memory]                 # plugin-owned section, passed through to letscode-memory
relevance = false
```

`letscode --init` writes a commented starter file (it never overwrites an existing one).

Resolution order (lowest → highest priority):

1. Built-in defaults
2. User config (`~/.letscode/config.toml`)
3. Project config (`./.letscode/config.toml`)
4. Environment variables (`LETSCODE_MODEL`, `OPENAI_BASE_URL`, `OPENAI_API_KEY`)
5. CLI flags

API keys are deliberately **not** loaded from the file. Full reference in [docs/user-guide/configuration.md](docs/user-guide/configuration.md).

`AGENTS.md` and `CLAUDE.md` files in cwd and its ancestors, plus `~/.letscode/AGENTS.md`, are loaded automatically and appended to the system prompt. Use `--no-context-files` to skip.

## Writing a plugin

A plugin is a Python package with at least one `@hookimpl` declaration and a `letscode` entry-point. Minimal example:

```python title="mypkg/plugin.py"
import pluggy
from pydantic import BaseModel

from letscode.agent.tools import ToolContext, ToolResult, tool
from letscode.llm.types import TextPart

hookimpl = pluggy.HookimplMarker("letscode")


class _GreetParams(BaseModel):
    name: str


@tool(name="greet", description="Say hello to someone.")
async def _greet(params: _GreetParams, ctx: ToolContext) -> ToolResult:
    del ctx
    return ToolResult(content=[TextPart(text=f"Hello, {params.name}!")])


@hookimpl
def letscode_register_tools(registry):
    registry.add(_greet)
```

```toml title="pyproject.toml"
[project.entry-points.letscode]
my_plugin = "mypkg.plugin"
```

`pip install -e .` and the LLM can now call the `greet` tool.

For the full surface — every hook, the stable-import contract, the versioning rule, packaging notes, `ExecutionEnv` overrides, the trust model — see [docs/plugins/authoring.md](docs/plugins/authoring.md). For the design rationale behind the extension model itself, see [docs/architecture/extension-model.md](docs/architecture/extension-model.md).

A worked reference plugin lives in [`plugins/letscode-memory/`](plugins/letscode-memory/) — cross-session memory via `/remember`, `/memories`, and `/forget`, implemented in a handful of hooks against the same stable surface external plugins target.

## Project layout

```text
src/letscode/       # the app: agent loop, CLI, frontends, tools, skills, plugin system
plugins/            # in-repo reference plugins (letscode-memory, letscode-goal, letscode-textual)
docs/               # public documentation (Zensical)
tests/              # a_unit / b_integration / c_e2e (the latter gated by LETSCODE_E2E=1)
```

A monorepo split is on the roadmap once external plugin contracts are exercised. For now everything ships as a single package.

## Development

```sh
uv sync                           # install
make check                        # lint + type-check
make test                         # pytest + coverage gate
make                              # check + test
make docs                         # build docs (Zensical, --strict)
make docs-serve                   # live-reload docs preview
make verify-dist                  # build the wheel and drive it from a clean venv
make verify-plugin                # discover letscode-memory from a built install
```

For the end-to-end smoke suite (real LLM, costs money or runs locally):

```sh
LETSCODE_E2E=1 \
LETSCODE_E2E_BASE_URL=https://api.fireworks.ai/inference/v1 \
LETSCODE_E2E_API_KEY=$FIREWORKS_API_KEY \
LETSCODE_E2E_MODEL=accounts/fireworks/models/minimax-m2p5 \
uv run pytest tests/c_e2e/
```

## Documentation

The full documentation lives in [`docs/`](docs/) and is built with [Zensical](https://zensical.org). Top-level sections:

- **[Getting started](docs/getting-started.md)** — install, first run, switching providers.
- **[User guide](docs/user-guide/)** — configuration, slash commands, skills, sessions.
- **[Plugins](docs/plugins/)** — authoring guide, reference plugin.
- **[Architecture](docs/architecture/)** — extension model, agent loop.
- **[About](docs/about/)** — changelog, roadmap, lessons learned, related projects.

## License

MIT.
