tidyout-cli 0.1.0__py3-none-any.whl

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.
tidyout/__init__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
tidyout/cli.py ADDED
@@ -0,0 +1,31 @@
1
+ import sys
2
+
3
+ from tidyout.runner import run_command
4
+ from tidyout.stripper import strip_output
5
+ from tidyout.init_agent import run_init
6
+
7
+
8
+ def main():
9
+ args = sys.argv[1:]
10
+
11
+ if not args:
12
+ print("Usage: tidy <command> [args...]")
13
+ print(" tidy init")
14
+ sys.exit(1)
15
+
16
+ if args[0] == "init":
17
+ run_init()
18
+ return
19
+
20
+ raw_output, return_code = run_command(args)
21
+ compressed, original_lines, compressed_lines, saved_tokens, percent = strip_output(args, raw_output)
22
+
23
+ if compressed:
24
+ print(compressed, end="" if compressed.endswith("\n") else "\n")
25
+
26
+ print(
27
+ f"\u2702\ufe0f tidyout: {original_lines} lines \u2192 {compressed_lines} lines"
28
+ f" | saved ~{saved_tokens} tokens ({percent}% reduction)"
29
+ )
30
+
31
+ sys.exit(return_code)
tidyout/init_agent.py ADDED
@@ -0,0 +1,28 @@
1
+ from pathlib import Path
2
+
3
+ TIDYOUT_BLOCK = """
4
+ ## Token Optimization with tidyout
5
+ Always prefix the following commands with `tidy` when running in terminal:
6
+ pytest, npm test, jest, rspec, git status, git log, ls, find, tree,
7
+ pip install, npm install, docker logs, docker ps, make, cargo build
8
+ This reduces token usage by 60-97% on command outputs.
9
+ """
10
+
11
+
12
+ def run_init():
13
+ cwd = Path.cwd()
14
+ claude_md = cwd / "CLAUDE.md"
15
+ cursorrules = cwd / ".cursorrules"
16
+
17
+ if claude_md.exists():
18
+ with open(claude_md, "a") as f:
19
+ f.write(TIDYOUT_BLOCK)
20
+ print(f"Appended tidyout rules to {claude_md}")
21
+ elif cursorrules.exists():
22
+ with open(cursorrules, "a") as f:
23
+ f.write(TIDYOUT_BLOCK)
24
+ print(f"Appended tidyout rules to {cursorrules}")
25
+ else:
26
+ with open(claude_md, "w") as f:
27
+ f.write(TIDYOUT_BLOCK.lstrip())
28
+ print(f"Created {claude_md} with tidyout rules")
@@ -0,0 +1,19 @@
1
+ strip:
2
+ - pattern: " Downloading "
3
+ type: prefix
4
+ - pattern: " Downloaded "
5
+ type: prefix
6
+ - pattern: " Updating "
7
+ type: prefix
8
+ - pattern: " Locking "
9
+ type: prefix
10
+
11
+ keep:
12
+ - pattern: "^warning"
13
+ type: regex
14
+ - pattern: "^error"
15
+ type: regex
16
+ - pattern: " Finished"
17
+ type: prefix
18
+ - pattern: " Compiling "
19
+ type: prefix
@@ -0,0 +1,19 @@
1
+ strip:
2
+ - pattern: "health_check\\|"
3
+ type: regex
4
+ - pattern: "healthcheck"
5
+ type: contains
6
+
7
+ keep:
8
+ - pattern: "ERROR"
9
+ type: contains
10
+ - pattern: "WARN"
11
+ type: contains
12
+ - pattern: "FATAL"
13
+ type: contains
14
+ - pattern: "Exception"
15
+ type: contains
16
+ - pattern: "Traceback"
17
+ type: contains
18
+ - pattern: "panic:"
19
+ type: contains
@@ -0,0 +1,5 @@
1
+ strip:
2
+ - pattern: "^$"
3
+ type: regex
4
+
5
+ keep: []
@@ -0,0 +1,23 @@
1
+ strip:
2
+ - pattern: "/.git/"
3
+ type: contains
4
+ - pattern: "^\\.git"
5
+ type: regex
6
+ - pattern: "/node_modules/"
7
+ type: contains
8
+ - pattern: "node_modules/"
9
+ type: prefix
10
+ - pattern: "/__pycache__/"
11
+ type: contains
12
+ - pattern: "__pycache__/"
13
+ type: prefix
14
+ - pattern: "/\\.git$"
15
+ type: regex
16
+ - pattern: "node_modules$"
17
+ type: regex
18
+ - pattern: "__pycache__$"
19
+ type: regex
20
+ - pattern: "\\.pyc$"
21
+ type: regex
22
+
23
+ keep: []
@@ -0,0 +1,15 @@
1
+ strip:
2
+ - pattern: "Author: "
3
+ type: prefix
4
+ - pattern: "Merge: "
5
+ type: prefix
6
+ - pattern: "^$"
7
+ type: regex
8
+
9
+ keep:
10
+ - pattern: "commit "
11
+ type: prefix
12
+ - pattern: "Date:"
13
+ type: prefix
14
+ - pattern: " "
15
+ type: prefix
@@ -0,0 +1,31 @@
1
+ strip:
2
+ - pattern: "Your branch is"
3
+ type: prefix
4
+ - pattern: "^ \\(use \""
5
+ type: regex
6
+ - pattern: "nothing to commit"
7
+ type: prefix
8
+ - pattern: "no changes added"
9
+ type: prefix
10
+ - pattern: "Changes not staged"
11
+ type: prefix
12
+ - pattern: "Changes to be committed"
13
+ type: prefix
14
+ - pattern: "Untracked files:"
15
+ type: prefix
16
+ - pattern: "^$"
17
+ type: regex
18
+
19
+ keep:
20
+ - pattern: "On branch "
21
+ type: prefix
22
+ - pattern: "modified:"
23
+ type: contains
24
+ - pattern: "deleted:"
25
+ type: contains
26
+ - pattern: "new file:"
27
+ type: contains
28
+ - pattern: "renamed:"
29
+ type: contains
30
+ - pattern: "both modified:"
31
+ type: contains
@@ -0,0 +1,33 @@
1
+ strip:
2
+ - pattern: " ✓ "
3
+ type: prefix
4
+ - pattern: " ✔ "
5
+ type: prefix
6
+ - pattern: " ✅ "
7
+ type: prefix
8
+ - pattern: " PASS "
9
+ type: contains
10
+ - pattern: "^\\s*$"
11
+ type: regex
12
+
13
+ keep:
14
+ - pattern: " FAIL "
15
+ type: contains
16
+ - pattern: " ✕ "
17
+ type: prefix
18
+ - pattern: " ✗ "
19
+ type: prefix
20
+ - pattern: " ● "
21
+ type: prefix
22
+ - pattern: "Tests:"
23
+ type: contains
24
+ - pattern: "Test Suites:"
25
+ type: contains
26
+ - pattern: "Snapshots:"
27
+ type: contains
28
+ - pattern: "Time:"
29
+ type: contains
30
+ - pattern: "FAIL "
31
+ type: contains
32
+ - pattern: "expect("
33
+ type: contains
tidyout/rules/ls.yaml ADDED
@@ -0,0 +1,6 @@
1
+ strip:
2
+ - pattern: "total "
3
+ type: prefix
4
+ - pattern: "^$"
5
+ type: regex
6
+ keep: []
@@ -0,0 +1,9 @@
1
+ strip:
2
+ - pattern: "Entering directory"
3
+ type: contains
4
+ - pattern: "Leaving directory"
5
+ type: contains
6
+ - pattern: "^make\\[\\d+\\]"
7
+ type: regex
8
+
9
+ keep: []
@@ -0,0 +1,23 @@
1
+ strip:
2
+ - pattern: "npm warn"
3
+ type: prefix
4
+ - pattern: "npm notice"
5
+ type: prefix
6
+ - pattern: "^\\s*$"
7
+ type: regex
8
+
9
+ keep:
10
+ - pattern: "added "
11
+ type: contains
12
+ - pattern: "removed "
13
+ type: contains
14
+ - pattern: "changed "
15
+ type: contains
16
+ - pattern: "audited "
17
+ type: contains
18
+ - pattern: "vulnerabilit"
19
+ type: contains
20
+ - pattern: "found 0 vulnerabilities"
21
+ type: contains
22
+ - pattern: "npm error"
23
+ type: prefix
@@ -0,0 +1,35 @@
1
+ strip:
2
+ - pattern: "Collecting "
3
+ type: prefix
4
+ - pattern: " Downloading "
5
+ type: prefix
6
+ - pattern: "Requirement already satisfied:"
7
+ type: prefix
8
+ - pattern: " Already satisfied:"
9
+ type: prefix
10
+ - pattern: "Building wheels"
11
+ type: prefix
12
+ - pattern: " Building wheel"
13
+ type: prefix
14
+ - pattern: " Created wheel"
15
+ type: prefix
16
+ - pattern: "Stored in directory"
17
+ type: prefix
18
+ - pattern: "Installing collected packages:"
19
+ type: prefix
20
+ - pattern: "WARNING: You are using pip version"
21
+ type: contains
22
+ - pattern: "You should consider upgrading"
23
+ type: contains
24
+ - pattern: "━"
25
+ type: contains
26
+ - pattern: "^\\s*$"
27
+ type: regex
28
+
29
+ keep:
30
+ - pattern: "Successfully installed"
31
+ type: prefix
32
+ - pattern: "ERROR"
33
+ type: prefix
34
+ - pattern: "error:"
35
+ type: prefix
@@ -0,0 +1,41 @@
1
+ strip:
2
+ - pattern: "collecting "
3
+ type: prefix
4
+ - pattern: "platform "
5
+ type: prefix
6
+ - pattern: "rootdir:"
7
+ type: prefix
8
+ - pattern: "plugins:"
9
+ type: prefix
10
+ - pattern: "cacheprovider"
11
+ type: prefix
12
+ - pattern: " PASSED$"
13
+ type: regex
14
+ - pattern: " PASSED "
15
+ type: contains
16
+ - pattern: "^={10,}$"
17
+ type: regex
18
+ - pattern: "^-{10,}$"
19
+ type: regex
20
+
21
+ keep:
22
+ - pattern: "FAILED"
23
+ type: contains
24
+ - pattern: "ERROR"
25
+ type: contains
26
+ - pattern: " passed"
27
+ type: contains
28
+ - pattern: " failed"
29
+ type: contains
30
+ - pattern: " error"
31
+ type: contains
32
+ - pattern: "warning"
33
+ type: contains
34
+ - pattern: "short test summary"
35
+ type: contains
36
+ - pattern: "^FAILED "
37
+ type: regex
38
+ - pattern: "^E "
39
+ type: regex
40
+ - pattern: "^>"
41
+ type: regex
@@ -0,0 +1,11 @@
1
+ strip:
2
+ - pattern: "node_modules"
3
+ type: contains
4
+ - pattern: "\\.git"
5
+ type: contains
6
+ - pattern: "__pycache__"
7
+ type: contains
8
+ - pattern: "\\.pyc"
9
+ type: contains
10
+
11
+ keep: []
tidyout/runner.py ADDED
@@ -0,0 +1,15 @@
1
+ import subprocess
2
+
3
+
4
+ def run_command(args):
5
+ """Execute a command, merging stdout and stderr. Returns (output, return_code)."""
6
+ try:
7
+ result = subprocess.run(
8
+ args,
9
+ stdout=subprocess.PIPE,
10
+ stderr=subprocess.STDOUT,
11
+ text=True,
12
+ )
13
+ return result.stdout, result.returncode
14
+ except FileNotFoundError:
15
+ return f"tidy: command not found: {args[0]}\n", 127
tidyout/stripper.py ADDED
@@ -0,0 +1,297 @@
1
+ import re
2
+ from pathlib import Path
3
+
4
+ import yaml
5
+
6
+ RULES_DIR = Path(__file__).parent / "rules"
7
+
8
+
9
+ # ---------------------------------------------------------------------------
10
+ # Rule loading & matching
11
+ # ---------------------------------------------------------------------------
12
+
13
+ def get_rule_key(args):
14
+ """Return the rule key for a command. Tries two-word key first."""
15
+ if len(args) >= 2:
16
+ two_word = f"{args[0]}_{args[1]}"
17
+ if (RULES_DIR / f"{two_word}.yaml").exists():
18
+ return two_word
19
+ return args[0]
20
+
21
+
22
+ def load_rule(rule_key):
23
+ """Load a YAML rule file. Returns None if no rule exists for this key."""
24
+ rule_path = RULES_DIR / f"{rule_key}.yaml"
25
+ if not rule_path.exists():
26
+ return None
27
+ with open(rule_path) as f:
28
+ return yaml.safe_load(f)
29
+
30
+
31
+ def _matches(line, entry):
32
+ pattern = entry["pattern"]
33
+ ptype = entry.get("type", "contains")
34
+ if ptype == "regex":
35
+ return bool(re.search(pattern, line))
36
+ if ptype == "prefix":
37
+ return line.startswith(pattern)
38
+ if ptype == "exact":
39
+ return line == pattern
40
+ # default: contains
41
+ return pattern in line
42
+
43
+
44
+ def apply_rule(lines, rule):
45
+ """Filter lines using strip/keep rules. keep always wins over strip."""
46
+ strip_patterns = rule.get("strip") or []
47
+ keep_patterns = rule.get("keep") or []
48
+
49
+ result = []
50
+ for line in lines:
51
+ if any(_matches(line, p) for p in keep_patterns):
52
+ result.append(line)
53
+ continue
54
+ if any(_matches(line, p) for p in strip_patterns):
55
+ continue
56
+ result.append(line)
57
+ return result
58
+
59
+
60
+ # ---------------------------------------------------------------------------
61
+ # Transform hooks (reformatting beyond simple line filtering)
62
+ # ---------------------------------------------------------------------------
63
+
64
+ def _month_num(month):
65
+ return {
66
+ "Jan": "01", "Feb": "02", "Mar": "03", "Apr": "04",
67
+ "May": "05", "Jun": "06", "Jul": "07", "Aug": "08",
68
+ "Sep": "09", "Oct": "10", "Nov": "11", "Dec": "12",
69
+ }.get(month, "??")
70
+
71
+
72
+ def transform_ls(lines):
73
+ dirs, files = [], []
74
+ for line in lines:
75
+ if not line.strip():
76
+ continue
77
+ parts = line.split()
78
+ # Long format: permissions links owner group size ... name
79
+ if len(parts) >= 9 and line[0] in ("-", "d", "l"):
80
+ permissions = parts[0]
81
+ size = parts[4]
82
+ name = " ".join(parts[8:])
83
+ if name in (".", ".."):
84
+ continue
85
+ if permissions.startswith("d"):
86
+ dirs.append(f"DIR:{name}")
87
+ elif permissions.startswith("l"):
88
+ link_name = name.split(" -> ")[0]
89
+ files.append(f"L:{link_name}")
90
+ else:
91
+ files.append(f"F:{name}:{size}")
92
+ else:
93
+ files.append(line)
94
+ return dirs + files
95
+
96
+
97
+ def transform_git_status(lines):
98
+ result = []
99
+ for line in lines:
100
+ stripped = line.strip()
101
+ if line.startswith("On branch "):
102
+ result.append(f"branch: {line[len('On branch '):]}")
103
+ continue
104
+ for prefix in ("modified:", "deleted:", "new file:", "renamed:", "both modified:"):
105
+ if stripped.startswith(prefix):
106
+ result.append(f"{prefix} {stripped[len(prefix):].strip()}")
107
+ break
108
+ else:
109
+ result.append(line)
110
+ return result
111
+
112
+
113
+ def transform_git_log(lines):
114
+ result = []
115
+ cur_hash = cur_date = cur_msg = None
116
+
117
+ for line in lines:
118
+ if line.startswith("commit "):
119
+ if cur_hash and cur_msg:
120
+ result.append(f"{cur_hash} {cur_date} {cur_msg}")
121
+ cur_hash = line.split()[1][:7]
122
+ cur_date = cur_msg = None
123
+ elif line.startswith("Date:"):
124
+ date_str = line[5:].strip()
125
+ parts = date_str.split()
126
+ if len(parts) >= 5:
127
+ cur_date = f"{parts[4]}-{_month_num(parts[1])}-{parts[2].zfill(2)}"
128
+ else:
129
+ cur_date = date_str
130
+ elif line.startswith(" ") and cur_hash and cur_msg is None:
131
+ cur_msg = line.strip()
132
+
133
+ if cur_hash and cur_msg:
134
+ result.append(f"{cur_hash} {cur_date} {cur_msg}")
135
+ return result
136
+
137
+
138
+ def transform_docker_ps(lines):
139
+ result = []
140
+ col_starts = {}
141
+ col_order = ["CONTAINER ID", "IMAGE", "COMMAND", "CREATED", "STATUS", "PORTS", "NAMES"]
142
+
143
+ for line in lines:
144
+ if not line.strip():
145
+ continue
146
+ if "CONTAINER ID" in line:
147
+ for col in col_order:
148
+ idx = line.find(col)
149
+ if idx >= 0:
150
+ col_starts[col] = idx
151
+ continue
152
+
153
+ if not col_starts:
154
+ result.append(line)
155
+ continue
156
+
157
+ def get_field(col):
158
+ start = col_starts.get(col)
159
+ if start is None:
160
+ return ""
161
+ col_idx = col_order.index(col)
162
+ for next_col in col_order[col_idx + 1:]:
163
+ if next_col in col_starts:
164
+ return line[start:col_starts[next_col]].strip()
165
+ return line[start:].strip()
166
+
167
+ name = get_field("NAMES")
168
+ status = get_field("STATUS")
169
+ ports = get_field("PORTS")
170
+ if not name:
171
+ continue
172
+ entry = f"{name}:{status}:{ports}" if ports else f"{name}:{status}"
173
+ result.append(entry)
174
+
175
+ return result
176
+
177
+
178
+ def transform_find(lines):
179
+ result = []
180
+ for line in lines:
181
+ line = line.strip()
182
+ if line.startswith("./"):
183
+ line = line[2:]
184
+ if line and line != ".":
185
+ result.append(line)
186
+ return result
187
+
188
+
189
+ def _tree_depth(line):
190
+ i = depth = 0
191
+ while i < len(line):
192
+ chunk = line[i:i+4]
193
+ if chunk in ("│ ", " "):
194
+ depth += 1
195
+ i += 4
196
+ elif chunk in ("├── ", "└── "):
197
+ depth += 1
198
+ break
199
+ else:
200
+ break
201
+ return depth
202
+
203
+
204
+ def transform_tree(lines):
205
+ result = []
206
+ for line in lines:
207
+ if not line.rstrip():
208
+ result.append(line)
209
+ continue
210
+ if _tree_depth(line) <= 3:
211
+ result.append(line)
212
+ return result
213
+
214
+
215
+ def transform_docker_logs(lines):
216
+ # Keep last 50 lines after filtering
217
+ if len(lines) > 50:
218
+ return lines[-50:]
219
+ return lines
220
+
221
+
222
+ def transform_cargo_build(lines):
223
+ # Keep only the last "Compiling" line (the project crate itself)
224
+ compiling = [l for l in lines if l.startswith(" Compiling ")]
225
+ others = [l for l in lines if not l.startswith(" Compiling ")]
226
+ if compiling:
227
+ # Insert the last compiling line back in its original position
228
+ last = compiling[-1]
229
+ original_idx = lines.index(last)
230
+ result = []
231
+ inserted = False
232
+ pos = 0
233
+ for line in others:
234
+ orig_pos = lines.index(line) if line in lines else 9999
235
+ if not inserted and original_idx < orig_pos:
236
+ result.append(last)
237
+ inserted = True
238
+ result.append(line)
239
+ if not inserted:
240
+ result.append(last)
241
+ return result
242
+ return others
243
+
244
+
245
+ TRANSFORMS = {
246
+ "ls": transform_ls,
247
+ "git_status": transform_git_status,
248
+ "git_log": transform_git_log,
249
+ "docker_ps": transform_docker_ps,
250
+ "find": transform_find,
251
+ "tree": transform_tree,
252
+ "docker_logs": transform_docker_logs,
253
+ "cargo_build": transform_cargo_build,
254
+ }
255
+
256
+
257
+ # ---------------------------------------------------------------------------
258
+ # Savings calculation
259
+ # ---------------------------------------------------------------------------
260
+
261
+ def calculate_savings(original, compressed):
262
+ orig_chars = len(original)
263
+ comp_chars = len(compressed)
264
+ saved_tokens = max(0, (orig_chars - comp_chars) // 4)
265
+ percent = max(0, int((1 - comp_chars / orig_chars) * 100)) if orig_chars else 0
266
+ return saved_tokens, percent
267
+
268
+
269
+ # ---------------------------------------------------------------------------
270
+ # Public entry point
271
+ # ---------------------------------------------------------------------------
272
+
273
+ def strip_output(args, raw_output):
274
+ """
275
+ Apply rules and transforms to raw command output.
276
+
277
+ Returns:
278
+ (compressed_text, original_line_count, compressed_line_count, saved_tokens, percent)
279
+ """
280
+ rule_key = get_rule_key(args)
281
+ rule = load_rule(rule_key)
282
+
283
+ lines = raw_output.splitlines()
284
+ original_line_count = len(lines)
285
+
286
+ filtered = apply_rule(lines, rule) if rule is not None else list(lines)
287
+
288
+ if rule_key in TRANSFORMS:
289
+ filtered = TRANSFORMS[rule_key](filtered)
290
+
291
+ compressed = "\n".join(filtered)
292
+ if raw_output.endswith("\n"):
293
+ compressed += "\n"
294
+
295
+ saved_tokens, percent = calculate_savings(raw_output, compressed)
296
+
297
+ return compressed, original_line_count, len(filtered), saved_tokens, percent
@@ -0,0 +1,105 @@
1
+ Metadata-Version: 2.4
2
+ Name: tidyout-cli
3
+ Version: 0.1.0
4
+ Summary: CLI wrapper that compresses command output for AI agents, reducing token usage by 60-97%
5
+ License: MIT
6
+ Keywords: ai,cli,compression,developer-tools,tokens
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Environment :: Console
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Classifier: Topic :: Utilities
18
+ Requires-Python: >=3.9
19
+ Requires-Dist: pyyaml>=6.0
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest>=7.0; extra == 'dev'
22
+ Description-Content-Type: text/markdown
23
+
24
+ # tidyout
25
+
26
+ A CLI wrapper that sits between you (or your AI agent) and the terminal. Prefix any command with `tidy` and tidyout executes it, strips the noise, and returns a compressed version with a token savings summary.
27
+
28
+ ```
29
+ ✂️ tidyout: 312 lines → 18 lines | saved ~2376 tokens (94% reduction)
30
+ ```
31
+
32
+ Built for AI coding sessions (Claude Code, Cursor, Copilot) where terminal output burns tokens fast.
33
+
34
+ ## Install
35
+
36
+ ```bash
37
+ pip install tidyout-cli
38
+ ```
39
+
40
+ ## Usage
41
+
42
+ ```bash
43
+ tidy pytest tests/
44
+ tidy git status
45
+ tidy git log
46
+ tidy ls -la
47
+ tidy find . -name "*.py"
48
+ tidy docker logs mycontainer
49
+ tidy npm install
50
+ tidy cargo build
51
+ ```
52
+
53
+ Every run ends with a summary line:
54
+
55
+ ```
56
+ ✂️ tidyout: {original} lines → {compressed} lines | saved ~{tokens} tokens ({percent}% reduction)
57
+ ```
58
+
59
+ Commands without a matching rule pass through unchanged and still show the summary (0% reduction).
60
+
61
+ ## Supported commands
62
+
63
+ | Command | What gets stripped | Typical savings |
64
+ |---|---|---|
65
+ | `pytest` | PASSED lines, platform info, separators | ~80% |
66
+ | `git status` | hints, up-to-date messages, blank lines | ~60% |
67
+ | `git log` | Author email, full hashes, blank lines | ~50% |
68
+ | `ls` | permissions, owner, group, timestamp — reformatted to `DIR:name` / `F:name:size` | ~70% |
69
+ | `find` | `.git/`, `node_modules/`, `__pycache__/`, `./` prefix | ~40% |
70
+ | `tree` | entries beyond depth 3, `node_modules`, `.git`, `__pycache__` | ~50% |
71
+ | `pip install` | progress bars, Collecting/Downloading, already-satisfied | ~90% |
72
+ | `npm install` | warn/notice lines, blank lines | ~85% |
73
+ | `docker logs` | health check noise, keeps last 50 lines | ~70% |
74
+ | `docker ps` | header, full IDs, padding — reformatted to `name:status:ports` | ~60% |
75
+ | `jest` | passing test names, keeps failures and summary | ~80% |
76
+ | `make` | Entering/Leaving directory noise | ~40% |
77
+ | `cargo build` | dependency Compiling lines, Downloading | ~75% |
78
+
79
+ ## `tidy init` — inject rules into your AI agent config
80
+
81
+ Running `tidy init` in your project directory automatically tells your AI agent to always use `tidy`:
82
+
83
+ ```bash
84
+ tidy init
85
+ ```
86
+
87
+ It detects which config file to update:
88
+
89
+ - `CLAUDE.md` exists → appends tidyout usage rules
90
+ - `.cursorrules` exists → appends tidyout usage rules
91
+ - Neither exists → creates `CLAUDE.md` with the rules
92
+
93
+ Injected block:
94
+
95
+ ```markdown
96
+ ## Token Optimization with tidyout
97
+ Always prefix the following commands with `tidy` when running in terminal:
98
+ pytest, npm test, jest, rspec, git status, git log, ls, find, tree,
99
+ pip install, npm install, docker logs, docker ps, make, cargo build
100
+ This reduces token usage by 60-97% on command outputs.
101
+ ```
102
+
103
+ ## Contributing a new rule
104
+
105
+ See [CONTRIBUTING.md](CONTRIBUTING.md) — it's just one YAML file.
@@ -0,0 +1,22 @@
1
+ tidyout/__init__.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
2
+ tidyout/cli.py,sha256=k8v8PK4N5INOu1pdpCbAdSQnoyzMxqxf9Tc0weVNpzk,800
3
+ tidyout/init_agent.py,sha256=vE2nASWFFVcA0gFhVaW7NYkngn_hw5annsDX2aFdN5U,939
4
+ tidyout/runner.py,sha256=ttVTFFs-xb9o_yh-hBtgnTCRJO0osEXqkdmytlIg6c8,435
5
+ tidyout/stripper.py,sha256=mIhKLHCEV5kUVCqSrorb8ObgV8nN8F0JH7mIdWSANDE,8876
6
+ tidyout/rules/cargo_build.yaml,sha256=HeNIVxVDE-InR5B9lDwC6s8gac8WA7k581h638maohE,370
7
+ tidyout/rules/docker_logs.yaml,sha256=ZIO-RBNKO-NcBNsu1eng1UZk5rkexjCTKac1c5gASk0,355
8
+ tidyout/rules/docker_ps.yaml,sha256=LwR5gG_-MQhkQ5I7HrVJKdOAfpJesFra1rndeFwZnqM,51
9
+ tidyout/rules/find.yaml,sha256=szijnx8TeuZBAAS5WvRjahmfnOueRlmOtbm4XpQChrI,453
10
+ tidyout/rules/git_log.yaml,sha256=ZYkZgFqNOxXZxmhc53t1xioFykEtoxlkc16Ds5-Pb7g,244
11
+ tidyout/rules/git_status.yaml,sha256=KSdC1oL-JCpQ0vyt9VV2z0adW5R7QwWW7Up2IghV59w,660
12
+ tidyout/rules/jest.yaml,sha256=37gKOVJcA3-LYZtwQqCPcyVCIltT_-9GATbxnjdroMw,623
13
+ tidyout/rules/ls.yaml,sha256=gT6jJp5M-yEG_2sKeMqZyWCfdXXtmemsef83r2Ti9Po,89
14
+ tidyout/rules/make.yaml,sha256=08KUROTBUFf1Qrd_1eW-V5rsM4XAy-FlbaBIsSgDlbQ,169
15
+ tidyout/rules/npm_install.yaml,sha256=3WDDo23r3cMKTZpvv6SKy6NVG4pYHKDczxhfbGz7kyM,453
16
+ tidyout/rules/pip_install.yaml,sha256=SejE1eTCKUNllzKGdwwQk6uAdMsz-2zi3bon8fjuBNg,822
17
+ tidyout/rules/pytest.yaml,sha256=5tRwxJyKSYLuCrCtpqkisAq5ER-g1k8KPTDOfvRoWl0,801
18
+ tidyout/rules/tree.yaml,sha256=b-v6fD0k487Bx2aIWcDoSNYn7VzX4wXD57M_LOynA6Y,192
19
+ tidyout_cli-0.1.0.dist-info/METADATA,sha256=tI5DiMNwspq_67u5K3sbUTJIRK480Fnq2vnl6pcm_3w,3627
20
+ tidyout_cli-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
21
+ tidyout_cli-0.1.0.dist-info/entry_points.txt,sha256=7xhGowKdbIkQepakCivMjKIh_Tv1kqm_HRM81Hk-5Wo,42
22
+ tidyout_cli-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ tidy = tidyout.cli:main