Safe-by-default CLI layer for AI agents — less noise, less risk: no-shell proxy, fail-closed policy, deterministic output filtering, local gain (Cursor-friendly)
Project description
PyRTKAI
Less noise. Less risk. Same commands.
Safe-by-default execution layer for Cursor and other AI-driven terminals — local, inspectable Python (stdlib-only at runtime).
Summary
| What | CLI proxy, hook, rewrite, output filtering, fail-closed policy, optional gain metrics |
| Who | Teams using Cursor or any agent-heavy shell / automation |
| Outcome | Less wasted context on huge logs, fewer shell surprises, JSON tool output preserved when detected |
| Safety | proxy runs the child with argv-only subprocess (no shell); bad deny-regex config → deny |
Not a ChatGPT/OpenAI client: no API keys or cloud calls for core behavior — see FAQ.
Quickstart (~60 seconds)
Copy-paste into a clean venv (Python 3.11+):
python3 -m venv .venv && .venv/bin/pip install -U pip pyrtkai
.venv/bin/pyrtkai doctor --json
.venv/bin/pyrtkai proxy python3 -c "print('ok')"
Optional: stdin/stdout hook JSON:
printf '%s' '{"tool_input":{"command":"echo hi"}}' | .venv/bin/pyrtkai hook
Features
- Context optimization — deterministic truncation + marker; optional gain with
tokens_saved_est/tokens_saved_pct_est(char-based estimates, not provider tokenizers). - Safe execution —
proxyuses argv-onlysubprocess(no shell) for the executed program. - Predictable policy —
PYRTKAI_DENY_REGEXES/PYRTKAI_DENY_REGEX; invalid configuration → deny (fail-closed). - Stable tool contracts — JSON / NDJSON pass-through when detected.
- Cursor-ready — hook adapter,
doctor/verify-hook, bundle underintegrations/cursor-plugin/.
Why PyRTKAI exists
- Noise is expensive — long command output consumes model context even when the answer is short.
- Shells are sharp — agents and wrappers often re-introduce quoting and injection risk.
- Safety should default closed — ambiguous policy configuration should deny, not silently allow.
- Thin, inspectable layer — small codebase, tests, optional local SQLite metrics only.
Before / after (typical behaviors)
| Situation | Without PyRTKAI | With PyRTKAI |
|---|---|---|
Very long stdout |
Full text reaches the agent/session | Text may be truncated with a fixed marker (PYRTKAI_OUTPUT_MAX_CHARS) |
| Messy shell pipeline / heredoc | Higher risk and harder reasoning | rewrite tends to skip risky shapes; proxy stays no-shell |
| Tool returns JSON | Naive truncation may break parsers | Structured pass-through when JSON/NDJSON is detected |
| Bad deny-regex config | Some systems ignore errors | Fail-closed deny for invalid policy input |
Demo: truncated text (real proxy output)
export PYRTKAI_OUTPUT_MAX_CHARS=120
pyrtkai proxy python3 -c "print('x' * 400)"
Expect first half + default marker ...[TRUNCATED]... + last half (sizes scale with PYRTKAI_OUTPUT_MAX_CHARS).
Demo: JSON unchanged
pyrtkai proxy python3 -c "import json; print(json.dumps({'ok': True, 'id': 42}))"
Valid JSON on one line is not shortened by the text filter.
Compared to common alternatives
| Approach | Trade-off |
|---|---|
| Raw shell for every agent command | More shell-injection and quoting risk |
Ad-hoc head / manual copy |
Often breaks structured output; not a stable contract |
| IDE-only allow/deny lists | Useful, but host-specific; PyRTKAI adds a portable policy + filtering layer |
| Larger “do everything” CLI proxies | Broader surface; PyRTKAI optimizes for predictability, tests, and small code |
Security
- No shell in
proxy: child processes use argv-onlysubprocess— see SECURITY.md (reporting, scope, threat model). - Fail-closed policy: invalid deny-regex configuration denies.
- Hook integrity: SHA-256 baseline vs script on disk —
pyrtkai doctor,pyrtkai verify-hook --json(details). - CI:
ruff,mypy,bandit,pip-audit,pip check(workflow).
Cursor integration
Step-by-step guide (install, PYRTKAI_PYTHON, hooks.json, verify-hook, troubleshooting): integrations/cursor-plugin/README.md.
Installation
The installed command is pyrtkai (Python 3.11+).
From PyPI (typical users)
python3 -m venv .venv
.venv/bin/pip install -U pip
.venv/bin/pip install pyrtkai
.venv/bin/pyrtkai --help
Activate the venv or call .venv/bin/pyrtkai directly. If pyrtkai is not on the PATH Cursor uses, set PYRTKAI_PYTHON to your venv’s python (absolute path).
PEP 668: On many Linux distributions, pip install into the system Python fails with externally-managed-environment. Use a venv (as above), pipx, or another dedicated environment—not sudo pip unless you understand the risks.
From a source clone (contributors, Cursor plugin dev)
Use a venv in the repository root and an editable install:
cd /path/to/PyRTKAI
python3 -m venv .venv
.venv/bin/pip install -U pip
.venv/bin/pip install -e .
.venv/bin/pyrtkai --help
Cursor hook: set PYRTKAI_PYTHON to .venv/bin/python (absolute path) so IDE hooks use the same interpreter.
Cursor plugin bundle
See Cursor integration and integrations/cursor-plugin/README.md.
Use cases
- Cursor agent sessions — shrink noisy
git/build/testoutput before it hits the model; optional gain to quantify estimates. - Terminal automation — same filtering/proxy path for scripts that wrap CLI tools.
- CI / developer machines — enforce deny patterns (
PYRTKAI_DENY_REGEX*) at the hook layer alongside IDE rules. - Log-heavy commands — deterministic truncation instead of ad-hoc
head/tailthat breaks JSON.
Benchmarks
Measure proxy vs direct subprocess latency ratio (local, no network):
pyrtkai bench proxy --iters 10 --json -- python3 -c "print(1)"
Interpretation: ratios depend on OS and workload; use the same command you care about in production.
Command reference
Run pyrtkai --help and pyrtkai <subcommand> --help for the full CLI.
| Area | Commands |
|---|---|
| Health / config | doctor [--json], config [--json] |
| Core | rewrite <words…> (rewrite decision), proxy <argv…> (run command with filtering) |
| Cursor hook | hook (stdin/stdout JSON), verify-hook [--json] |
| Metrics | gain summary, gain export, gain history (optional --json, --limit); gain project [--root PATH] |
| Benchmark | bench proxy --iters N [--json] -- <argv…> |
Examples:
pyrtkai doctor --jsonpyrtkai config --jsonpyrtkai rewrite git statuspyrtkai proxy git statuspyrtkai hookreads hook JSON from stdin and writes the adapted JSON response to stdout.pyrtkai verify-hook --jsonverifies the SHA-256 integrity baseline for the installed hook script (when using the Cursor bundle).pyrtkai gain summary --jsonpyrtkai gain export --limit 1000pyrtkai bench proxy --iters 5 --json -- python3 -c "print(1)"
Example: doctor --json (shape)
Fields vary by machine; expect keys such as hook_integrity, hooks_json, mvp_rewrite_rules, and output_filter:
{
"hook_integrity": {
"ok": true,
"reason": "ok",
"hook_path": "/path/to/pyrtkai-rewrite.sh",
"baseline_path": "/path/to/pyrtkai-rewrite.sh.sha256"
},
"hooks_json": {"present": true, "configured": true},
"mvp_rewrite_rules": {"git_status": true, "ls": true, "grep": true, "rg": true},
"output_filter": {"profile": "truncating", "max_chars": 4000}
}
Example: rewrite (illustrative)
pyrtkai rewrite git status
Stdout is a single JSON object describing rewrite/skip and reasons (exact schema: run the command locally).
Doctor and config
pyrtkai doctor checks local health:
- whether the hook script integrity baseline matches
- whether a
hooks.jsonfile exists - whether
hooks.jsonis configured to call the expected hook script - which MVP rewrite rules are currently enabled
The doctor --json output includes:
hook_integrity.okhook_integrity.hook_path/hook_integrity.baseline_path(which files were checked)hooks_json.presenthooks_json.configuredmvp_rewrite_rulesoutput_filter.profileoutput_filter.max_chars
pyrtkai config is a lightweight command for inspecting the enabled MVP rewrite rules:
pyrtkai config --json
Configuration
Full reference: docs/environment-variables.md (all PYRTKAI_* variables in one place).
Environment variables control the rewrite allow list, output truncation, and policy gating.
MVP rewrite enable flags:
PYRTKAI_MVP_ENABLE_GIT_STATUS(default true)PYRTKAI_MVP_ENABLE_LS(default true)PYRTKAI_MVP_ENABLE_GREP(default true)PYRTKAI_MVP_ENABLE_RG(default true)
Any of these values disable the rule:
0falsenooff
Output filtering:
PYRTKAI_OUTPUT_MAX_CHARSinteger >= 0 (default 4000)PYRTKAI_TRUNC_MARKERmarker string (default contains a truncation marker)PYRTKAI_OUTPUT_FILTER_PROFILE(defaulttruncating)
Policy gate deny patterns:
PYRTKAI_DENY_REGEXEScomma-separated regex patternsPYRTKAI_DENY_REGEXsingle regexPYRTKAI_DENY_REGEX_MAX_INPUT_CHARS(default65536): if the original or rewritten command string exceeds this length while deny patterns are configured, the policy gate denies (fail-closed). Mitigates pathological regex cost on huge commands; increase only if needed.
Limits (doctor and policy)
pyrtkai doctorreads~/.cursor/hooks.jsononly if its size is at most 1 MiB. Larger files are treated as present but not parsed, sohooks_json.configuredmay stay false even when the file exists.- With
PYRTKAI_DENY_REGEXES/PYRTKAI_DENY_REGEXset, very long commands are subject toPYRTKAI_DENY_REGEX_MAX_INPUT_CHARS(see above).
Gain tracking (local SQLite)
When PYRTKAI_GAIN_ENABLED=1, events are stored under PYRTKAI_GAIN_DB_PATH (default ~/.pyrtkai/gain.sqlite). Older rows are pruned using PYRTKAI_GAIN_RETENTION_DAYS (default 30). Treat the DB path like any local credential store: use a user-writable directory you trust; do not point it at world-writable locations in multi-user setups.
CLI: pyrtkai gain with no subcommand (summary / export / history) behaves the same as pyrtkai gain summary: both print the aggregated summary (JSON if --json). Use explicit subcommands when you want export/history or separate --limit on summary.
Understanding savings (amount and percent)
PyRTKAI does not call a model tokenizer. It estimates “tokens” from character counts of stdout/stderr (default: one token ≈ four characters, overridable with PYRTKAI_CHARS_PER_TOKEN). That makes numbers stable and comparable across runs, but they are not identical to provider tokenizer counts.
After commands run through pyrtkai proxy, read totals with:
PYRTKAI_GAIN_ENABLED=1 pyrtkai gain summary --json
| Field | Meaning |
|---|---|
tokens_before |
Estimated tokens that would correspond to the full captured stdout+stderr before filtering (pass-through or truncated). |
tokens_after |
Estimated tokens in what was actually printed after filtering (e.g. shorter text if truncation applied). |
tokens_saved_est |
tokens_before − tokens_after. The “amount” of estimated savings (larger ⇒ more characters removed by the filter). |
tokens_saved_pct_est |
100 × tokens_saved_est / tokens_before, rounded to two decimals. Share of estimated output “trimmed” relative to the pre-filter size. null when there is nothing to compare (e.g. empty DB or tokens_before is 0). |
Per-command groupings live under by_classification (same fields per group, including tokens_saved_pct_est).
How to read it: use tokens_saved_est for a rough quantity of reduction; use tokens_saved_pct_est to see how large that reduction was relative to the original output size (e.g. 50% means about half of the estimated pre-filter volume was not printed after filtering). Short runs with little or no truncation often show 0 saved and 0% (or null percent when there is no baseline).
Known limitations (heuristics and deny-regex)
- Rewrite registry skips MVP rewrite if the command string contains substrings such as
--json,--format, or--templateanywhere (conservative; may skip in edge cases such as odd paths). - Proxy streaming treats output that begins (after whitespace) with
{or[as JSON pass-through; rare text that looks like JSON at the start will not be truncated. - Deny regexes (
PYRTKAI_DENY_REGEXES): input length is capped (see Limits), but a regex with catastrophic backtracking can still be expensive on worst-case input within that cap. Prefer simple patterns; for stricter isolation run hooks in a resource-limited environment.
FAQ
- Do I need an OpenAI / ChatGPT API key? No. PyRTKAI does not call cloud LLM APIs for its core CLI,
proxy, orhookpaths. - Why does
pip installfail on Debian/Ubuntu system Python? Many distros use PEP 668 (externally-managed-environment). Use a venv, pipx, or install into a user environment you control — notsudo pipunless you understand the risks. pyrtkainot on PATH inside Cursor — setPYRTKAI_PYTHONto the absolute path of the interpreter that has PyRTKAI installed (see integrations/cursor-plugin/README.md).- Why was my JSON output not truncated? Output that looks like JSON/NDJSON at the start is treated as structured pass-through so tools do not break.
- How do I loosen or tighten filtering? See
PYRTKAI_OUTPUT_MAX_CHARS,PYRTKAI_OUTPUT_FILTER_PROFILE, and docs/environment-variables.md. - How do I effectively disable text truncation? Set
PYRTKAI_OUTPUT_MAX_CHARSto a very large value (e.g.999999999). There is no separate “off” switch;0is not recommended (behavior is edge-case–sensitive). - How do I debug a failing hook? Run
pyrtkai doctor --jsonandpyrtkai verify-hook --json; run the hook script from a terminal with the samePYRTKAI_PYTHONand watch stderr; usepyrtkai hookwith a minimal JSON payload (see Quickstart). - How do I know the Cursor hook is active? Run
pyrtkai doctor --jsonand inspecthooks_json/hook_integrity; usepyrtkai verify-hook --jsonfor the bundled script checksum. - Where are security vulnerabilities reported? See SECURITY.md (private channel — not a public issue for undisclosed vulnerabilities).
License: MIT.
Contributing
- Contributor guide: CONTRIBUTING.md (tests, linters, packaging, PyPI).
- Roadmap: docs/product-roadmap.md.
- Forks: if your canonical GitHub repository is not the one listed in
[project.urls]insidepyproject.toml, update those URLs so PyPI and metadata point to your repo. - Pull requests: run the same checks as CI before pushing:
make test,make lint,make typecheck, andmake security(or the individualpytest/ruff/mypy/bandit/pip-auditcommands). Optionally runpython -m buildto verify wheels/sdists. - Commits: large documentation-only changes (e.g. under
.doc/) can be split from code commits if you want a clearer history—optional, not required.
Development and testing
Common commands:
make testmake lintmake typecheckmake security(same tools as CI whenruff,mypy,bandit, andpip-auditare installed)
Direct checks:
pytestruff check src testsmypy src testsbandit -r src -q(application code; tests usesubprocessintentionally in harnesses)pip-auditorpip-audit --skip-editable(for a local editable install, PyPI may not resolve the package name andpip-auditcan report Skip Reason: Dependency not found on PyPI — that is expected; CI uses a normal install from the repo)
Optional: bandit on tests
To scan tests/ with relaxed skips (assert/subprocess/random in harnesses), use the bundled config:
bandit -c bandit-tests.yaml -r tests -q
Performance SLO (optional)
Loose proxy-overhead checks live in tests/test_performance_slo.py. Local run:
PYRTKAI_ENFORCE_PERF_SLO=1 pytest -q tests/test_performance_slo.py
Profiling slow deny-regex / hook paths (optional)
PyRTKAI does not ship a regex timeout. If policy matching feels slow, simplify PYRTKAI_DENY_REGEXES, reduce pattern complexity, or profile locally, e.g. python -m cProfile -m pyrtkai.cli rewrite 'your command string' and inspect hotspots.
Example: token economy (gain)
With an editable venv install, enable gain and point the DB to a temp file, run a proxy command that prints more characters than PYRTKAI_OUTPUT_MAX_CHARS (default 4000), then inspect estimated token savings:
python3 -m venv .venv
.venv/bin/pip install -e .
export PYRTKAI_GAIN_ENABLED=1
export PYRTKAI_GAIN_DB_PATH=/tmp/pyrtkai_gain_demo.sqlite
rm -f /tmp/pyrtkai_gain_demo.sqlite
.venv/bin/pyrtkai proxy python3 -c "print('x'*12000)" >/dev/null
.venv/bin/pyrtkai gain summary --json
On a typical run (default PYRTKAI_CHARS_PER_TOKEN=4, default max chars 4000), JSON output is similar to: tokens_before ≈ 3000, tokens_after ≈ 1000, tokens_saved_est ≈ 2000 — exact numbers depend on your environment and truncation. Use pyrtkai bench proxy --iters 5 <command...> to measure proxy overhead (latency ratio vs direct subprocess); ratios vary by machine.
Gain DB inside this repo (local only)
To keep metrics under the project tree (ignored by git via .pyrtkai/ in .gitignore):
export PYRTKAI_GAIN_ENABLED=1
export PYRTKAI_GAIN_DB_PATH="$PWD/.pyrtkai/gain.sqlite"
pyrtkai proxy <command...> # or: PYTHONPATH=src python3 -m pyrtkai.cli proxy ...
pyrtkai gain summary --json
pyrtkai gain export --limit 500
Replace pyrtkai with .venv/bin/pyrtkai if you use a venv. Only commands run through proxy while gain is enabled are recorded.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyrtkai-0.1.2.tar.gz.
File metadata
- Download URL: pyrtkai-0.1.2.tar.gz
- Upload date:
- Size: 52.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f6a9372460813233a5eaeb6fc0300dffa4f737999ce661e304bfab9d2c8c1d2c
|
|
| MD5 |
885630c71e40f426ffe1acbf03260e6d
|
|
| BLAKE2b-256 |
a11854de6575437f3cafa6e6a1adf24b1ad6bab4a8ff897570f9dd1e4acdb820
|
Provenance
The following attestation bundles were made for pyrtkai-0.1.2.tar.gz:
Publisher:
publish.yml on irmedvedeva/PyRTKAI
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyrtkai-0.1.2.tar.gz -
Subject digest:
f6a9372460813233a5eaeb6fc0300dffa4f737999ce661e304bfab9d2c8c1d2c - Sigstore transparency entry: 1297605209
- Sigstore integration time:
-
Permalink:
irmedvedeva/PyRTKAI@8d68a01fdef30f2f750d53426790b8a878ab0b54 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/irmedvedeva
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8d68a01fdef30f2f750d53426790b8a878ab0b54 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file pyrtkai-0.1.2-py3-none-any.whl.
File metadata
- Download URL: pyrtkai-0.1.2-py3-none-any.whl
- Upload date:
- Size: 33.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
986f18c99d87e3e994778077e0110a4cdb43c8233c8347d815eb12008f47ac03
|
|
| MD5 |
5d2e954b84c0c9eba44ed37b628db2a9
|
|
| BLAKE2b-256 |
175e7c4c336f4c6c9b844b3f169b0c8b75fe371eb2b20025998d26a7fc6eda44
|
Provenance
The following attestation bundles were made for pyrtkai-0.1.2-py3-none-any.whl:
Publisher:
publish.yml on irmedvedeva/PyRTKAI
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyrtkai-0.1.2-py3-none-any.whl -
Subject digest:
986f18c99d87e3e994778077e0110a4cdb43c8233c8347d815eb12008f47ac03 - Sigstore transparency entry: 1297605294
- Sigstore integration time:
-
Permalink:
irmedvedeva/PyRTKAI@8d68a01fdef30f2f750d53426790b8a878ab0b54 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/irmedvedeva
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8d68a01fdef30f2f750d53426790b8a878ab0b54 -
Trigger Event:
workflow_dispatch
-
Statement type: