tidyout-cli 0.1.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.
- tidyout_cli-0.1.0/.github/workflows/publish.yml +42 -0
- tidyout_cli-0.1.0/.gitignore +17 -0
- tidyout_cli-0.1.0/CONTRIBUTING.md +78 -0
- tidyout_cli-0.1.0/PKG-INFO +105 -0
- tidyout_cli-0.1.0/README.md +82 -0
- tidyout_cli-0.1.0/docs/superpowers/specs/2026-05-12-tidyout-design.md +169 -0
- tidyout_cli-0.1.0/pyproject.toml +42 -0
- tidyout_cli-0.1.0/tests/test_cli.py +98 -0
- tidyout_cli-0.1.0/tests/test_stripper.py +375 -0
- tidyout_cli-0.1.0/tidyout/__init__.py +1 -0
- tidyout_cli-0.1.0/tidyout/cli.py +31 -0
- tidyout_cli-0.1.0/tidyout/init_agent.py +28 -0
- tidyout_cli-0.1.0/tidyout/rules/cargo_build.yaml +19 -0
- tidyout_cli-0.1.0/tidyout/rules/docker_logs.yaml +19 -0
- tidyout_cli-0.1.0/tidyout/rules/docker_ps.yaml +5 -0
- tidyout_cli-0.1.0/tidyout/rules/find.yaml +23 -0
- tidyout_cli-0.1.0/tidyout/rules/git_log.yaml +15 -0
- tidyout_cli-0.1.0/tidyout/rules/git_status.yaml +31 -0
- tidyout_cli-0.1.0/tidyout/rules/jest.yaml +33 -0
- tidyout_cli-0.1.0/tidyout/rules/ls.yaml +6 -0
- tidyout_cli-0.1.0/tidyout/rules/make.yaml +9 -0
- tidyout_cli-0.1.0/tidyout/rules/npm_install.yaml +23 -0
- tidyout_cli-0.1.0/tidyout/rules/pip_install.yaml +35 -0
- tidyout_cli-0.1.0/tidyout/rules/pytest.yaml +41 -0
- tidyout_cli-0.1.0/tidyout/rules/tree.yaml +11 -0
- tidyout_cli-0.1.0/tidyout/runner.py +15 -0
- tidyout_cli-0.1.0/tidyout/stripper.py +297 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*.*.*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
|
|
14
|
+
- uses: actions/setup-python@v5
|
|
15
|
+
with:
|
|
16
|
+
python-version: "3.11"
|
|
17
|
+
|
|
18
|
+
- name: Install dependencies
|
|
19
|
+
run: pip install -e ".[dev]"
|
|
20
|
+
|
|
21
|
+
- name: Run tests
|
|
22
|
+
run: pytest
|
|
23
|
+
|
|
24
|
+
publish:
|
|
25
|
+
needs: test
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
steps:
|
|
28
|
+
- uses: actions/checkout@v4
|
|
29
|
+
|
|
30
|
+
- uses: actions/setup-python@v5
|
|
31
|
+
with:
|
|
32
|
+
python-version: "3.11"
|
|
33
|
+
|
|
34
|
+
- name: Build package
|
|
35
|
+
run: |
|
|
36
|
+
pip install build
|
|
37
|
+
python -m build
|
|
38
|
+
|
|
39
|
+
- name: Publish to PyPI
|
|
40
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
41
|
+
with:
|
|
42
|
+
password: ${{ secrets.PYPI_API_TOKEN }}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Contributing a New Rule
|
|
2
|
+
|
|
3
|
+
Adding support for a new command is just one file.
|
|
4
|
+
|
|
5
|
+
## Steps
|
|
6
|
+
|
|
7
|
+
1. Create `tidyout/rules/<command>.yaml`
|
|
8
|
+
2. Follow the schema below
|
|
9
|
+
3. Add a test in `tests/test_stripper.py`
|
|
10
|
+
4. Open a PR
|
|
11
|
+
|
|
12
|
+
## YAML Schema
|
|
13
|
+
|
|
14
|
+
```yaml
|
|
15
|
+
strip:
|
|
16
|
+
- pattern: "string or regex"
|
|
17
|
+
type: prefix | contains | exact | regex
|
|
18
|
+
|
|
19
|
+
keep:
|
|
20
|
+
- pattern: "string or regex"
|
|
21
|
+
type: prefix | contains | exact | regex
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**`type` options:**
|
|
25
|
+
|
|
26
|
+
| Type | Behaviour |
|
|
27
|
+
|---|---|
|
|
28
|
+
| `prefix` | `line.startswith(pattern)` |
|
|
29
|
+
| `contains` | `pattern in line` |
|
|
30
|
+
| `exact` | `line == pattern` |
|
|
31
|
+
| `regex` | `re.search(pattern, line)` |
|
|
32
|
+
|
|
33
|
+
**Evaluation order:** `keep` always wins over `strip`. If a line matches a keep pattern it is always kept, even if it also matches a strip pattern.
|
|
34
|
+
|
|
35
|
+
## Two-word commands
|
|
36
|
+
|
|
37
|
+
For commands like `git status` or `docker logs`, name the file using an underscore:
|
|
38
|
+
`git_status.yaml`, `docker_logs.yaml`, `npm_install.yaml`
|
|
39
|
+
|
|
40
|
+
tidyout automatically tries the two-word key first, then falls back to the single-word key.
|
|
41
|
+
|
|
42
|
+
## Transform hooks (optional)
|
|
43
|
+
|
|
44
|
+
If the rule needs to reformat lines rather than just filter them (e.g., shorten a hash, reorder output), add a Python function in `tidyout/stripper.py` and register it in the `TRANSFORMS` dict:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
def transform_mycommand(lines):
|
|
48
|
+
# lines: filtered output after YAML rules applied
|
|
49
|
+
# return: final list of lines
|
|
50
|
+
return [line.upper() for line in lines]
|
|
51
|
+
|
|
52
|
+
TRANSFORMS = {
|
|
53
|
+
...
|
|
54
|
+
"mycommand": transform_mycommand,
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Example rule
|
|
59
|
+
|
|
60
|
+
`tidyout/rules/rspec.yaml` — strip passing examples, keep failures and summary:
|
|
61
|
+
|
|
62
|
+
```yaml
|
|
63
|
+
strip:
|
|
64
|
+
- pattern: "^ \\."
|
|
65
|
+
type: regex
|
|
66
|
+
- pattern: "^$"
|
|
67
|
+
type: exact
|
|
68
|
+
|
|
69
|
+
keep:
|
|
70
|
+
- pattern: "example"
|
|
71
|
+
type: contains
|
|
72
|
+
- pattern: "failure"
|
|
73
|
+
type: contains
|
|
74
|
+
- pattern: "Failure/Error"
|
|
75
|
+
type: contains
|
|
76
|
+
- pattern: "rspec"
|
|
77
|
+
type: contains
|
|
78
|
+
```
|
|
@@ -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,82 @@
|
|
|
1
|
+
# tidyout
|
|
2
|
+
|
|
3
|
+
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.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
✂️ tidyout: 312 lines → 18 lines | saved ~2376 tokens (94% reduction)
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Built for AI coding sessions (Claude Code, Cursor, Copilot) where terminal output burns tokens fast.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install tidyout-cli
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
tidy pytest tests/
|
|
21
|
+
tidy git status
|
|
22
|
+
tidy git log
|
|
23
|
+
tidy ls -la
|
|
24
|
+
tidy find . -name "*.py"
|
|
25
|
+
tidy docker logs mycontainer
|
|
26
|
+
tidy npm install
|
|
27
|
+
tidy cargo build
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Every run ends with a summary line:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
✂️ tidyout: {original} lines → {compressed} lines | saved ~{tokens} tokens ({percent}% reduction)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Commands without a matching rule pass through unchanged and still show the summary (0% reduction).
|
|
37
|
+
|
|
38
|
+
## Supported commands
|
|
39
|
+
|
|
40
|
+
| Command | What gets stripped | Typical savings |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| `pytest` | PASSED lines, platform info, separators | ~80% |
|
|
43
|
+
| `git status` | hints, up-to-date messages, blank lines | ~60% |
|
|
44
|
+
| `git log` | Author email, full hashes, blank lines | ~50% |
|
|
45
|
+
| `ls` | permissions, owner, group, timestamp — reformatted to `DIR:name` / `F:name:size` | ~70% |
|
|
46
|
+
| `find` | `.git/`, `node_modules/`, `__pycache__/`, `./` prefix | ~40% |
|
|
47
|
+
| `tree` | entries beyond depth 3, `node_modules`, `.git`, `__pycache__` | ~50% |
|
|
48
|
+
| `pip install` | progress bars, Collecting/Downloading, already-satisfied | ~90% |
|
|
49
|
+
| `npm install` | warn/notice lines, blank lines | ~85% |
|
|
50
|
+
| `docker logs` | health check noise, keeps last 50 lines | ~70% |
|
|
51
|
+
| `docker ps` | header, full IDs, padding — reformatted to `name:status:ports` | ~60% |
|
|
52
|
+
| `jest` | passing test names, keeps failures and summary | ~80% |
|
|
53
|
+
| `make` | Entering/Leaving directory noise | ~40% |
|
|
54
|
+
| `cargo build` | dependency Compiling lines, Downloading | ~75% |
|
|
55
|
+
|
|
56
|
+
## `tidy init` — inject rules into your AI agent config
|
|
57
|
+
|
|
58
|
+
Running `tidy init` in your project directory automatically tells your AI agent to always use `tidy`:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
tidy init
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
It detects which config file to update:
|
|
65
|
+
|
|
66
|
+
- `CLAUDE.md` exists → appends tidyout usage rules
|
|
67
|
+
- `.cursorrules` exists → appends tidyout usage rules
|
|
68
|
+
- Neither exists → creates `CLAUDE.md` with the rules
|
|
69
|
+
|
|
70
|
+
Injected block:
|
|
71
|
+
|
|
72
|
+
```markdown
|
|
73
|
+
## Token Optimization with tidyout
|
|
74
|
+
Always prefix the following commands with `tidy` when running in terminal:
|
|
75
|
+
pytest, npm test, jest, rspec, git status, git log, ls, find, tree,
|
|
76
|
+
pip install, npm install, docker logs, docker ps, make, cargo build
|
|
77
|
+
This reduces token usage by 60-97% on command outputs.
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Contributing a new rule
|
|
81
|
+
|
|
82
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) — it's just one YAML file.
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# tidyout — Design Spec
|
|
2
|
+
**Date:** 2026-05-12
|
|
3
|
+
**Status:** Approved
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## What It Does
|
|
8
|
+
|
|
9
|
+
`tidyout` is a Python CLI wrapper that sits between an AI agent (Claude Code, Cursor, etc.) and the terminal. When a command is prefixed with `tidy`, tidyout:
|
|
10
|
+
|
|
11
|
+
1. Executes the real command and captures stdout + stderr
|
|
12
|
+
2. Looks up a strip/keep rule for the command
|
|
13
|
+
3. Applies the rule to compress the output
|
|
14
|
+
4. Prints the compressed output followed by a token savings summary
|
|
15
|
+
|
|
16
|
+
Goal: reduce token consumption in AI agent sessions by stripping human-pretty noise and returning machine-readable output.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## CLI Usage
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
tidy pytest tests/
|
|
24
|
+
tidy git status
|
|
25
|
+
tidy ls -la
|
|
26
|
+
tidy docker logs mycontainer
|
|
27
|
+
tidy init
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Every run ends with:
|
|
31
|
+
```
|
|
32
|
+
✂️ tidyout: {original} lines → {compressed} lines | saved ~{tokens} tokens ({percent}% reduction)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Token estimate: `1 token ≈ 4 characters`. Savings calculated from character count difference.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Architecture
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
tidy <cmd> [args]
|
|
43
|
+
│
|
|
44
|
+
▼
|
|
45
|
+
cli.py:main()
|
|
46
|
+
│
|
|
47
|
+
├── cmd == "init" → init_agent.py
|
|
48
|
+
│
|
|
49
|
+
└── everything else → runner.py
|
|
50
|
+
│
|
|
51
|
+
▼
|
|
52
|
+
subprocess (stdout+stderr merged)
|
|
53
|
+
│
|
|
54
|
+
▼
|
|
55
|
+
stripper.py
|
|
56
|
+
│
|
|
57
|
+
├── loads rules/<cmd>.yaml (or no rule → pass-through)
|
|
58
|
+
├── applies strip/keep logic line-by-line
|
|
59
|
+
└── computes token savings
|
|
60
|
+
│
|
|
61
|
+
▼
|
|
62
|
+
print output + ✂️ summary line
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Command detection:**
|
|
66
|
+
- Single-word commands: `tidy pytest tests/` → rule key `pytest`
|
|
67
|
+
- Two-word subcommands: `tidy git status` → rule key `git_status`, `tidy docker logs` → `docker_logs`
|
|
68
|
+
- No matching rule → pass-through: run command, return output as-is, still print summary line (0% reduction)
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## YAML Rule Schema
|
|
73
|
+
|
|
74
|
+
Each file in `tidyout/rules/` follows this schema:
|
|
75
|
+
|
|
76
|
+
```yaml
|
|
77
|
+
strip:
|
|
78
|
+
- pattern: "regex or string"
|
|
79
|
+
type: regex | prefix | exact | contains
|
|
80
|
+
|
|
81
|
+
keep:
|
|
82
|
+
- pattern: "..."
|
|
83
|
+
type: regex | prefix | exact | contains
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Evaluation order per line:**
|
|
87
|
+
1. Matches any `keep` pattern → kept (keep always wins)
|
|
88
|
+
2. Matches any `strip` pattern → dropped
|
|
89
|
+
3. Otherwise → kept
|
|
90
|
+
|
|
91
|
+
**Special transforms** (reformatting, not just filtering) for `ls`, `git_log`, `docker_ps`, `git_status` are implemented as post-processing hooks in `stripper.py`, keyed by command name.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Components
|
|
96
|
+
|
|
97
|
+
| File | Responsibility |
|
|
98
|
+
|---|---|
|
|
99
|
+
| `cli.py` | Parse `sys.argv`, detect `init`, dispatch to runner or init_agent |
|
|
100
|
+
| `runner.py` | `subprocess.run` with merged stdout+stderr, returns raw output string |
|
|
101
|
+
| `stripper.py` | Load rule YAML, apply strip/keep, run transform hooks, calculate savings |
|
|
102
|
+
| `init_agent.py` | Detect/create CLAUDE.md / .cursorrules, append tidyout usage block |
|
|
103
|
+
| `rules/*.yaml` | One file per command, declarative strip/keep patterns |
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Rules Inventory
|
|
108
|
+
|
|
109
|
+
| Rule file | Command | Typical savings |
|
|
110
|
+
|---|---|---|
|
|
111
|
+
| `pytest.yaml` | `pytest` | ~80% |
|
|
112
|
+
| `git_status.yaml` | `git status` | ~60% |
|
|
113
|
+
| `git_log.yaml` | `git log` | ~50% |
|
|
114
|
+
| `ls.yaml` | `ls` | ~70% |
|
|
115
|
+
| `find.yaml` | `find` | ~40% |
|
|
116
|
+
| `tree.yaml` | `tree` | ~50% |
|
|
117
|
+
| `pip_install.yaml` | `pip install` | ~90% |
|
|
118
|
+
| `npm_install.yaml` | `npm install` | ~85% |
|
|
119
|
+
| `docker_logs.yaml` | `docker logs` | ~70% |
|
|
120
|
+
| `docker_ps.yaml` | `docker ps` | ~60% |
|
|
121
|
+
| `jest.yaml` | `jest` | ~80% |
|
|
122
|
+
| `make.yaml` | `make` | ~40% |
|
|
123
|
+
| `cargo_build.yaml` | `cargo build` | ~75% |
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## `tidy init` Behavior
|
|
128
|
+
|
|
129
|
+
- Detects `CLAUDE.md` in CWD → appends tidyout usage block
|
|
130
|
+
- Detects `.cursorrules` in CWD → appends tidyout usage block
|
|
131
|
+
- Neither exists → creates `CLAUDE.md` with the block
|
|
132
|
+
- Prints exactly what action was taken
|
|
133
|
+
|
|
134
|
+
**Injected block:**
|
|
135
|
+
```markdown
|
|
136
|
+
## Token Optimization with tidyout
|
|
137
|
+
Always prefix the following commands with `tidy` when running in terminal:
|
|
138
|
+
pytest, npm test, jest, rspec, git status, git log, ls, find, tree,
|
|
139
|
+
pip install, npm install, docker logs, docker ps, make, cargo build
|
|
140
|
+
This reduces token usage by 60-97% on command outputs.
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Token Savings Calculation
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
original_chars = len(original_output)
|
|
149
|
+
compressed_chars = len(compressed_output)
|
|
150
|
+
saved_tokens = (original_chars - compressed_chars) // 4
|
|
151
|
+
percent = int((1 - compressed_chars / original_chars) * 100) if original_chars else 0
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
`1 token ≈ 4 characters` is the standard cross-industry rule of thumb (from OpenAI tokenizer research, also used by Anthropic). Accurate enough for an estimate.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Testing
|
|
159
|
+
|
|
160
|
+
- `tests/test_stripper.py` — unit tests per rule: feed known raw output string, assert compressed output matches expected
|
|
161
|
+
- `tests/test_cli.py` — integration tests for `tidy init`: verify file creation/appending with correct content
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Packaging & Publishing
|
|
166
|
+
|
|
167
|
+
- `pyproject.toml`: package `tidyout`, version `0.1.0`, entry point `tidy = tidyout.cli:main`, Python `>=3.9`
|
|
168
|
+
- GitHub Actions workflow at `.github/workflows/publish.yml`: triggers on `v*.*.*` tags, runs tests, builds, publishes to PyPI via `PYPI_API_TOKEN` secret
|
|
169
|
+
- GitHub repo: `sathish-2000/tidyout`
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tidyout-cli"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "CLI wrapper that compresses command output for AI agents, reducing token usage by 60-97%"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
keywords = ["cli", "ai", "tokens", "compression", "developer-tools"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 3 - Alpha",
|
|
15
|
+
"Environment :: Console",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.9",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
24
|
+
"Topic :: Utilities",
|
|
25
|
+
]
|
|
26
|
+
dependencies = [
|
|
27
|
+
"pyyaml>=6.0",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.scripts]
|
|
31
|
+
tidy = "tidyout.cli:main"
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
dev = [
|
|
35
|
+
"pytest>=7.0",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[tool.hatch.build.targets.wheel]
|
|
39
|
+
packages = ["tidyout"]
|
|
40
|
+
|
|
41
|
+
[tool.pytest.ini_options]
|
|
42
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pytest
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from tidyout.init_agent import run_init, TIDYOUT_BLOCK
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_init_creates_claude_md_when_neither_exists(tmp_path):
|
|
9
|
+
orig = os.getcwd()
|
|
10
|
+
os.chdir(tmp_path)
|
|
11
|
+
try:
|
|
12
|
+
run_init()
|
|
13
|
+
claude_md = tmp_path / "CLAUDE.md"
|
|
14
|
+
assert claude_md.exists()
|
|
15
|
+
content = claude_md.read_text()
|
|
16
|
+
assert "tidyout" in content
|
|
17
|
+
assert "tidy" in content
|
|
18
|
+
finally:
|
|
19
|
+
os.chdir(orig)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_init_appends_to_existing_claude_md(tmp_path):
|
|
23
|
+
orig = os.getcwd()
|
|
24
|
+
os.chdir(tmp_path)
|
|
25
|
+
try:
|
|
26
|
+
claude_md = tmp_path / "CLAUDE.md"
|
|
27
|
+
claude_md.write_text("# Existing content\n")
|
|
28
|
+
run_init()
|
|
29
|
+
content = claude_md.read_text()
|
|
30
|
+
assert "# Existing content" in content
|
|
31
|
+
assert "tidyout" in content
|
|
32
|
+
finally:
|
|
33
|
+
os.chdir(orig)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_init_does_not_create_new_claude_md_when_cursorrules_exists(tmp_path):
|
|
37
|
+
orig = os.getcwd()
|
|
38
|
+
os.chdir(tmp_path)
|
|
39
|
+
try:
|
|
40
|
+
cursorrules = tmp_path / ".cursorrules"
|
|
41
|
+
cursorrules.write_text("# Cursor rules\n")
|
|
42
|
+
run_init()
|
|
43
|
+
assert not (tmp_path / "CLAUDE.md").exists()
|
|
44
|
+
assert "tidyout" in cursorrules.read_text()
|
|
45
|
+
finally:
|
|
46
|
+
os.chdir(orig)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_init_appends_to_cursorrules(tmp_path):
|
|
50
|
+
orig = os.getcwd()
|
|
51
|
+
os.chdir(tmp_path)
|
|
52
|
+
try:
|
|
53
|
+
cursorrules = tmp_path / ".cursorrules"
|
|
54
|
+
cursorrules.write_text("# Cursor rules\n")
|
|
55
|
+
run_init()
|
|
56
|
+
content = cursorrules.read_text()
|
|
57
|
+
assert "# Cursor rules" in content
|
|
58
|
+
assert "tidyout" in content
|
|
59
|
+
finally:
|
|
60
|
+
os.chdir(orig)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_init_prefers_claude_md_over_cursorrules(tmp_path):
|
|
64
|
+
orig = os.getcwd()
|
|
65
|
+
os.chdir(tmp_path)
|
|
66
|
+
try:
|
|
67
|
+
claude_md = tmp_path / "CLAUDE.md"
|
|
68
|
+
cursorrules = tmp_path / ".cursorrules"
|
|
69
|
+
claude_md.write_text("# Claude\n")
|
|
70
|
+
cursorrules.write_text("# Cursor\n")
|
|
71
|
+
run_init()
|
|
72
|
+
assert "tidyout" in claude_md.read_text()
|
|
73
|
+
assert "tidyout" not in cursorrules.read_text()
|
|
74
|
+
finally:
|
|
75
|
+
os.chdir(orig)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_init_block_contains_key_commands(tmp_path):
|
|
79
|
+
orig = os.getcwd()
|
|
80
|
+
os.chdir(tmp_path)
|
|
81
|
+
try:
|
|
82
|
+
run_init()
|
|
83
|
+
content = (tmp_path / "CLAUDE.md").read_text()
|
|
84
|
+
for cmd in ["pytest", "git status", "git log", "docker logs", "npm install"]:
|
|
85
|
+
assert cmd in content
|
|
86
|
+
finally:
|
|
87
|
+
os.chdir(orig)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_init_prints_action(tmp_path, capsys):
|
|
91
|
+
orig = os.getcwd()
|
|
92
|
+
os.chdir(tmp_path)
|
|
93
|
+
try:
|
|
94
|
+
run_init()
|
|
95
|
+
out = capsys.readouterr().out
|
|
96
|
+
assert "CLAUDE.md" in out
|
|
97
|
+
finally:
|
|
98
|
+
os.chdir(orig)
|