lithe-cli
lithe-cli is the command-line interface for
lithe — the storage-free ReAct agent
kernel. Installing it pulls in lithe automatically and gives you a lithe
command: chat with an agent that reads and writes files in your workspace,
runs code, searches the web through MCP tools, inspects stored runs, and
undoes a run's mutations.
pip install lithe-cli
Configure
There is no default endpoint — the CLI refuses to run rather than silently
hitting some third-party URL. The easiest way in is the wizard: on the
first lithe chat / lithe run (or any time, via lithe config) an
interactive terminal walks a provider-first interview — profile name,
provider type (Tab-completed: openai / anthropic / zai / deepseek /
openrouter / qwen / moonshot, or none for a hand-written endpoint),
base_url (defaults to the preset's official URL — override it to point a
provider's format at your own router), API key, model — and saves them
to $LITHE_HOME/config.json (mode 0600 — it holds the key). An optional
connectivity probe catches typos before your first turn, using the
provider's own auth (x-api-key for messages endpoints).
The config file holds named profiles — several endpoints, one active:
{
"version": 2,
"active": "zhipu",
"profiles": {
"zhipu": {"base_url": "https://…", "api_key": "…", "model": "glm-4.6",
"context_window": 128000, "cached_models": ["glm-4.6"]},
"zai-preset": {"provider": "zai", "api_key": "…", "model": "glm-4.6"}
}
}
A profile may set "provider" instead of (or alongside) base_url: the
vendor preset from lithe.bundles.providers fills base_url when unset
and contributes LLMConfig defaults (transport, extra_body,
default_headers) under your explicit values — you write the key and
model, the preset knows the endpoint shape. --provider /
LITHE_PROVIDER override the profile field; config --list / --show
display it; an unknown name fails loudly with the available presets
(openai / anthropic / zai / deepseek / openrouter / qwen / moonshot).
lithe config # provider-first wizard (edits a named profile)
lithe config --list # profiles, key masked
lithe config --use openrouter # switch active profile (script-friendly)
lithe config --model glm-4.5 # set the active profile's default model
lithe config --provider anthropic # set the active profile's preset
# (none/off clears)
lithe models # GET {base_url}/models, cached into the profile
# (in chat: /models refreshes the current
# profile's cache; /models all fans out to
# every saved profile in parallel)
A one-shot provider for a single invocation pairs the flag with an
explicit --base-url — the custom-router form without touching the
config file:
lithe run --provider openrouter --base-url https://my-router/api/v1 \
--api-key sk-... --model vendor/claude-sonnet-4 "task"
To configure by hand instead, point the CLI at any OpenAI-compatible endpoint:
export LITHE_API_KEY=sk-...
export LITHE_BASE_URL=https://your-endpoint/api/v1
export LITHE_MODEL=your-model
Resolution order is flag (--api-key, --base-url, --model) >
environment > the selected profile's fields, per key; --profile NAME
(or LITHE_PROFILE) selects a profile for one invocation. The wizard
never runs without a TTY on both ends, so pipes and CI keep the hard
refusal; --no-setup restores that fail-fast behavior on terminals
too. lithe config --show peeks at the saved values with a masked key.
LITHE_HOME (default ~/.lithe) locates the run store; the agent's
workspace defaults to the current directory (--workspace to change).
lithe doctor prints the effective configuration — endpoint (with a
masked key), config file, store, workspace, sandbox backend, skills, MCP
servers — so you can see what a run would use before starting one.
Use
One-shot task
$ cd my-project
$ lithe run "总结 README.md 的要点,存到 SUMMARY.md"
⚒ read_file 读取 README.md
✓ 已读取 README.md(120 行) 0.1s
⚒ write_file 写入 SUMMARY.md
✓ 写入 SUMMARY.md(+12 行,新建) 0.0s
已把要点写入 SUMMARY.md。
── done · steps 3 · tokens 2100 · cost 0.0042
修改已有文件时,助手优先调用 edit_file 或 apply_patch 做局部更新;只有新建文件或需要整体替换时才使用 write_file。工具调用界面只显示工具名、文件路径等摘要,文件写入/编辑的结果带 +N -M 行 的行数变化提示(lithe 0.1.0 起由内核统计)。
--stream streams tokens as they generate; -v adds per-call
usage/context gauges; --max-steps caps the tool loop;
--context-window enables fullness gauges; --reasoning-effort LEVEL
sets the reasoning intensity for one invocation (flag > profile field;
/reasoning adjusts it in-session). Run budgets end the turn with
status budget_exceeded once crossed: --max-cost USD (needs a
pricing table in the profile on endpoints that report no usage cost)
and --max-tokens N (cumulative); both show progress lines in the
sidebar and adjust via /set (off clears). Sampling:
--temperature / --max-output-tokens (flag > profile field).
Full-screen interface
On an interactive terminal, lithe chat and lithe run TASK open a
persistent full-screen interface (built on Textual)
instead of scribbling one-line events into the console. The chrome is
deliberately quiet (kilo-style): no box borders and no header strip —
just the conversation on the left (blocks separated by a breathing
line; your own messages sit on a solid violet panel with near-white
text, so quoted input reads as a block), and a sidebar of collapsible sections on the right on
a darker background band — the two panes separate by color alone, with
no divider line (click a ▾/▸ heading to fold/unfold; a folded
section keeps its one-line summary — the numbers worth a glance never
require unfolding). There is no run-status section: while a turn runs
the conversation itself shows a live ⠋ thinking · 12.3s lane (a
rotating spinner, the live elapsed, click to unfold the model's
reasoning), and once the turn ends that lane settles under the output —
▸ thought · 4.2s when the model reasoned, a dim model · 4.2s meta
line otherwise — so the current turn's duration always sits below its
message. Section headings render bold with their bodies indented
underneath in regular weight, and a 工作区 section carries the resolved
workspace path (wrapped at path separators). There is no footer status
bar — the key map lives in the sidebar's folded 按键 section, and run
state belongs to the conversation. The prompt is a taller ❯ box (min
3 lines) on a solid, slightly raised background band whose bottom
carries a dim info line — glm-4.6 · zhipu · 推理 high (model with
its profile, plus the reasoning effort when one is set), the endpoint
truth at a glance where you type. Assistant replies render as
Markdown — headings keep their text but lose the # markers (a ▍
bar and weight carry the level), **emphasis**/`code`/links
turn into styled spans, fenced code blocks render whole as
pygments-highlighted code (the terminal's own 16-color palette via
rich's ansi_dark, unknown language tags fall back to plain), lists
normalize to •/n./task glyphs, and blank lines between paragraphs
render as nothing (code fences keep them verbatim) so the transcript
stays dense — while anything that is not Markdown
(bracket pairs like [dim], unclosed markers, intraword 2*3*4)
passes through verbatim, and /copy still lifts the original
Markdown off the feed.
A fresh, empty conversation opens on a welcome screen — the lithe logo
plus the effective model, workspace and session, and the handful of keys
worth knowing — which the first message replaces (it is chrome, never
feed: /copy still reports an empty conversation while it shows).
把 a.txt 改成三行待办清单 │ ▾ 会话
◆ edit_file · 局部修改 a.txt │ ▾ 工作区
│ I:\Lithe\my-project
✓ 编辑 a.txt(+3 -1 行) 0.0s │ ▾ 模型
│ zhipu · glm-4.6
⠋ thinking · 4.2s │ zai · chat · F7 端点
│ ▾ 工具
│ ✓ edit_file 0.0s
│ ▾ 待办 1/2
│ [~] 改 a.txt
│ ▸ 用量 输入 1,024 · 输出 216 · $0.0008 · 上下文 0.8%
Tab 采纳 → /model /models /new │ ▸ 按键 F2–F7 · /help
❯ _ │
glm-4.6 · zhipu · 推理 high
Switch without leaving the screen: F3 opens the session picker
(Enter switch, n new, d delete, r rename), F4 the model picker
(grouped by profile; Enter switch, s save as the profile's default,
r fetch /models), F5 the settings picker (Enter toggles a
capability; see below), and F7 the endpoint manager — every saved
profile with its provider · transport dialect, base_url and masked
key; Enter switches, n creates a profile right in the form (provider
type cycled with Enter, base_url defaulting to each preset's official
URL — overridable for custom routers, star-echoed API key; the model
field is optional — leave it blank and pick from the /models listing
afterwards), e edits one, p swaps its provider type, d deletes one
after a y/n confirmation. A profile created in the form is
switched to on save; edits to the current profile apply to the next
turn. Switching away from a running session does not
cancel it — its badge stays lit and the pane rebuilds from the store when
you come back. Typing / shows matching commands above the prompt (Tab
accepts; /model, /profile, /set complete their arguments too),
and ↑/↓ recall the persistent input history. Ctrl+C cancels the
current turn (a second press during the same turn force-exits) and exits
when idle. Plain text typed mid-turn steers the running agent: it is
queued into the kernel's steering inbox and injected as a user message at
the next step boundary (rendered in the transcript; persistently
recorded), so you can correct course while it works — texts that cannot
be injected before the turn ends are honestly reported as dropped. Slash
commands work while a turn runs. The transcript follows new output only
while you're at the bottom — scroll up to re-read and your position
sticks.
Text selection uses the terminal's native Shift+drag (hide the sidebar
with F2 first if it grabs both panes). Pipes and CI keep the plain
per-line output unchanged; the legacy prompt_toolkit full-screen was
removed — the Textual UI is the only full-screen front-end.
Sessions
Every chat conversation persists as a kernel conversation (one run per turn, messages written incrementally):
lithe chat -c # continue the most recent session
lithe chat --resume 12 # by id (or unique title prefix)
lithe sessions # list; newest activity first
lithe sessions --rename 12 规划 # rename
lithe sessions --delete 12 # soft delete (runs stay: log/undo work)
A session remembers its last profile and model and restores them on
resume (unless --model/env pinned). Inside chat, the same operations
are slash commands: /sessions, /resume 12, /new [标题],
/rename 标题, /model glm-4.5 [--save], /profile zhipu,
/models, /undo, /tools, /sidebar, /help, /exit.
In-session settings
The per-turn knobs don't need a restart: /set (or F5) opens a picker
where Enter toggles a capability, and typed forms set anything —
/set shell on, /set code off, /set vision, /set document on,
/set max-steps 50, /set timeout 240, /set attempts 3,
/set stream on, /set verbose.
Settings apply to the next turn (the running turn keeps its own tool
set), stay session-scoped (nothing is written to the profile), and
turning shell on restates its trust warning. /tools re-derives the
registry after a capability flip, so the listing always matches what the
next turn will see.
Reasoning intensity: /reasoning (or F6) picks a level —
off / minimal / low / medium / high, or any value your model accepts
(none on some) typed directly; --save persists it to the profile. The
kernel maps it per protocol (reasoning_effort on chat-completions,
reasoning.effort on Responses) and combines it with
extra_body["reasoning"] siblings such as OpenRouter's
max_tokens/exclude. The sidebar shows the active level when set.
Extra capabilities
The kernel ships these as bundles. Default policy: only the execution surfaces are off until asked for — the analysis/ingress bundles are on out of the box:
| Flag | Tools granted | Notes |
|---|---|---|
--code |
run_code / run_file |
Python under bubblewrap when installed (passthrough otherwise); doctor shows which; off by default |
--shell |
run_command |
Native bash/sh on Linux and macOS, PowerShell/cmd on Windows; runs with the current user's host permissions, is not sandboxed, and cannot be undone; off by default |
--skills DIR |
load_skill |
markdown skill library; defaults to $LITHE_HOME/skills when it exists, --skills "" disables |
--download / --no-download |
download_file |
SSRF-guarded, size-capped network fetch; on by default |
--vision / --no-vision |
image_info / analyze_image |
image probe is stdlib-only; analysis routes one vision call to the main endpoint; on by default |
--document / --no-document |
document_info / analyze_document |
PDF/DOCX/XLSX/PPTX reading on the main endpoint; on by default; /set document toggles in-session |
--document-format |
(dialect for analyze_document) |
inline-file (OpenRouter family) / files-api (strict OpenAI upload) / none (probe only); default: profile field document_format, else provider preset |
--max-cost USD |
(run budget) | the turn ends budget_exceeded once cumulative cost crosses it; on endpoints reporting no usage.cost add a profile pricing table ({"prompt": 3, "completion": 15} per 1M) — the preset providers' known prices work too |
--max-tokens N |
(run budget) | cumulative token budget for the turn; --max-output-tokens N is the different per-call cap |
--subagents / --no-subagents |
delegate / delegate_parallel |
kernel delegation on the default roster — researcher (read-only lookup, gains vision/document tools with those flags), coder (file edits + run_code with --code), operator (run_command/download_file, present only with --shell/--download); children inherit the endpoint and budgets, parallel workers share one live cost ceiling; on by default (the roster reuses already-enabled tools only — no new powers); /set subagents toggles in-session |
--mcp SPEC |
whatever the servers expose | JSON array/object or @file (env LITHE_MCP); stdio and streamable-http; failed servers degrade gracefully |
$ lithe run --code "用 run_code 验证 results.csv 的行数"
$ lithe run --document "总结 report.pdf 的结论,存到 NOTES.md"
$ lithe run --subagents "先让 researcher 摸清 src 结构,再让 coder 分头改"
$ lithe run --shell "检查当前目录的项目状态"
$ lithe chat --skills ~/my-skills --mcp @~/mcp.json
Undo a run
The bundled file and todo tools register reverters, so undo works with zero configuration:
$ lithe runs # find the run
run status steps cost task
──────────── ────── ───── ──── ────────────────
abc123def456 done 3 0.01 总结 README.md …
$ lithe undo abc123def456
✓ 已撤销 2 个操作(run abc123def456)
A file the run created is deleted; a file it overwrote is restored.
(run_code side effects are not revertible — its tool description warns
the model.)
Inspect
$ lithe runs # stored runs (newest last)
$ lithe sessions # stored conversations (newest first)
$ lithe log abc123def456 # messages + actions of one run
$ lithe models # what the endpoint offers (cached)
$ lithe doctor # config + capability status
Tools
$ lithe tools # what the agent can do
tool category description
──────────── ──────── ──────────────────────────────
apply_patch WRITE 以行级 patch 一次修改多个文件…
read_file READ 读取文件内容…
...
The default tool set is the workspace bundle (read_file / write_file /
edit_file / list_files / search_files / glob_files /
apply_patch) plus todos (update_todos / list_todos); the flags above
add capabilities. (MCP tools attach at run time, so lithe tools lists
them only after a session has started the servers.)
Colors
Output is colored when stdout is a TTY and NO_COLOR is unset; --color
/ --no-color force either way. Output rendering itself stays
dependency-free — plain text degrades cleanly through pipes and cron.
Interactive input (the chat prompt and the setup wizard) is
prompt_toolkit:
wide-character-safe editing, bracketed-paste handling, star-echoed API
keys, inline wizard validation, and persistent history.
Where things land
| Path | Contents |
|---|---|
current dir (or --workspace) |
the agent's sandboxed workspace — every tool path resolves strictly inside it |
~/.lithe/config.json (or LITHE_HOME) |
endpoint profiles: {version, active, profiles} (0600 — it holds keys) |
~/.lithe/runs (or --store) |
JSONL run store: conversations, runs, messages, actions, undo records |
~/.lithe/runs/todos-<scope-hash>.json |
the agent's task list, isolated by user and resolved workspace |
~/.lithe/skills (or --skills) |
the markdown skill library, when enabled |
~/.lithe/history |
chat input history (prompt_toolkit FileHistory; ↑/↓ recall in the TUI, Ctrl+R search in the plain REPL) |
Design notes
- The CLI is a thin host over a
Workbench: the workbench owns the endpoint view (profile + model), the session manager and the running turns; the plain REPL and the full-screen Textual app are both just subscribers to its event bus and callers of one dispatcher. Everything else (ReAct loop, streaming, budgets, replay, undo engine) is reused from lithe. - The Textual screen is tested headlessly with
App.run_test()pilots (turns, F2/F3/F4, history, completion) — real interaction tests that also run on Windows; its pure builders (sidebar sections, prompt info line, Markdown rendering, completion suggestions) and the sharedTuiStateevent folding have direct unit tests. python -m lithe_cliworks alongside thelitheconsole script.
License
MIT
Metadata
Release files for lithe-cli 0.3.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| lithe_cli-0.3.1.tar.gz | 172.3 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| lithe_cli-0.3.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 292.1 kB
Release files / lithe_cli-0.3.1.tar.gz
| Download URL | lithe_cli-0.3.1.tar.gz |
|---|---|
| Size | 172.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c4b444cb339aa60e071746fee29d9403bf56133853d69db8fdee6af0703324d0
|
|
BLAKE2b-256 checksum How to use checksums |
730b38c8de60224fce3d2c80d7800e397436e86b88a05bc79a286968f4b7c0b0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.
Transparency logRelease files / lithe_cli-0.3.1-py3-none-any.whl
| Download URL | lithe_cli-0.3.1-py3-none-any.whl |
|---|---|
| Size | 119.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
982842a8e62381f49a0691f5841cb5b18a7bd2c8f326db4ebf1cd149c78a38d4
|
|
BLAKE2b-256 checksum How to use checksums |
8b9c9e887b4cdd15816868bee6f3cceb91ed723f62d718f921260e2b08ea525f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.
Transparency logSecurity for lithe-cli 0.3.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for lithe_cli-0.3.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.