From Greek θλίβω — to press, crush, compress.
AI tool output compressor for local coding agents. When Claude
Code runs git status, npm install, cargo test, or a verbose
log dump, thlibo intercepts the Bash-tool invocation, runs the real
command, and hands the AI a compressed version of the output —
preserving every load-bearing detail and dropping the noise.
Saves 60-99% of tokens on common dev commands without the AI knowing the difference.
Uses the same PreToolUse+updatedInput pattern described in Claude Code's hooks documentation: rewrite the Bash command before it runs so the subprocess stdout is already compressed when the tool result is captured. Same token savings, no proxy, no API-wire tampering.
git diff HEAD~5 68821 bytes ──thlibo──► 929 bytes (98.6%)
npm list 200 deps ~6200 bytes ──thlibo──► ~600 bytes (90%)
cargo test failing ~4800 bytes ──thlibo──► ~480 bytes (90%)
log file 500 lines ~20000 bytes ──thlibo──► ~1500 bytes (92%)
The savings figures on the marketing page and in this README are measured in tokens — the unit you actually pay for — not raw bytes:
- Without thlibo = what Claude Code consumes natively. Text outputs at ~4 chars/tok; PDFs at ~2,250 tok/page (page render + extracted text).
- With thlibo = bytes returned by the processor, ÷4.
A 7-day log won't fit in any context window (Claude's is 200k tok). The "without thlibo" figure for those rows is what the bytes would tokenise to — in practice, without thlibo you wouldn't be able to ask the model about that data at all.
Commands reproducible via
go test ./internal/middleware/... -run TokenSavings. PDF + log
fixtures embedded with inferd's
300M-param embed model; top-percentile windows surfaced by k-NN
density (CORDON_MAX_WINDOWS=5000).
Works with Claude Code (Bash + PowerShell + Read + Write/Edit hooks),
Codex CLI (PostToolUse decision: block), Cursor IDE (preToolUse
updated_input command + file-read rewrite; no MCP), GitHub Copilot
CLI (preToolUse modifiedArgs command rewrite + postToolUse
modifiedResult output compression), and VS Code Copilot (1.111+,
currently Insiders) — the Copilot hook file thlibo installs is
auto-discovered by VS Code too, and the hook scripts speak both wire
formats.
Want to see how much it's saving you? thlibo can emit optional OpenTelemetry metrics + events to a collector you run — off by default, content-free, one env var to enable. See Monitoring.
thlibo is one binary plus PreToolUse hooks. Inference runs in a separate sidecar daemon, inferd, which thlibo's installer detects-or-installs alongside itself.
Claude Code: about to run `git status`
│
▼
PreToolUse hook (bash / powershell)
│ ask: thlibo rewrite "git status"
▼
thlibo (CLI)
│ registry lookup on argv[0] == "git" → wrap it
│
▼
hook emits: updatedInput = "thlibo exec -- git status"
│
▼
Claude Code runs the rewritten command
│
▼
thlibo exec runs the real git, captures stdout
│
▼
middleware: fast-path regex → git-filter (native Go)
OR router call → inferd sidecar → Gemma 4 processor
│
▼
compressed stdout, original stderr, original exit code
│
▼
Claude Code: tool_output = compressed bytes
│
▼
Model never sees the original — only the summary.
The deterministic filters (git-filter, npm-filter, cargo-filter,
pytest-filter, go-test-filter, ndjson-filter, stacktrace-filter,
lint-filter, trivy-filter, har-filter, mhtml-filter, pdf-filter)
are native Go — compiled into the binary, run in-process, no inferd and
no Python (ADR 0010, ADR 0015). PDFs included: pdf-filter reads them
in-process, roughly 99× faster than the Python path and with less text
mangling, using the document's own structure tree when it has one. (That
figure is an end-to-end aggregate over five real corpus documents,
51.3 s → 0.52 s — full table and method in
ADR 0015. It was measured once
against Python during the port and is not re-derived by the in-tree
benchmarks, which are Go-only.)
pdf-to-md remains installed for the one thing native Go can't do — a
scanned, image-only PDF has no extractable text, so its pages get
rasterized and handed to inferd's Gemma vision model for OCR (see below).
Prompt processors (compress, casefolder, shorthand) dispatch
through inferd for LLM-driven summarisation of unfamiliar output.
cordon-filter (semantic anomaly surfacer, native Go) embeds
windows via inferd and surfaces the rare ones — it runs automatically as
a fallback when ndjson-filter over-collapses a log (e.g. an access log
where every line shares the same level+msg), restoring the outliers a
signature-based collapse would hide.
Everything runs on your machine. No network calls during inference, no telemetry, nothing leaves localhost — unless you explicitly opt in to OpenTelemetry metrics, which emit content-free usage/savings stats to a collector you configure.
curl -fsSL https://raw.githubusercontent.com/3rg0n/thlibo/main/scripts/install.sh | bashPin to a specific version:
curl -fsSL https://raw.githubusercontent.com/3rg0n/thlibo/main/scripts/install.sh | THLIBO_VERSION=v0.11.5 bashirm https://raw.githubusercontent.com/3rg0n/thlibo/main/scripts/install.ps1 | iexOr pinned:
$env:THLIBO_VERSION='v0.11.5'; irm https://raw.githubusercontent.com/3rg0n/thlibo/main/scripts/install.ps1 | iexBoth installers:
- Download the platform-matching release tarball/zip from GitHub.
- Verify SHA-256 against
SHA256SUMSpublished in the same release. - Extract
thlibointo~/.local/bin(Unix) or%LOCALAPPDATA%\thlibo\bin(Windows). - On Windows, add the install dir to the User PATH (no admin).
- Run
thlibo install— writes Claude Code hooks, mirrors processors, probe-or-installs the inferd sidecar.
Skip step 5 with THLIBO_SKIP_INSTALL=1 if you want to inspect the
binary before running configure.
The one-liner wires Claude Code by default. To also install the Codex, Cursor, or Copilot hooks through the one-liner (it can't take flags), set an env var:
# Unix
curl -fsSL https://raw.githubusercontent.com/3rg0n/thlibo/main/scripts/install.sh | THLIBO_CODEX=1 THLIBO_CURSOR=1 THLIBO_COPILOT=1 bash# Windows
$env:THLIBO_CODEX=1; $env:THLIBO_CURSOR=1; $env:THLIBO_COPILOT=1; irm https://raw.githubusercontent.com/3rg0n/thlibo/main/scripts/install.ps1 | iex(Equivalently, run thlibo install --codex --cursor --copilot yourself afterward.)
- Python 3.8+ — optional. Every built-in filter (git, npm, cargo,
go test, pytest, lint, trivy, ndjson, stacktrace, pdf, cordon)
is native Go and needs no Python. Python is only required for
scanned PDFs (
pdf-to-mdrasterizes their pages for OCR; born-digital PDFs don't touch it), plus any of your own.pyprocessors. jq— the Claude Code hook shell script needs it. Install via your package manager orwinget install jqlang.jqon Windows.git— for git-related compression you probably have it already.
On Windows you also need bash on PATH so Claude Code can execute
the PreToolUse hook script. Git Bash (bundled with Git for
Windows) is sufficient — if git works in your shell, you're
fine. WSL is also an option.
git clone https://github.com/3rg0n/thlibo.git
cd thlibo
mkdir -p bin
# Unix:
go build -o bin/thlibo ./cmd/thlibo
# Windows (note the .exe):
go build -o bin/thlibo.exe ./cmd/thliboCopy the binary somewhere on your $PATH (e.g. ~/.local/bin/)
and run thlibo install.
thlibo installThis does five things:
- Mirrors the embedded built-in processors to
~/.thlibo/processors/(script processors need an on-disk directory to chdir+exec into). - Writes the Claude Code hook scripts to
~/.thlibo/hooks/. - Merges PreToolUse/Bash, PowerShell, Read, and Write/Edit hook
entries into
~/.claude/settings.json(preserves all your existing hooks and settings verbatim — idempotent). - Probe-then-delegate inferd: if inferd is already running, uses it; if installed but stopped, starts it; otherwise downloads the latest inferd release and runs inferd's bundled installer (which registers inferd's own per-platform autostart).
- Reports the plan as it goes (use
--dry-runto see without touching anything).
No admin/root required, on any OS — for thlibo or inferd. All three
inferd autostart mechanisms are per-user and need no elevation: a
launchd LaunchAgent (macOS), a systemctl --user unit (Linux), and a
Startup-folder shortcut (Windows). A fresh install goes from nothing to
a running, login-autostarting daemon with no manual step.
--dry-run Report the plan, don't apply it.
--processors-dir Override ~/.thlibo/processors.
--hook-dir Override ~/.thlibo/hooks.
--settings Override ~/.claude/settings.json.
--skip-hook Mirror processors only; don't touch Claude Code.
--skip-inferd Don't probe / install the inferd sidecar.
--inferd-version Pin inferd to a specific tag (default: latest).
--codex Also install the Codex CLI PostToolUse hook.
--cursor Also install the Cursor IDE preToolUse hook.
--copilot Also install the GitHub Copilot CLI hooks.
With --codex, thlibo appends an inline [[hooks.PostToolUse]] block
(matcher ^Bash$) to ~/.codex/config.toml and sets [features] hooks = true in the same file — written inline, not to a separate hooks.json,
because Codex warns and hides hooks when one config layer mixes both
representations (#170); any stale thlibo hooks.json entry from an older
install is removed. Codex gates command hooks behind a trust step:
after install, run /hooks inside Codex, review the thlibo hook, and
approve it — until you do, Codex sees the hook but won't run it
(compression stays off). The installer prints this reminder.
With --copilot, thlibo writes ~/.copilot/hooks/thlibo.json plus four
hook scripts (a Bash + PowerShell pair for each event). Copilot reads
every *.json in that directory and each tool owns its own file, so
thlibo's never collides with another tool's. Two hooks are installed: a
preToolUse hook that rewrites a shell command's input
(git status → thlibo exec -- git status) via modifiedArgs, and a
postToolUse hook that replaces any verbose tool's output with a
compressed version via modifiedResult. Copilot's preToolUse is
fail-closed, so the hook only ever allows — it never blocks a tool
call; postToolUse is fail-open. Restart Copilot CLI to load the hooks.
VS Code Copilot (1.111+, currently Insiders) comes along for free.
VS Code reads agent hooks from ~/.copilot/hooks/ too, so the same
thlibo.json is auto-discovered — no extra install step. VS Code uses
a different wire format than the CLI (the Claude-Code envelope:
tool_input / hookSpecificOutput.updatedInput, and an observe-only
postToolUse), so the hook scripts detect which host is calling and
reply in the matching format. On VS Code, shell output is compressed via
the preToolUse command-wrap (its postToolUse can't replace output — same
limitation as Claude Code). No --vscode flag is needed; --copilot
covers both.
The model GGUF (~5.1 GB Gemma 4 E4B) is downloaded by inferd on
first inference request, into a shared per-platform model store
(~/.local/share/models/ on Linux, ~/Library/Application Support/models/
on macOS, %LOCALAPPDATA%\models\ on Windows). thlibo doesn't
manage the model — that's inferd's job.
thlibo install touches these paths and nothing else. Every hook and
skill file is SHA-stamped: if you've edited one, the new version lands
at <path>.new and your edit is preserved.
| Path | What |
|---|---|
~/.local/bin/thlibo (Unix) · %LOCALAPPDATA%\thlibo\bin\ (Win) |
the thlibo binary (placed by the one-liner installer; on Windows the dir is added to the User PATH) |
~/.thlibo/hooks/ |
the six PreToolUse hook scripts (Bash + PowerShell variants of exec / read / write) |
~/.thlibo/processors/ |
the embedded built-in processors, mirrored to disk (your own processors here are left untouched) |
~/.claude/settings.json |
only the hooks block — PreToolUse matchers for Bash, PowerShell, Read, Write, Edit are merged in; every other key and hook you have is preserved verbatim |
~/.claude/skills/caselog/ |
the /caselog skill |
inferd binary + backends/ libs |
~/.local/bin (Unix) · %LOCALAPPDATA%\inferd\ (Win), via inferd's installer |
| inferd autostart | LaunchAgent (macOS) · systemctl --user unit (Linux) · Startup-folder shortcut (Windows) |
~/.codex/config.toml |
only with --codex — inline [[hooks.PostToolUse]] block + [features] hooks = true appended (a stale hooks.json entry, if any, is removed) |
~/.copilot/hooks/thlibo.json + four hook scripts |
only with --copilot |
On macOS the one-liner installer (install.sh) also strips the
Gatekeeper quarantine attribute (xattr -d com.apple.quarantine) from
the downloaded thlibo binary so it runs without a "blocked" dialog.
thlibo install does not modify any other settings.json keys — in
particular it does not touch skipDangerousModePermissionPrompt,
skipWebFetchPreflight, or any permission/safety setting.
These are intentional and named in THREAT_MODEL.md
(findings MA-2 and MA-6); calling them out here so they aren't a
surprise:
- The hooks auto-allow their own rewrites. When a PreToolUse hook
rewrites a command (or substitutes a compressed file for the Read
tool), it emits
permissionDecision: "allow"for that single, thlibo-rewritten invocation — so Claude Code doesn't re-prompt for the thing thlibo just produced. It only ever allows the rewritten form it emitted; it does not blanket-allow other commands. The rewritten command is visible to you and logged bythlibo exec. - The PreToolUse hook is persistent. It's a one-time install but
the hook stays in
~/.claude/settings.jsonand intercepts matching tool calls in every future Claude Code session until you runthlibo uninstall.cat ~/.claude/settings.jsonto see it.
thlibo uninstall # remove hooks + scripts; leave ~/.thlibo
thlibo uninstall --purge # also delete ~/.thlibo (processors + state)uninstall removes the Claude Code hook entries and thlibo's own
~/.copilot/hooks/thlibo.json (leaving any other tool's hook file
untouched). It does not unpick the inline hook thlibo appended to
~/.codex/config.toml or the Cursor hooks.json entry — remove those by
hand if you installed them.
Inferd is left running because other tools may use it. To remove inferd separately, use inferd's own uninstaller — see inferd's docs.
mkdir -p ~/.thlibo/processors/my-tool
cat > ~/.thlibo/processors/my-tool/processor.yaml <<'YAML'
name: my-tool
type: script
entry: run.py
commands:
- my-custom-cli
match: "^Running: "
description: >
Compresses my-custom-cli's verbose progress output to a summary line.
YAML
cat > ~/.thlibo/processors/my-tool/run.py <<'PY'
import sys
for line in sys.stdin:
if not line.startswith("Progress:"):
sys.stdout.write(line)
PY
chmod +x ~/.thlibo/processors/my-tool/run.pyRestart your shell (or re-run thlibo install) and the hook picks
it up on the next Claude Code invocation. User processors with the
same name as a built-in override the built-in.
| Script | Prompt | |
|---|---|---|
| Descriptor | processor.yaml + entry file |
processor.md (YAML frontmatter + body) |
| Speed | ~10 ms | ~200-800 ms (inferd round-trip) |
| Determinism | Always the same output for the same input | Model-dependent |
| When to use | Fixed-format output (git, npm, cargo, known log schemas) | Unfamiliar output; stack traces; arbitrary logs |
| Inferd needed? | No | Yes |
| Name | Type | Handles |
|---|---|---|
git-filter |
native | git status, git diff, git log |
npm-filter |
native | npm, npx, pnpm, yarn |
cargo-filter |
native | cargo build, cargo test, cargo clippy |
pytest-filter |
native | pytest output |
ndjson-filter |
native | structured-log streams |
stacktrace-filter |
native | Python / Go / Rust / Java / Node stack traces |
lint-filter |
native | clang, gcc, clippy, eslint, golangci-lint, gosec, shellcheck, flake8, ruff, mypy, rubocop, stylelint. Auto-wraps gosec, staticcheck, golangci-lint, shellcheck (not go/make/docker — see below) |
go-test-filter |
native | go test -v / go test -json — keeps failures + package tally, drops passing-test noise. Auto-wraps go test (only that subcommand) |
trivy-filter |
native | trivy scan output — keeps findings by severity, drops the per-target boilerplate |
cordon-filter |
native | Semantic anomaly surfacer for logs: windows the input, embeds each window via inferd, ranks by k-NN distance and keeps the outliers. Fires automatically when ndjson-filter over-collapses a stream (so a repetitive access log doesn't compress down to one uninformative row), or on request in a chain. Never model-selected |
har-filter |
native | .har (HTTP Archive) captures — content-matched, not command-wrapped. One redacted line per request (METHOD status url (mime size ms)); drops static assets + non-text bodies + timing plumbing; redacts query-string secrets, auth headers, POST-body creds, JWTs + long tokens (typically ~99% smaller) |
mhtml-filter |
native | .mhtml/.mht saved-web-page archives — content-matched. Extracts the article HTML from the MIME bundle → Markdown (headings, lists, links, code/pre, tables, images as  refs); drops the base64-embedded images/CSS/scripts that are ~90% of the file (typically ~98% smaller) |
pdf-filter |
native | PDF → GitHub-flavored markdown, in-process, no Python — content-matched on %PDF-. Four tiers, best evidence first: a Tagged PDF's own /StructTreeRoot (headings, tables and reading order stated by the producer), native text extraction, geometry table detection, and a [scanned page N] placeholder handing image-only pages to the OCR path. Also emits document metadata + bookmark outline; drops running headers/footers, promotes numbered headings, rejects invisible layout grids so a slide deck's positioning frames aren't reported as tables. Encrypted documents pass through untouched (typically ~97% smaller) |
pdf-to-md |
script | The OCR path for scanned PDFs — rasterizes pages for inferd's Gemma vision model (ADR 0009). Born-digital PDFs go to pdf-filter instead |
shorthand |
prompt | LLM-facing prose compression (SKILL.md, CLAUDE.md, system prompts) |
compress |
prompt | Generic verbose output, fallback |
casefolder |
prompt | Stack traces, error logs, crash output |
go is matched per-subcommand. go test wraps (→ go-test-filter),
but go build / go run / go vet / go generate do not — go's
argv[0] is multiplexed, so a command_prefixes: ["go test"] rule wraps
exactly the test verb and leaves the others alone. Intentionally not
wrapped at all (a recorded decision): make, docker build — they
emit too many output shapes for a single filter to compress safely.
# Inferd sidecar is running
# Linux: systemctl --user is-active inferd
# macOS: launchctl list | grep io.inferd.daemon
# Windows: sc.exe query inferd-daemon
# Hook is registered in Claude Code
grep -c thlibo ~/.claude/settings.json
# Expected: 5+ (Bash + PowerShell + Read + Write + Edit matchers)
# Direct test of the rewrite path
thlibo rewrite 'git status'
# Expected stdout: "<thlibo-path> exec -- git status"
# Expected exit: 0
# Direct test of the exec path
thlibo exec -- git diff HEAD~5 | wc -c
# Expected: far fewer bytes than `git diff HEAD~5 | wc -c` alone.If the hook silently doesn't fire in a Claude Code session, check the debug log:
claude --debug-file /tmp/claude.log 'Run git status via Bash'
grep -E 'Hook|PreToolUse|updatedInput' /tmp/claude.logYou should see Hook PreToolUse:Bash (PreToolUse) success: with a
updatedInput object pointing at thlibo exec -- .... If you don't,
the hook script isn't being invoked — usually a PATH issue (the hook
needs thlibo and jq on Claude Code's Bash PATH).
thlibo uses stdout and stderr separately and on purpose:
- stdout — only the compressed (or pass-through) bytes the AI client should consume. Always safe to capture.
- stderr — diagnostics: reduction summaries, fallback reasons ("backend unavailable; emitting original"), and the occasional background update-available banner.
Don't merge them with 2>&1 when feeding output to an AI client or
to thlibo itself. The update banner and other stderr lines are
human diagnostics, not data — merging them risks polluting the
captured payload. Examples:
# Good: only the compressed payload reaches the AI.
thlibo exec -- git diff HEAD~5 > diff.compressed
# Good: keep stderr visible for the human in the terminal,
# stdout clean for the pipe.
thlibo exec -- npm install | other-tool
# Avoid: merges human diagnostics into the data stream.
thlibo exec -- git diff 2>&1 | other-toolIf you must capture stderr for debugging, route it to its own file
(2>thlibo.err) instead of merging.
Temporarily stop compressing without removing anything:
# Set this in your shell profile or Claude Code environment:
export THLIBO_DISABLED=1Every hook honours this flag and exits passthrough immediately.
thlibo can emit OpenTelemetry metrics and events so you can see how
much it's saving — for a single developer, or aggregated org-wide. It
mirrors Claude Code's monitoring model:
off by default, enabled by one flag, configured through the
standard OTEL_* environment variables, and pointed at your own
collector. thlibo only emits; you own the collector, storage, and
dashboards (Grafana, Honeycomb, a raw OTLP receiver — your choice). See
ADR 0011.
# Enable + point at a collector (recommended: one on localhost).
export THLIBO_ENABLE_TELEMETRY=1
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf # or grpc
# Optional: label your data for org rollups.
export OTEL_RESOURCE_ATTRIBUTES=team.id=platform,department=engConfig is read from the process environment (your shell/profile), not
from the AI client — Claude Code doesn't pass its own OTEL_* to hook
subprocesses, so thlibo's telemetry is configured independently.
| Variable | Meaning | Default |
|---|---|---|
THLIBO_ENABLE_TELEMETRY |
master enable | unset → off |
OTEL_METRICS_EXPORTER |
otlp | console | none |
otlp |
OTEL_LOGS_EXPORTER |
otlp | console | none |
otlp |
OTEL_EXPORTER_OTLP_ENDPOINT |
collector endpoint | http://localhost:4318 |
OTEL_EXPORTER_OTLP_PROTOCOL |
http/protobuf | grpc |
http/protobuf |
OTEL_EXPORTER_OTLP_HEADERS |
auth headers | — |
OTEL_SERVICE_NAME / OTEL_RESOURCE_ATTRIBUTES |
resource / org labels | thlibo / — |
Metrics (namespaced thlibo.*): invocations,
bytes.processed, bytes.saved, compression.ratio,
dispatch.duration, fallbacks. Savings are reported in exact
bytes (thlibo.bytes.saved = bytes in − bytes out); thlibo has no
tokenizer, so convert bytes → tokens → cost in your dashboard. One
event (thlibo.compression) is emitted per invocation with
{ processor, path, outcome, bytes_in, bytes_out, duration_ms }.
What is never emitted: tool output, prompts, shell commands, or
file paths — only sizes, counts, durations, and fixed enum labels.
There is no content-capture opt-in. Built-in processor names appear
verbatim; user processor names are redacted to "custom".
Cost when off: zero. With THLIBO_ENABLE_TELEMETRY unset, no SDK
is constructed and nothing runs on the hook path.
Latency when on: thlibo's hook subcommands are short-lived (they exit per tool call), so telemetry is force-flushed on exit within a fixed 2-second cap. Against a localhost collector that's microseconds; a misconfigured/unreachable remote endpoint makes each call wait up to 2 s before dropping the batch — telemetry is always best-effort and never blocks or breaks the AI client. Run a collector on localhost.
Both follow standard OpenTelemetry behaviour, but they bite the first time:
- Set
OTEL_*in your shell profile, not just the AI client's env. When thlibo runs from an AI client's hook (Claude Code's Bash-tool PreToolUse hook, for example), the client does not pass its ownOTEL_*into hook subprocesses — Claude Code deliberately strips them.THLIBO_ENABLE_TELEMETRYset in the client env still reaches thlibo, but theOTEL_EXPORTER_OTLP_*endpoint/protocol vars won't. Export them from your~/.bashrc/~/.zshrc/ PowerShell$PROFILE(or a system-wide env) so every thlibo subprocess inherits them. - A plaintext local collector needs an
http://endpoint (orINSECURE). With no endpoint set the OTLP exporter defaults to TLS, which fails silently against a plaintext collector (thlibo fails open and drops the data). Either give an explicitOTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318, or setOTEL_EXPORTER_OTLP_INSECURE=true.
A collector is a shared sink: thlibo, the AI client, and any other
instrumented tool can all point at the same one. Separate the sources
at query time by service.name (thlibo sets thlibo) or by the
metric-name prefix (thlibo.* vs claude_code.*). thlibo only ever
emits its own thlibo.* signals.
- All-local at runtime. No network calls during inference. The inferd sidecar listens only on a Unix domain socket / Windows named pipe / loopback TCP — never on a public interface.
- One network touch per host: model download. Inferd fetches the GGUF on first request and verifies a SHA-256 published in inferd's own release. After that, the daemon is offline.
- User-scoped. On Unix, the inference socket is mode 0660 owned
by group
inferd-users(or user-only when the group doesn't exist); admin socket is 0600 owned by the daemon user. On Windows, the pipe ACL grants the current user only; Everyone is excluded. - No elevation, anywhere.
thlibo installruns entirely under your user account, and so does inferd's: all three autostart mechanisms are per-user — LaunchAgent (macOS),systemctl --userunit (Linux), Startup-folder shortcut (Windows). No admin/root at any point. - Fallback on every error. If anything in the compression path fails — inferd unreachable, script crashes, processor times out, malformed response — the original output is returned unchanged. The AI never sees a broken intermediate state.
- Hook auto-allows rewritten commands. By design, the PreToolUse
hook returns
permissionDecision: allowfor every Bash command it rewrites so users aren't re-prompted for the same action. That means: once installed, every Bash tool-call that matches the hook matcher runs through thlibo's rewrite without a Claude Code permission prompt. SeeTHREAT_MODEL.mdfinding #15 for the trade-off discussion. - Activity log redaction.
~/.thlibo/logs/*.ndjsonrecords byte-count telemetry only; subprocess stderr and error strings pass through a secret-pattern redactor before disk write (AWS keys, GitHub PATs, HuggingFace tokens, genericSECRET=/API_KEY=assignments). The redactor is a best-effort backstop, not a replacement for keeping secrets out of subprocess output. - Telemetry is opt-in and content-free. OpenTelemetry emission
(see Monitoring) is off unless
THLIBO_ENABLE_TELEMETRYis set. When on, it emits only sizes, counts, durations, and fixed enum labels to a collector you configure — never tool output, prompts, commands, or file paths; user processor names redact to"custom". It fails open (a dead collector never blocks a tool call). SeeTHREAT_MODEL.md(2026-07-14 addendum) and ADR 0011. - Inferd version gate. thlibo refuses to delegate to inferd
binaries older than
MinInferdVersion(currently v0.4.0 — the first release with the unified IPC wire thlibo's codec speaks; earlier daemons are unreachable). The gate detects an older daemon and triggers a fresh inferd install instead of failing open forever. - Supply chain. Every GitHub Action in this repo is pinned by
commit SHA. Every release archive, the
SHA256SUMS, and the CycloneDX SBOM are signed with cosign via Sigstore's keyless flow — no key to manage, identity rooted in the GitHub OIDC token for.github/workflows/release.ymlat the release tag, transparency- log entry published torekor.sigstore.dev. Verification command is in the release notes for each tag. The release pipeline runs the installer scripts against the just-built archive on ubuntu-latest + windows-latest beforegh release create— a broken installer cannot ship. A full threat model lives atTHREAT_MODEL.md.
- Bash, PowerShell, Read, and Write/Edit tool coverage; MCP tools
bypass. The PreToolUse hooks intercept Claude Code's
Bashtool,PowerShelltool (whenCLAUDE_CODE_USE_POWERSHELL_TOOL=1),Readtool (for files dragged into the window or referenced by path), andWrite/Edittools (when shorthand auto-apply is enabled).Grep/Glob/MCP-served tools bypass the hook — their inputs and outputs are not intercepted. - Cursor: shell + file reads, no MCP.
thlibo install --cursorinstalls twopreToolUsehooks. The Shell hook rewrites the command (viaupdated_input) to run throughthlibo exec, so terminal output is compressed before the model reads it. The Read hook rewritestool_input.file_pathto a pre-builtthlibo case(compressed.log) so large logs/PDFs are compressed too — bounded by a timeout (THLIBO_READ_TIMEOUT, default 20s) so a slow scanned-PDF OCR falls through to the original rather than hanging Cursor. Cursor's hooks still cannot substitute MCP-tool output for built-in tools (afterShellExecutionis observe-only;updated_mcp_tool_outputis MCP-server-only). User-level~/.cursor/hooks.jsonloads automatically; project-scoped hooks require a trusted workspace. - Compound shell commands pass through.
git status | headorcmd1 && cmd2are not rewritten — only single-program invocations.thlibo rewritematches onargv[0]and deliberately doesn't try to parse a shell AST. - Inferd is a separate dependency. thlibo no longer ships its
own inference daemon. The first
thlibo installon a fresh host pulls inferd over HTTPS and runs inferd's installer; if you need air-gapped install, fetch inferd manually first (see github.com/3rg0n/inferd) and thlibo's probe-then-delegate will use it without touching the network.
- AI-assistant guidance:
CLAUDE.md. - Architecture decisions:
docs/adr/. - Changelog:
CHANGELOG.md. - Run the tests:
go test ./... -timeout 120s - Scanner sweep:
go vet ./... && staticcheck ./... && gosec ./... && govulncheck ./... - Fuzz the PDF parser (the untrusted-input boundary):
go test ./internal/pdf/ -run '^$' -fuzz '^FuzzOpenBytes$' -fuzztime 60s. Three targets —FuzzOpenBytes,FuzzParseInlineDict,FuzzDecodeTextString— run nightly in CI, not per-PR. Touching the parser? Soak it before the PR rather than waiting for the nightly. - Benchmark the two filters whose cost is load-bearing:
go test ./internal/processors/ -run '^$' -bench . -benchmem. Compare revisions withbenchstatrather than eyeballing one run. Two ceiling tests run with the ordinary suite (-run StaysUnderBudget) — forcordon-filterthat ceiling is an availability guard, not a speed one: its k-NN pass is O(n²), and once it can no longer finish insideCORDON_TIMEOUTthe filter fails open and silently becomes a no-op that every other test still passes.
cmd/
thlibo/ User CLI: rewrite, exec, compress, case, install,
uninstall, shorthand, version.
internal/
adapters/
claudecode/ PreToolUse hooks (Bash + PowerShell + Read + Write/Edit),
/caselog skill, settings.json merger.
codex/ PostToolUse hook (decision: block) + config.toml merger.
cursor/ preToolUse hooks (Shell + Read updated_input) + hooks.json merger.
copilot/ preToolUse (modifiedArgs) + postToolUse (modifiedResult) hooks.
casefile/ `thlibo case` directory builder (compressed.log + summary + meta).
config/ ~/.thlibo/config.yaml read/write.
execpolicy/ `thlibo exec` allow/deny policy.
inferd/ thlibo's own codec for inferd's v2 IPC wire
(length-prefixed framing); no client dependency.
install/ Disk mirror + per-platform inferd sidecar installer
(probe-then-delegate) + v0.5 → v0.6 migration.
logx/ NDJSON activity log with rolling rotation + secret redactor.
middleware/ Main flow: short-circuit → fast-path → router → chain.
processors/ Registry, descriptors, script+prompt dispatch, thought-stripping.
promptsan/ Gemma marker sanitiser for untrusted tool output.
router/ Processor routing via inferd response_format (JSON-Schema).
shellcmd/ Minimal shell-command argv[0] extractor.
shorthand/ LLM-facing prose compression (SKILL.md / CLAUDE.md).
update/ Background release check + upgrade banner.
version/ Build-tag constant (overridable via -ldflags).
processors/ Embedded built-ins (go:embed).
skills/ Claude Code skills: /caselog.
The Greek word θλίβω means to press, squeeze, compress. Same root as "tribulation" — being crushed down. Thlibo crushes tool output before the model ever sees it.