tersetrim 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Terse Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,78 @@
1
+ Metadata-Version: 2.4
2
+ Name: tersetrim
3
+ Version: 0.2.0
4
+ Summary: Token-optimizing command wrapper for AI coding agents — compacts CLI output, argv-safe, no junk files.
5
+ Author: Terse Labs
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/terse-labs/tersetrim
8
+ Keywords: llm,ai-agent,cli,tokens,claude-code,developer-tools
9
+ Requires-Python: >=3.9
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Dynamic: license-file
13
+
14
+ # tersetrim
15
+
16
+ **Trim your shell output. Save your tokens.**
17
+
18
+ *The first tool from **Terse Labs** — lean developer tools for the AI-agent era.*
19
+
20
+ A token-optimizing command wrapper for AI coding agents (Claude Code, Cursor, and friends). It runs
21
+ your shell commands and compacts their verbose output so the agent reads far fewer tokens — without
22
+ ever corrupting an argument or spraying junk files into your working directory.
23
+
24
+ ## Why
25
+
26
+ LLM coding agents pay per token and blow through context on noisy tool output. A 20-line `git status`
27
+ or a screenful of `git log` is mostly ceremony the model doesn't need. `tersetrim` compacts that output
28
+ to the signal, so every command costs the agent less.
29
+
30
+ ## Quick start
31
+
32
+ ```bash
33
+ pip install -e . # from the tersetrim/ dir; PyPI publish pending
34
+ tersetrim git status # runs it, prints a compact summary + a savings line on stderr
35
+ tersetrim git log -n 20 # one line per commit
36
+ tersetrim --stats # cumulative tokens saved so far
37
+ ```
38
+
39
+ Prefix `tersetrim` to any command. Commands it doesn't have a compactor for pass through untouched —
40
+ it never changes what a command *does*, only what the agent *reads*.
41
+
42
+ ## Example (real output)
43
+
44
+ `tersetrim git log -n 2` on this repo:
45
+
46
+ ```
47
+ [tersetrim] ~110->32 tok (70% saved)
48
+ 3cbb2eed4 chore: Terse Labs launch
49
+ 8a1879e9f tersetrim v0.2.0 — argv-safe token-optimizing command wrapper for AI coding agents
50
+ ```
51
+
52
+ The full multi-line `git log` (commit / Author / Date / blank / message per commit) becomes one
53
+ `<short-hash> <subject>` line each — **70% fewer tokens** even on this tiny 2-commit log (longer logs save more), no commit dropped.
54
+
55
+ `git status` collapses to `N changed:` + one porcelain line per file; `ls -l` becomes `size name`
56
+ (perms/owner/date dropped); `docker ps` / `docker images` keep only the columns an agent reasons about
57
+ (names/status/image/ports, repo/tag/id/size) and drop the id/command/created noise.
58
+
59
+ ## Why argv-safe matters
60
+
61
+ Some command wrappers **reconstruct your command into a shell string and re-exec it**. A quoted `>`
62
+ or `/` inside an argument then becomes a shell redirect, spraying 0-byte junk files and directories
63
+ into your cwd — a real bug class we hit in production before building this.
64
+
65
+ tersetrim runs the **argv list directly** (`shell=False`), so the OS receives your arguments verbatim.
66
+ That class of bug is **structurally impossible** — on Windows, macOS, and Linux alike. Its self-check
67
+ asserts exactly this: an argument containing `>` and `/` round-trips untouched and creates no file.
68
+
69
+ ## Roadmap
70
+
71
+ - **v0.2** — `docker ps` / `docker images` ✓; next: `kubectl`, `npm ls`, `pip list`, `git diff --stat`
72
+ - **v0.3** — a hook that auto-wraps an agent's commands (no per-command prefix)
73
+ - **v0.4** — per-tool profiles + user-defined compactors
74
+ - **v1.0** — a compactor plugin ecosystem
75
+
76
+ ## License
77
+
78
+ MIT.
@@ -0,0 +1,65 @@
1
+ # tersetrim
2
+
3
+ **Trim your shell output. Save your tokens.**
4
+
5
+ *The first tool from **Terse Labs** — lean developer tools for the AI-agent era.*
6
+
7
+ A token-optimizing command wrapper for AI coding agents (Claude Code, Cursor, and friends). It runs
8
+ your shell commands and compacts their verbose output so the agent reads far fewer tokens — without
9
+ ever corrupting an argument or spraying junk files into your working directory.
10
+
11
+ ## Why
12
+
13
+ LLM coding agents pay per token and blow through context on noisy tool output. A 20-line `git status`
14
+ or a screenful of `git log` is mostly ceremony the model doesn't need. `tersetrim` compacts that output
15
+ to the signal, so every command costs the agent less.
16
+
17
+ ## Quick start
18
+
19
+ ```bash
20
+ pip install -e . # from the tersetrim/ dir; PyPI publish pending
21
+ tersetrim git status # runs it, prints a compact summary + a savings line on stderr
22
+ tersetrim git log -n 20 # one line per commit
23
+ tersetrim --stats # cumulative tokens saved so far
24
+ ```
25
+
26
+ Prefix `tersetrim` to any command. Commands it doesn't have a compactor for pass through untouched —
27
+ it never changes what a command *does*, only what the agent *reads*.
28
+
29
+ ## Example (real output)
30
+
31
+ `tersetrim git log -n 2` on this repo:
32
+
33
+ ```
34
+ [tersetrim] ~110->32 tok (70% saved)
35
+ 3cbb2eed4 chore: Terse Labs launch
36
+ 8a1879e9f tersetrim v0.2.0 — argv-safe token-optimizing command wrapper for AI coding agents
37
+ ```
38
+
39
+ The full multi-line `git log` (commit / Author / Date / blank / message per commit) becomes one
40
+ `<short-hash> <subject>` line each — **70% fewer tokens** even on this tiny 2-commit log (longer logs save more), no commit dropped.
41
+
42
+ `git status` collapses to `N changed:` + one porcelain line per file; `ls -l` becomes `size name`
43
+ (perms/owner/date dropped); `docker ps` / `docker images` keep only the columns an agent reasons about
44
+ (names/status/image/ports, repo/tag/id/size) and drop the id/command/created noise.
45
+
46
+ ## Why argv-safe matters
47
+
48
+ Some command wrappers **reconstruct your command into a shell string and re-exec it**. A quoted `>`
49
+ or `/` inside an argument then becomes a shell redirect, spraying 0-byte junk files and directories
50
+ into your cwd — a real bug class we hit in production before building this.
51
+
52
+ tersetrim runs the **argv list directly** (`shell=False`), so the OS receives your arguments verbatim.
53
+ That class of bug is **structurally impossible** — on Windows, macOS, and Linux alike. Its self-check
54
+ asserts exactly this: an argument containing `>` and `/` round-trips untouched and creates no file.
55
+
56
+ ## Roadmap
57
+
58
+ - **v0.2** — `docker ps` / `docker images` ✓; next: `kubectl`, `npm ls`, `pip list`, `git diff --stat`
59
+ - **v0.3** — a hook that auto-wraps an agent's commands (no per-command prefix)
60
+ - **v0.4** — per-tool profiles + user-defined compactors
61
+ - **v1.0** — a compactor plugin ecosystem
62
+
63
+ ## License
64
+
65
+ MIT.
@@ -0,0 +1,23 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "tersetrim"
7
+ version = "0.2.0"
8
+ description = "Token-optimizing command wrapper for AI coding agents — compacts CLI output, argv-safe, no junk files."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ keywords = ["llm", "ai-agent", "cli", "tokens", "claude-code", "developer-tools"]
13
+ authors = [{ name = "Terse Labs" }]
14
+ dependencies = [] # stdlib-only by design (ponytail: no deps for what argv+re do)
15
+
16
+ [project.scripts]
17
+ tersetrim = "tersetrim:main"
18
+
19
+ [project.urls]
20
+ Homepage = "https://github.com/terse-labs/tersetrim"
21
+
22
+ [tool.setuptools]
23
+ py-modules = ["tersetrim"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,78 @@
1
+ Metadata-Version: 2.4
2
+ Name: tersetrim
3
+ Version: 0.2.0
4
+ Summary: Token-optimizing command wrapper for AI coding agents — compacts CLI output, argv-safe, no junk files.
5
+ Author: Terse Labs
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/terse-labs/tersetrim
8
+ Keywords: llm,ai-agent,cli,tokens,claude-code,developer-tools
9
+ Requires-Python: >=3.9
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Dynamic: license-file
13
+
14
+ # tersetrim
15
+
16
+ **Trim your shell output. Save your tokens.**
17
+
18
+ *The first tool from **Terse Labs** — lean developer tools for the AI-agent era.*
19
+
20
+ A token-optimizing command wrapper for AI coding agents (Claude Code, Cursor, and friends). It runs
21
+ your shell commands and compacts their verbose output so the agent reads far fewer tokens — without
22
+ ever corrupting an argument or spraying junk files into your working directory.
23
+
24
+ ## Why
25
+
26
+ LLM coding agents pay per token and blow through context on noisy tool output. A 20-line `git status`
27
+ or a screenful of `git log` is mostly ceremony the model doesn't need. `tersetrim` compacts that output
28
+ to the signal, so every command costs the agent less.
29
+
30
+ ## Quick start
31
+
32
+ ```bash
33
+ pip install -e . # from the tersetrim/ dir; PyPI publish pending
34
+ tersetrim git status # runs it, prints a compact summary + a savings line on stderr
35
+ tersetrim git log -n 20 # one line per commit
36
+ tersetrim --stats # cumulative tokens saved so far
37
+ ```
38
+
39
+ Prefix `tersetrim` to any command. Commands it doesn't have a compactor for pass through untouched —
40
+ it never changes what a command *does*, only what the agent *reads*.
41
+
42
+ ## Example (real output)
43
+
44
+ `tersetrim git log -n 2` on this repo:
45
+
46
+ ```
47
+ [tersetrim] ~110->32 tok (70% saved)
48
+ 3cbb2eed4 chore: Terse Labs launch
49
+ 8a1879e9f tersetrim v0.2.0 — argv-safe token-optimizing command wrapper for AI coding agents
50
+ ```
51
+
52
+ The full multi-line `git log` (commit / Author / Date / blank / message per commit) becomes one
53
+ `<short-hash> <subject>` line each — **70% fewer tokens** even on this tiny 2-commit log (longer logs save more), no commit dropped.
54
+
55
+ `git status` collapses to `N changed:` + one porcelain line per file; `ls -l` becomes `size name`
56
+ (perms/owner/date dropped); `docker ps` / `docker images` keep only the columns an agent reasons about
57
+ (names/status/image/ports, repo/tag/id/size) and drop the id/command/created noise.
58
+
59
+ ## Why argv-safe matters
60
+
61
+ Some command wrappers **reconstruct your command into a shell string and re-exec it**. A quoted `>`
62
+ or `/` inside an argument then becomes a shell redirect, spraying 0-byte junk files and directories
63
+ into your cwd — a real bug class we hit in production before building this.
64
+
65
+ tersetrim runs the **argv list directly** (`shell=False`), so the OS receives your arguments verbatim.
66
+ That class of bug is **structurally impossible** — on Windows, macOS, and Linux alike. Its self-check
67
+ asserts exactly this: an argument containing `>` and `/` round-trips untouched and creates no file.
68
+
69
+ ## Roadmap
70
+
71
+ - **v0.2** — `docker ps` / `docker images` ✓; next: `kubectl`, `npm ls`, `pip list`, `git diff --stat`
72
+ - **v0.3** — a hook that auto-wraps an agent's commands (no per-command prefix)
73
+ - **v0.4** — per-tool profiles + user-defined compactors
74
+ - **v1.0** — a compactor plugin ecosystem
75
+
76
+ ## License
77
+
78
+ MIT.
@@ -0,0 +1,9 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ tersetrim.py
5
+ tersetrim.egg-info/PKG-INFO
6
+ tersetrim.egg-info/SOURCES.txt
7
+ tersetrim.egg-info/dependency_links.txt
8
+ tersetrim.egg-info/entry_points.txt
9
+ tersetrim.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ tersetrim = tersetrim:main
@@ -0,0 +1 @@
1
+ tersetrim
@@ -0,0 +1,262 @@
1
+ #!/usr/bin/env python3
2
+ """tersetrim — a token-optimizing command wrapper for AI coding agents. A Terse Labs tool.
3
+
4
+ Shrinks verbose CLI output so an LLM reads fewer tokens — without the bug class that plagues
5
+ shell-string rewrappers: a tool that rebuilds your command into a SHELL STRING and re-execs it
6
+ turns a quoted '>' or '/' inside an argument into a redirect, spraying junk files/dirs into your
7
+ working directory. tersetrim NEVER reconstructs a shell string — it runs the argv list directly,
8
+ so that class of bug is STRUCTURALLY IMPOSSIBLE, on every platform.
9
+
10
+ tersetrim git status # runs `git status`, prints a compact summary + a token-savings line
11
+ tersetrim git log -n 20 # compacts to one line per commit
12
+ tersetrim --stats # cumulative tokens saved so far
13
+ tersetrim --self-check
14
+
15
+ Design: a command has an optional COMPACTOR (compact its output); unknown commands pass through
16
+ untouched. Compaction is pure text, so it can never change what the command DID — only what the
17
+ agent READS. A tokens≈chars/4 estimate is logged to a ledger so savings are measurable.
18
+ """
19
+ from __future__ import annotations
20
+
21
+ import json
22
+ import os
23
+ import re
24
+ import subprocess
25
+ import sys
26
+ import time
27
+ from pathlib import Path
28
+
29
+ _LEDGER = Path(os.environ.get("TERSETRIM_LEDGER", Path.home() / ".tersetrim" / "savings.jsonl"))
30
+
31
+ # A git PORCELAIN status line: two status codes from the git set, a space, a path. Prose lines from
32
+ # plain `git status` ("On branch main") do NOT match, so the compactor never counts prose as a file.
33
+ _PORCELAIN = re.compile(r"^[ MADRCU?!]{2} \S")
34
+
35
+
36
+ def _compact_git_status(raw: str) -> str:
37
+ """`git status` -> one line per changed file + a count; passes prose through if no porcelain lines."""
38
+ files = [l for l in raw.splitlines() if _PORCELAIN.match(l)]
39
+ if not files:
40
+ return "clean working tree" if "nothing to commit" in raw else raw
41
+ return f"{len(files)} changed:\n" + "\n".join(f" {l.strip()}" for l in files)
42
+
43
+
44
+ def _compact_git_log(raw: str) -> str:
45
+ """Full `git log` (commit/Author/Date/blank/message blocks) -> one '<short> <subject>' per commit.
46
+ Leaves already-oneline output untouched. Never drops a commit; only trims per-commit prose."""
47
+ commits = []
48
+ cur = {}
49
+ for line in raw.splitlines():
50
+ if line.startswith("commit "):
51
+ if cur:
52
+ commits.append(cur)
53
+ cur = {"hash": line.split()[1][:9], "subject": ""}
54
+ elif line.startswith(" ") and cur and not cur["subject"]:
55
+ cur["subject"] = line.strip()
56
+ if cur:
57
+ commits.append(cur)
58
+ if not commits: # not the multi-line format (already --oneline etc.)
59
+ return raw
60
+ return "\n".join(f"{c['hash']} {c['subject']}" for c in commits)
61
+
62
+
63
+ def _compact_ls_long(raw: str) -> str:
64
+ """`ls -l`/`ls -la` -> 'size name', dropping perms/links/owner/group/date (noise for an agent).
65
+ Keeps the 'total' header out; leaves non-long output (plain `ls`) untouched."""
66
+ out = []
67
+ matched = False
68
+ for line in raw.splitlines():
69
+ if line.startswith("total "):
70
+ continue
71
+ # perms(10) links owner group size mon day time/year name...
72
+ m = re.match(r"^[-dlbcps][-rwxsStT]{9}[.+]?\s+\d+\s+\S+\s+\S+\s+(\d+)\s+\S+\s+\S+\s+\S+\s+(.+)$", line)
73
+ if m:
74
+ matched = True
75
+ out.append(f"{m.group(1):>10} {m.group(2)}")
76
+ else:
77
+ out.append(line)
78
+ return "\n".join(out) if matched else raw
79
+
80
+
81
+ def _compact_docker_ps(raw: str) -> str:
82
+ """`docker ps` is very wide (id, image, command, created, status, ports, names). Keep the SIGNAL
83
+ — NAMES, STATUS, IMAGE, PORTS — and drop id/command/created. Header-driven so column widths/order
84
+ can't break it; passes non-table output (errors, `-q`) through untouched."""
85
+ lines = raw.splitlines()
86
+ if not lines or "CONTAINER ID" not in lines[0]:
87
+ return raw
88
+ hdr = lines[0]
89
+ # Parse ALL columns by header position (must track every one, or a slice bleeds an untracked
90
+ # column into a kept one), then emit only NAMES/STATUS/IMAGE/PORTS.
91
+ heads = [h for h in ("CONTAINER ID", "IMAGE", "COMMAND", "CREATED", "STATUS", "PORTS", "NAMES")
92
+ if h in hdr]
93
+ if "NAMES" not in heads or "STATUS" not in heads:
94
+ return raw
95
+ heads.sort(key=lambda h: hdr.find(h))
96
+ starts = [hdr.find(h) for h in heads] + [len(hdr) + 9999]
97
+ keep = ("NAMES", "STATUS", "IMAGE", "PORTS")
98
+ out = [" ".join(k for k in keep if k in heads)]
99
+ for line in lines[1:]:
100
+ if not line.strip():
101
+ continue
102
+ vals = {heads[i]: line[starts[i]:starts[i + 1]].strip() for i in range(len(heads))}
103
+ out.append(" ".join(vals.get(c, "") for c in keep if c in heads).rstrip())
104
+ return "\n".join(out)
105
+
106
+
107
+ def _compact_docker_images(raw: str) -> str:
108
+ """`docker images` is wide (repo, tag, id, created, size). Keep REPOSITORY/TAG/IMAGE ID/SIZE
109
+ — the identity + footprint an agent reasons about — and drop CREATED (verbose relative time,
110
+ low signal). Header-driven like docker ps; passes non-table output (errors, `-q`) through."""
111
+ lines = raw.splitlines()
112
+ if not lines or "REPOSITORY" not in lines[0] or "SIZE" not in lines[0]:
113
+ return raw
114
+ hdr = lines[0]
115
+ heads = [h for h in ("REPOSITORY", "TAG", "IMAGE ID", "CREATED", "SIZE") if h in hdr]
116
+ heads.sort(key=lambda h: hdr.find(h))
117
+ starts = [hdr.find(h) for h in heads] + [len(hdr) + 9999]
118
+ keep = ("REPOSITORY", "TAG", "IMAGE ID", "SIZE")
119
+ out = [" ".join(k for k in keep if k in heads)]
120
+ for line in lines[1:]:
121
+ if not line.strip():
122
+ continue
123
+ vals = {heads[i]: line[starts[i]:starts[i + 1]].strip() for i in range(len(heads))}
124
+ out.append(" ".join(vals.get(c, "") for c in keep if c in heads).rstrip())
125
+ return "\n".join(out)
126
+
127
+
128
+ def _compactor_for(argv: list[str]):
129
+ joined = tuple(a for a in argv if not a.startswith("-")) # ignore flags for the key
130
+ for n in (2, 1):
131
+ key = joined[:n]
132
+ if key in _COMPACTORS:
133
+ return _COMPACTORS[key]
134
+ return None
135
+
136
+
137
+ _COMPACTORS = {
138
+ ("git", "status"): _compact_git_status,
139
+ ("git", "log"): _compact_git_log,
140
+ ("ls",): _compact_ls_long,
141
+ ("docker", "ps"): _compact_docker_ps,
142
+ ("docker", "images"): _compact_docker_images,
143
+ }
144
+
145
+
146
+ def _est_tokens(s: str) -> int:
147
+ return max(0, len(s) // 4) # ponytail: chars/4 is the standard rough token estimate
148
+
149
+
150
+ def _record_saving(cmd: str, raw: str, out: str) -> None:
151
+ try:
152
+ _LEDGER.parent.mkdir(parents=True, exist_ok=True)
153
+ saved = _est_tokens(raw) - _est_tokens(out)
154
+ with _LEDGER.open("a", encoding="utf-8") as f:
155
+ f.write(json.dumps({"ts": int(time.time()), "cmd": cmd,
156
+ "raw_tok": _est_tokens(raw), "out_tok": _est_tokens(out),
157
+ "saved_tok": saved}) + "\n")
158
+ except Exception: # noqa: BLE001 — telemetry must never break the wrapper
159
+ pass
160
+
161
+
162
+ def run(argv: list[str], _runner=None) -> tuple[int, str]:
163
+ """Run argv DIRECTLY (never via a shell string) and return (returncode, output_for_agent).
164
+ _runner is a test seam returning (rc, stdout)."""
165
+ if not argv:
166
+ return 2, "tersetrim: no command given"
167
+ if _runner is not None:
168
+ rc, raw = _runner(argv)
169
+ else:
170
+ # shell=False + a list => the OS gets argv verbatim; no quoting/redirect reinterpretation.
171
+ p = subprocess.run(argv, capture_output=True, text=True)
172
+ rc, raw = p.returncode, (p.stdout or "") + (p.stderr or "")
173
+ comp = _compactor_for(argv)
174
+ out = comp(raw) if comp else raw
175
+ if comp and raw:
176
+ _record_saving(" ".join(argv), raw, out)
177
+ saved = _est_tokens(raw) - _est_tokens(out)
178
+ pct = (100 * saved // _est_tokens(raw)) if _est_tokens(raw) else 0
179
+ sys.stderr.write(f"[tersetrim] ~{_est_tokens(raw)}->{_est_tokens(out)} tok ({pct}% saved)\n")
180
+ return rc, out
181
+
182
+
183
+ def _stats() -> int:
184
+ if not _LEDGER.exists():
185
+ print("tersetrim: no savings recorded yet (run some commands through it first)")
186
+ return 0
187
+ n = saved = 0
188
+ for line in _LEDGER.read_text(encoding="utf-8").splitlines():
189
+ try:
190
+ saved += int(json.loads(line).get("saved_tok", 0))
191
+ n += 1
192
+ except (json.JSONDecodeError, ValueError):
193
+ continue
194
+ print(f"tersetrim: {n} commands compacted, ~{saved:,} tokens saved cumulatively")
195
+ return 0
196
+
197
+
198
+ def _self_check() -> int:
199
+ import tempfile
200
+ # 1. THE rtk BUG CANNOT HAPPEN: an argument with '>' and '/' passes through as one argv element.
201
+ marker = os.path.join(tempfile.gettempdir(), "tersetrim_selfcheck_marker")
202
+ if os.path.exists(marker):
203
+ os.unlink(marker)
204
+ hostile = f"a > {marker} and x/inf/) fragment"
205
+ rc, out = run(["python", "-c", "import sys; sys.stdout.write(sys.argv[1])", hostile])
206
+ assert rc == 0 and out == hostile, f"argv passthrough corrupted the argument: {out!r}"
207
+ assert not os.path.exists(marker), "tersetrim created a redirect file — the shell-rewrap redirect bug is present!"
208
+ # 2. git status: counts porcelain lines, never prose, honest on clean
209
+ raw = "On branch main\nChanges not staged for commit:\n M a.py\n M b.py\n\nno changes added\n"
210
+ got = run(["git", "status"], _runner=lambda a: (0, raw))[1]
211
+ assert got.startswith("2 changed") and "a.py" in got and "On branch" not in got, got
212
+ assert run(["git", "status"], _runner=lambda a: (0, "nothing to commit, working tree clean\n"))[1] == "clean working tree"
213
+ # 3. git log: multi-line -> one line per commit, never drops a commit
214
+ glog = ("commit abc1234567\nAuthor: X\nDate: today\n\n first subject\n\n"
215
+ "commit def8901234\nAuthor: Y\nDate: today\n\n second subject\n")
216
+ gl = run(["git", "log"], _runner=lambda a: (0, glog))[1]
217
+ assert gl == "abc123456 first subject\ndef890123 second subject", gl
218
+ assert run(["git", "log", "--oneline"], _runner=lambda a: (0, "abc123 x\n"))[1] == "abc123 x\n" # already-oneline untouched
219
+ # 4. ls -l: 'size name', drops perms/owner/date; plain ls untouched
220
+ lsl = "total 8\n-rw-r--r-- 1 me grp 1234 Jul 15 10:00 file.py\ndrwxr-xr-x 2 me grp 4096 Jul 15 10:00 dir\n"
221
+ lo = run(["ls", "-la"], _runner=lambda a: (0, lsl))[1]
222
+ assert "1234 file.py" in lo and "rw-r" not in lo and "total" not in lo, lo
223
+ assert run(["ls"], _runner=lambda a: (0, "a.py\nb.py\n"))[1] == "a.py\nb.py\n" # plain ls untouched
224
+ # 5. docker ps: keep NAMES/STATUS/IMAGE/PORTS, drop id/command/created; non-table untouched
225
+ dps = ("CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES\n"
226
+ "abc123def456 nginx:latest \"/docker-e\" 2 hours ago Up 2 hours 0.0.0.0:80->80/tcp web\n")
227
+ dc = run(["docker", "ps"], _runner=lambda a: (0, dps))[1]
228
+ assert "web" in dc and "Up 2 hours" in dc and "nginx:latest" in dc, dc
229
+ assert "abc123def456" not in dc and "docker-e" not in dc, "compactor must drop id/command"
230
+ assert run(["docker", "ps", "-q"], _runner=lambda a: (0, "abc123\n"))[1] == "abc123\n" # non-table untouched
231
+ # 5b. docker images: keep REPOSITORY/TAG/IMAGE ID/SIZE, drop CREATED; non-table untouched
232
+ dim = ("REPOSITORY TAG IMAGE ID CREATED SIZE\n"
233
+ "nginx latest abc123def456 2 weeks ago 187MB\n")
234
+ di = run(["docker", "images"], _runner=lambda a: (0, dim))[1]
235
+ assert "nginx" in di and "187MB" in di and "abc123def456" in di, di
236
+ assert "2 weeks ago" not in di and "CREATED" not in di, "docker images must drop CREATED"
237
+ assert run(["docker", "images", "-q"], _runner=lambda a: (0, "abc123\n"))[1] == "abc123\n" # non-table untouched
238
+ # 6. unknown command passes through untouched
239
+ assert run(["echo", "hi"], _runner=lambda a: (0, "hi\n"))[1] == "hi\n"
240
+ # 6. flags don't break compactor lookup (git -C . status still compacts)
241
+ assert _compactor_for(["git", "status"]) is _compact_git_status
242
+ print("tersetrim self-check: PASS (argv passthrough quote/redirect-safe — shell-rewrap junk-file bug impossible; "
243
+ "git-status/git-log/ls-l compactors shrink without losing signal or fabricating state)")
244
+ return 0
245
+
246
+
247
+ def main() -> int:
248
+ args = sys.argv[1:]
249
+ if args == ["--self-check"]:
250
+ return _self_check()
251
+ if args == ["--stats"]:
252
+ return _stats()
253
+ if not args or args == ["--help"]:
254
+ print(__doc__.strip())
255
+ return 0
256
+ rc, out = run(args)
257
+ sys.stdout.write(out if (not out or out.endswith("\n")) else out + "\n")
258
+ return rc
259
+
260
+
261
+ if __name__ == "__main__":
262
+ sys.exit(main())