ctxctl
Pure-CLI, zero-MCP, stateless context layer for AI coding agents.
ctxctl lets an agent read only the part of a file it needs — a symbol
located via a tree-sitter AST, sliced straight from the original source —
and compress command output instead of dumping whole files into context.
Output is byte-stable (a pure function of input + config), so provider
prompt caching applies to repeated reads.
This crate is the thin clap shell over two engine crates:
ctx-symbol— tree-sitter AST symbol location + original-source slicing (Rust, TypeScript, Python, Go, JavaScript, Java, C, C++, C#, Ruby, Lua backends)ctx-exec— rg-rule-driven command output compression
Quickstart
Commands
outline — symbol outline with token savings
# src/server.rs [30 symbols, ~22.9 KB -> 5,429 tokens, saved ~7%]
type config L:13 mod config;
struct Cli L:30-49 struct Cli {
symbol — original source slice of one symbol
# handle_request src/server.rs:42-58 (58 tokens, saved ~85%)
pub async fn handle_request(&self, id: u64) -> Result<String, Error> {
let row = self.db.get(id).await?;
Ok(row.to_string())
}
--compact keeps the signature (and python decorators) and folds the
body behind a // ... [N lines omitted] marker.
read — raw line ranges, no AST
exec — run a command, compress its output
Keeps head/tail and lines matching keep patterns, folds the middle:
$ cargo test
error[E0308]: mismatched types --> src/main.rs:12
... [34 lines omitted]
warning: unused variable: `x` --> src/server.rs:88
Saved ~70% (1,240 -> 372 tokens)
deps — import dependency graph
# src/main.rs [3 imports, ~512 B -> ~64 tokens, saved ~88%]
external serde L:1
local crate::lib L:2
Configuration
Stateless preference file, no state/index/session. Lookup precedence:
--config <path>.ctxctl/config.tomldiscovered by walking up from the cwd$XDG_CONFIG_HOME/ctxctl/config.toml
[]
= ["error", "warning", "failed", "panic", "fatal"]
= 5
= 5
= 20
[]
= 50
= true
[]
= ["node_modules", "target", "dist", ".git"]
[]
= true
Project-level keys override global keys; undeclared keys fall back to global → defaults. No array-concatenation.
Byte stability
The output body never contains timestamps, counters, random values,
machine-specific paths, or PIDs. Same input + same config → byte-identical
output, which is what lets provider prompt caching discount the tokens
already saved. saved% is a deterministic estimate (4 bytes ≈ 1 token),
not an external measurement.
Contract
The authoritative CLI contract lives in
ctxctl-docs/cli-contract.md:
command contracts, JSON envelopes, exit codes, config keys, and byte
stability requirements.