@complexthings/superpowers-agent 9.0.0 → 9.0.2

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@complexthings/superpowers-agent",
3
- "version": "9.0.0",
3
+ "version": "9.0.2",
4
4
  "description": "Superpowers agent CLI — skills system for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -33,7 +33,7 @@
33
33
  },
34
34
  "repository": {
35
35
  "type": "git",
36
- "url": "https://github.com/complexthings/superpowers.git"
36
+ "url": "git+https://github.com/complexthings/superpowers.git"
37
37
  },
38
38
  "bugs": {
39
39
  "url": "https://github.com/complexthings/superpowers/issues"
@@ -1,132 +1,111 @@
1
1
  ---
2
2
  name: leveraging-cli-tools
3
- description: Use when performing code searches, JSON parsing, file viewing, or file finding tasks - ensures agents verify and use high-performance CLI tools (rg, jq, fd, bat, ast-grep) instead of slower standard tools, reducing token costs and latency by 5-50x
3
+ description: Use when performing code searches, JSON/YAML parsing, file finding, structural refactors, or data wrangling, or when a command floods context with verbose output like logs, CI, or test runs - ensures agents reach for high-performance CLI tools (rg, jq, fd, yq, ast-grep, gh, sd) over slower standard tools like grep/find/sed and use them with discipline, choosing the right output-reducing flags, composing pipelines so raw output never enters context, and using a bundled reducer for output that flags cannot shape, cutting token cost and latency 5-50x. Check availability and offer to install a tool when a relevant task arises.
4
+ compatibility: The bundled scripts/slim.py needs python3 (standard on macOS/Linux). The CLI tools install via the system package manager.
4
5
  ---
5
6
 
6
7
  # Leveraging CLI Tools
7
8
 
8
- ## Core Principle
9
+ ## Purpose
9
10
 
10
- **High-performance CLI tools reduce costs, latency, and cognitive load by 5-50x.**
11
+ Reach for high-performance CLI tools over slower standard tools. The leverage is filtering and transforming with the right tool **before reading**, so tokens and time go to the answer, not the search. On a large tree `rg` is 10-50x faster than `grep` and returns far less noise; across a session that compounds into hours and tens of thousands of tokens saved.
11
12
 
12
- Modern tools have massive RL optimization in frontier models - use them.
13
+ Picking the faster tool is only half of it. The other half is using it with discipline — two levers that keep raw output out of your context: **ask for less** (the right flags and selectors), and **shrink what you can't shape** (a reducer for inherently verbose output). A fast tool fed a lazy command still floods your context.
13
14
 
14
- ## Tool Availability Protocol
15
+ ## When a relevant task arises
15
16
 
16
- **Session start (silent check, cache result):**
17
+ 1. Pick the right tool from the table below.
18
+ 2. Confirm it's installed before relying on it — e.g. `command -v rg`.
19
+ 3. If it's missing, name the tool and its payoff and **offer** to install it — don't install silently. Adapt the command to the user's package manager/OS (the table shows `brew`; substitute `apt install`, `dnf install`, `pacman -S`, `cargo install`, etc.). If they decline, fall back to the standard tool and move on.
17
20
 
18
- ```bash
19
- command -v rg jq fd bat gh >/dev/null 2>&1 && echo "ready" || echo "missing"
20
- ```
21
+ Check only the tools the current task needs — no upfront session-wide scan.
21
22
 
22
- **If tools missing:** Offer installation when relevant task appears.
23
- **If tools ready:** Use optimal workflows throughout session.
23
+ ## Tools
24
24
 
25
- ## Tool Selection
25
+ | Rating | Tool | Replaces | Why | Install |
26
+ |:------:|------|----------|-----|---------|
27
+ | 10 | `rg` (ripgrep) | `grep`, `grep -r`, `ack` | Code/text search. Respects `.gitignore`, 10-50x faster than `grep` — the highest-leverage tool; filter before reading. | `brew install ripgrep` |
28
+ | 10 | `jq` | `grep`/`sed`/`awk` on JSON | JSON query/transform. Turns API responses and config into exactly the fields you need; the pipe target for JSON. | `brew install jq` |
29
+ | 9 | `fd` | `find` | File finding. Faster, saner syntax, parallel traversal, `.gitignore`-aware. | `brew install fd` |
30
+ | 8 | `yq` | `grep`/`sed`/`awk` on YAML | `jq` for YAML/TOML/XML. Reads CI files, `docker-compose`, k8s manifests, frontmatter. | `brew install yq` |
31
+ | 8 | `ast-grep` (`sg`) | `sed`/`grep` for refactors | Structural search/rewrite by AST, not regex. Safe codebase-wide refactors that `sed` would mangle. | `brew install ast-grep` |
32
+ | 8 | `gh` | `curl` + GitHub API + tokens | GitHub from the shell — PRs, issues, CI, API. No hand-rolled `curl` + token juggling. | `brew install gh` |
33
+ | 7 | `sd` | `sed -i`, `perl -pe` | Find/replace. Literal-string-safe, no regex-escaping footguns. | `brew install sd` |
34
+ | 6 | `dasel` | `jq`+`yq`+`xq` (mixed formats) | Query *and modify* JSON/YAML/TOML/XML/CSV through one selector. Use when format is mixed or unknown. | `brew install dasel` |
35
+ | 6 | `htmlq` | `grep`/`sed` on HTML | `jq` for HTML — CSS-selector extraction from fetched pages. | `brew install htmlq` |
36
+ | 6 | `miller` (`mlr`) | `awk`/`cut`/`join`/`sort` on CSV/TSV | `awk`/`cut`/`join`/`sort` for CSV/TSV/JSON with *named* fields — no brittle column counting. | `brew install miller` |
37
+ | 5 | `qsv` | `awk`/`cut`/`sort -u` on CSV, `csvkit` | High-perf CSV toolkit (maintained `xsv` successor). Stats, slice, join, dedup on big CSVs. | `brew install qsv` |
38
+ | 5 | `hyperfine` | `time`, `for`-loop timing | Statistical benchmarking with warmups. Real before/after numbers, not `time` guesses. | `brew install hyperfine` |
39
+ | 5 | `tokei` | `wc -l`, `find … \| wc`, `cloc` | Instant LOC/language breakdown. Orient in an unfamiliar repo before exploring. | `brew install tokei` |
40
+ | 5 | `fzf` (`-f`) | manual fuzzy filtering | Non-interactive `-f`/`--filter` mode: fuzzy-rank a candidate list piped from `fd`/`rg`. | `brew install fzf` |
41
+ | 4 | `watchexec` | `while`+`sleep`, `entr` | Run a command on file change. Useful in build/test loops; non-interactive unlike most watchers. | `brew install watchexec` |
26
42
 
27
- | Task | Use | Instead of | Speedup |
28
- |------|-----|-----------|---------|
29
- | Search code | **rg** | grep | 10-50x |
30
- | Parse JSON | **jq** | awk/sed | 5-20x |
31
- | Find files | **fd** | find | 5-10x |
32
- | View code | **bat** | cat | Better UX |
33
- | Transform code | **ast-grep** | sed | 3-10x |
34
- | GitHub ops | **gh** | curl+API | 2-5x |
35
- | Interactive select | **fzf** | manual | 10-100x |
43
+ ## Use them with discipline
36
44
 
37
- ## Quick Reference
45
+ Five habits separate a fast, low-noise result from a slow, context-flooding one. The token savings below are measured, not hypothetical.
38
46
 
39
- ### Code Search: rg
40
- ```bash
41
- rg "AuthError" --type typescript # 10-50x faster than grep, respects .gitignore
42
- ```
47
+ **1 Pick the tool that fits the data, not the one in muscle memory.** JSON → `jq`, YAML/TOML → `yq`, structural refactor → `ast-grep`, GitHub → `gh`. The reflex to resist is `grep`/`sed`/`awk` on structured data — they treat structure as flat text, so you pay in escaping bugs and noise the format-aware tool never produces.
43
48
 
44
- ### JSON Parse: jq
45
- ```bash
46
- jq '.results[] | select(.status=="error") | .error.code' api.json | sort -u
47
- ```
49
+ **2 Ask for less: use the flag or selector that returns the answer's _shape_.** The biggest single win is never pulling raw output into context. Match the request to the question:
48
50
 
49
- ### Find Files: fd
50
- ```bash
51
- fd "\.test\.ts$" # 5-10x faster than find, parallel traversal
52
- ```
51
+ | Question | Flag / selector | Returns |
52
+ |----------|-----------------|---------|
53
+ | Which files match? | `rg -l` | paths only, no lines |
54
+ | How many files? | `rg -l \| wc -l` | a file count, not a line count |
55
+ | How many hits? | `rg -c` (lines/file) / `rg --count-matches` (matches/file) | counts only |
56
+ | Just the matched bit? | `rg -o` | the substring, not the whole line |
57
+ | Enough to judge a hit? | `rg -A/-B/-C N`, `rg -m N` (cap per file) | bounded context |
58
+ | Only certain fields? | `jq -r '.a, .b'`, `yq`, `mlr --opprint cut -f` | projected values, not the whole doc |
59
+ | First N of a list? | pipe to `head`, or `fd --max-results N` | the slice you need |
53
60
 
54
- ### View Code: bat
55
- ```bash
56
- bat src/auth.ts # Syntax highlighting, Git integration, line numbers
57
- ```
61
+ For the **projection tools** (`jq`, `yq`, `dasel`, `miller`, `htmlq`) the selector *is* the filter — `jq` turning a 2,000-record array into one summed number is a ~100% reduction on its own. Shape their output at the selector; never post-filter it (that's what habit 4 is *not* for).
58
62
 
59
- ### Code Transform: ast-grep
63
+ **3 Compose so only the answer comes back.** Do the counting, dedup, and projection *in the pipeline*, not by reading raw output and reasoning over it. One pass keeps every intermediate result out of context:
60
64
  ```bash
61
- sg --pattern 'console.log($$$ARGS)' --rewrite 'logger.debug($$$ARGS)'
65
+ rg -l '"error"' logs/ --type json | xargs jq -r 'select(.level=="error") | .code' | sort -u
62
66
  ```
63
-
64
- ## Installation Protocol
65
-
66
- **If tool missing when needed:**
67
-
68
- 1. Explain impact: "Using rg is 10-50x faster, reducing token costs"
69
- 2. Install automatically for core tools (rg, jq, fd, bat, gh): `brew install ripgrep jq fd bat gh` (macOS) or equivalent
70
- 3. Ask before installing: ast-grep, httpie, fzf
71
-
72
- **No exceptions:** If tool unavailable and user declines install, explain performance cost but use fallback.
73
-
74
- ## Key Workflows
75
-
76
- ### Filter Before Reading
67
+ This is the lesson of programmatic tool calling (Amazon's PTC benchmarks: ~87-92% fewer tokens): when a task needs several tool calls plus processing, write **one** pipeline or script that does it all and returns only the result — the intermediate data never touches your context. So: filter before reading, and refactor by AST, not regex:
77
68
  ```bash
78
- # DON'T: Read all files blindly
79
- # DO: Filter first, read matches only
80
- rg "password.*hash" src/auth/ --type ts -l | xargs bat
69
+ rg -l "password.*hash" src/auth/ --type ts | xargs rg "TODO"
70
+ sg --pattern 'console.log($$$A)' --rewrite 'logger.debug($$$A)' --lang ts
81
71
  ```
82
72
 
83
- Under fatigue: filtering first reduces cognitive load.
84
-
85
- ### Compose Tools
73
+ **4 — Shrink what you can't shape: the bundled reducer for verbose emitters.** Some output has no projection flag — logs, CI output (`gh run view --log`), test runs, stack traces, status dumps. Flags get you part way, but the residual stays repetitive, wide, and noisy. Pipe it through `scripts/slim.py` (relative to this skill; use its absolute path if you're working elsewhere) and ask only for the cuts the task needs:
86
74
  ```bash
87
- rg "error.code" logs/ -o | jq -r . | sort -u # Search + parse + dedup
75
+ gh run view --log | python3 scripts/slim.py --errors --uniq --max-line 200
76
+ cargo test 2>&1 | python3 scripts/slim.py --errors --dedup
77
+ rg -n TODO --no-ignore | python3 scripts/slim.py --group-dir
88
78
  ```
79
+ It does dedup (`--dedup`/`--uniq`, repeats collapse to `(xN)`), grouping (`--group-dir` → per-directory counts), truncation (`--head/--tail/--middle/--max-line`), and noise-stripping (`--comments/--errors/--grep`). On a real 1,236-line CI log, `rg` error-filtering alone cut 91% but left ~6,700 tokens; adding `slim` reached 97% — a third the residual. Run `python3 scripts/slim.py -h` for all modes. The cuts are **lossy and opt-in**: ask for what you need, know what you're discarding, and don't reach for it when a projection tool's own selector would do the job.
89
80
 
90
- ## Performance Impact
81
+ **5 Read the output critically — it's a claim, not a fact.** A zero or suspiciously low result is the one to distrust: `rg` skips `.gitignore`d and hidden files by default, so a real match in `node_modules/`, `dist/`, or a dotfile is silently absent. Before concluding "none," re-run with `--no-ignore`, `-uu`, or `--hidden` — then decide whether those ignored hits (vendored deps, build output, generated mirrors) actually belong in the answer; surfacing them is the check, keeping them is a judgment call. Mind case (`-i`) and word boundaries (`-w`) so you neither miss `Subagent` nor over-match `tasks` — and when an identifier doubles as a common word (a `Task` tool vs. the word "task"), even `-w` isn't enough: glance at the context to confirm the hit is the symbol, not prose. When a count or list drives a decision, confirm a match means what you think before acting on it.
91
82
 
92
- | Task | grep/find/cat | rg/fd/bat | Savings |
93
- |------|---------------|-----------|---------|
94
- | Search 50k files | 45s | 0.8s | 56x, ~40k tokens |
95
- | Parse 10MB JSON | 12s (awk) | 2s (jq) | 6x, ~15k tokens |
96
- | Find in monorepo | 8s | 1s | 8x, ~10k tokens |
83
+ In Claude Code the `Grep` and `Glob` tools are themselves built on ripgrep — prefer them for in-context searches, and reach for the CLI tools when you need piping, transforms, rewrites, or output reduction.
97
84
 
98
- ## Red Flags
85
+ ## Red flags
99
86
 
100
- - Manually parsing JSON with awk/sed/grep (use jq)
101
- - Reading files without filtering first (use rg to filter)
102
- - Not checking tool availability at session start
103
- - Using bash grep when Grep tool exists (use Grep tool)
87
+ - Parsing JSON/YAML with `awk`/`sed`/`grep` instead of `jq`/`yq`.
88
+ - Reading files before filtering them with `rg`.
89
+ - Piping a tool's full output into context to eyeball it, when `-l`/`-c`/`-o` or a `jq` projection would return just the answer.
90
+ - Letting a verbose command (test run, CI log, `git`/`docker` status dump) land raw in context when `slim` would cut it 90%+.
91
+ - Reaching for `slim` on a projection tool's output — shape it at the `jq`/`yq`/`mlr` selector instead.
92
+ - Concluding "no matches" from a default `rg` run without re-checking `--no-ignore`/`--hidden` — the hit may be sitting in an ignored directory.
93
+ - Hand-rolling `curl` against the GitHub API instead of `gh`.
94
+ - Regex codemods with `sed` where `ast-grep` is structurally safe.
104
95
 
105
- ## Common Rationalizations
96
+ ## Common rationalizations
106
97
 
107
98
  | Excuse | Reality |
108
99
  |--------|---------|
109
- | "grep works fine" | 50x slower, burns 40k tokens on 50k files |
110
- | "I don't know if they have jq" | Check once, install takes 30s, saves hours |
111
- | "Not worth setup overhead" | One install = 10-100x speedup on ALL future tasks |
112
- | "User didn't ask for optimization" | Faster task completion IS better completion |
113
-
114
- ## When NOT to Use
115
-
116
- **Skip when:**
117
- - Small dataset (< 100 files, < 1MB) AND one-off task
118
- - User explicitly declines install
119
- - Teaching context where standard tools are the goal
120
-
121
- **Use when:**
122
- - Codebase search (always)
123
- - JSON operations (always)
124
- - Time pressure, large datasets, repeated operations
125
-
126
- ## Summary
127
-
128
- **Protocol: Check → Use → Combine**
129
-
130
- Session start: Silent availability check. Task appears: Use optimal tool or install. Complex tasks: Compose tools.
131
-
132
- **Not using available tools burns resources.**
100
+ | "grep works fine" | On a big tree it's 10-50x slower and floods context with noise `rg` would have filtered out. |
101
+ | "I don't know if they have jq" | One `command -v jq` answers it; install is seconds and pays back across the whole session. |
102
+ | "Not worth the setup" | One install = a speedup on every future task, not just this one. |
103
+ | "The test/CI output is just long, I'll scroll it" | A 1,200-line log is ~75k tokens of mostly noise; `slim --errors --uniq` makes it ~2k without losing the failures. |
104
+ | "User didn't ask for optimization" | Faster, lower-noise completion *is* better completion. |
105
+
106
+ ## When NOT to use
107
+
108
+ - A tiny one-off (a handful of files, well under a megabyte) where the standard tool is already at hand.
109
+ - The user declined the install — note the cost once, then use the fallback.
110
+ - A teaching context where the standard tool is the point.
111
+ - Output you already shaped with a selector — don't add a `slim` pass for its own sake.
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env python3
2
+ """slim - reduce noisy command output to signal before it reaches your context.
3
+
4
+ A stdin->stdout filter for output you CAN'T shape with a tool's own flags:
5
+ logs, scan results, status dumps, stack traces, repeated lines. For tools that
6
+ already project output (jq/yq/dasel/miller/htmlq), use their selectors instead;
7
+ piping them through slim is redundant.
8
+
9
+ Pipe a verbose command through it and ask only for the reductions you need:
10
+
11
+ cargo test 2>&1 | slim --errors --dedup
12
+ rg -n TODO --no-ignore | slim --group-dir
13
+ gh run view --log | slim --uniq --max-line 200 --middle 40
14
+ kubectl get events | slim --dedup --stats
15
+
16
+ Reductions (compose freely):
17
+ --comments drop comment-only lines (# // ; -- *) and blank lines
18
+ --errors keep only lines matching error|warn|fail|fatal|panic|exception (-i)
19
+ --grep RE keep only lines matching regex RE (use --invert to drop them)
20
+ --invert invert --grep / --errors (drop matches instead of keeping)
21
+ --dedup collapse ALL repeated lines, first-seen order, annotate " (xN)"
22
+ --uniq collapse only CONSECUTIVE repeats, annotate " (xN)" (log-friendly)
23
+ --group-dir turn a list of file paths into per-directory counts
24
+ --max-line N truncate each line to N chars, append a horizontal ellipsis
25
+ --head N keep first N lines
26
+ --tail N keep last N lines
27
+ --middle N keep first N and last N lines, replace the gap with a marker
28
+ --stats print "lines A->B, bytes A->B (-P%)" to stderr (output unaffected)
29
+
30
+ Always-on, lossless: trailing whitespace is stripped and runs of blank lines
31
+ collapse to one. Everything lossy is opt-in. Order: filter -> transform ->
32
+ dedup -> group -> blank-collapse -> truncate. Reductions are LOSSY by design;
33
+ ask for what the task needs and know what you're discarding.
34
+ """
35
+ import argparse
36
+ import re
37
+ import sys
38
+
39
+ COMMENT_RE = re.compile(r"^\s*(#|//|;|--|\*|/\*|\*/)")
40
+ ERROR_RE = re.compile(r"error|warn|fail|fatal|panic|exception|traceback", re.IGNORECASE)
41
+ ELLIPSIS = "…"
42
+
43
+
44
+ def annotate(line: str, n: int) -> str:
45
+ return line if n == 1 else f"{line} (x{n})"
46
+
47
+
48
+ def dedup_all(lines):
49
+ counts, order = {}, []
50
+ for ln in lines:
51
+ if ln not in counts:
52
+ counts[ln] = 0
53
+ order.append(ln)
54
+ counts[ln] += 1
55
+ return [annotate(ln, counts[ln]) for ln in order]
56
+
57
+
58
+ def dedup_adjacent(lines):
59
+ out, prev, n = [], None, 0
60
+ for ln in lines:
61
+ if ln == prev:
62
+ n += 1
63
+ else:
64
+ if prev is not None:
65
+ out.append(annotate(prev, n))
66
+ prev, n = ln, 1
67
+ if prev is not None:
68
+ out.append(annotate(prev, n))
69
+ return out
70
+
71
+
72
+ def group_dir(lines):
73
+ counts, order = {}, []
74
+ for ln in lines:
75
+ token = ln.split(":", 1)[0].strip() # tolerate rg's path:line: prefix
76
+ d = token.rsplit("/", 1)[0] + "/" if "/" in token else "./"
77
+ if d not in counts:
78
+ counts[d] = 0
79
+ order.append(d)
80
+ counts[d] += 1
81
+ width = max((len(d) for d in order), default=0)
82
+ return [f"{d.ljust(width)} ({counts[d]} file{'s' if counts[d] != 1 else ''})" for d in order]
83
+
84
+
85
+ def collapse_blanks(lines):
86
+ out, blank = [], False
87
+ for ln in lines:
88
+ if ln.strip() == "":
89
+ if not blank:
90
+ out.append("")
91
+ blank = True
92
+ else:
93
+ out.append(ln)
94
+ blank = False
95
+ while out and out[-1] == "":
96
+ out.pop()
97
+ return out
98
+
99
+
100
+ def main() -> int:
101
+ p = argparse.ArgumentParser(add_help=True, description="Reduce noisy stdout to signal.")
102
+ p.add_argument("--comments", action="store_true")
103
+ p.add_argument("--errors", action="store_true")
104
+ p.add_argument("--grep", metavar="RE")
105
+ p.add_argument("--invert", action="store_true")
106
+ p.add_argument("--dedup", action="store_true")
107
+ p.add_argument("--uniq", action="store_true")
108
+ p.add_argument("--group-dir", action="store_true")
109
+ p.add_argument("--max-line", type=int, metavar="N")
110
+ p.add_argument("--head", type=int, metavar="N")
111
+ p.add_argument("--tail", type=int, metavar="N")
112
+ p.add_argument("--middle", type=int, metavar="N")
113
+ p.add_argument("--stats", action="store_true")
114
+ args = p.parse_args()
115
+
116
+ raw = sys.stdin.read()
117
+ in_lines = raw.splitlines()
118
+ in_bytes = len(raw.encode("utf-8", "replace"))
119
+
120
+ lines = [ln.rstrip() for ln in in_lines]
121
+
122
+ if args.comments:
123
+ lines = [ln for ln in lines if ln.strip() and not COMMENT_RE.match(ln)]
124
+
125
+ if args.errors or args.grep:
126
+ pat = re.compile(args.grep) if args.grep else ERROR_RE
127
+ keep = (lambda ln: not pat.search(ln)) if args.invert else (lambda ln: bool(pat.search(ln)))
128
+ lines = [ln for ln in lines if keep(ln)]
129
+
130
+ if args.max_line and args.max_line > 0:
131
+ lines = [ln if len(ln) <= args.max_line else ln[: args.max_line] + ELLIPSIS for ln in lines]
132
+
133
+ if args.dedup:
134
+ lines = dedup_all(lines)
135
+ elif args.uniq:
136
+ lines = dedup_adjacent(lines)
137
+
138
+ if args.group_dir:
139
+ lines = group_dir(lines)
140
+
141
+ lines = collapse_blanks(lines)
142
+
143
+ if args.middle and args.middle > 0 and len(lines) > 2 * args.middle:
144
+ omitted = len(lines) - 2 * args.middle
145
+ lines = lines[: args.middle] + [f"{ELLIPSIS} {omitted} lines omitted {ELLIPSIS}"] + lines[-args.middle :]
146
+ if args.head and args.head > 0:
147
+ lines = lines[: args.head]
148
+ if args.tail and args.tail > 0:
149
+ lines = lines[-args.tail :]
150
+
151
+ out = "\n".join(lines)
152
+ if out:
153
+ out += "\n"
154
+ sys.stdout.write(out)
155
+
156
+ if args.stats:
157
+ out_bytes = len(out.encode("utf-8", "replace"))
158
+ pct = 0 if in_bytes == 0 else round(100 * (1 - out_bytes / in_bytes))
159
+ change = f"-{pct}%" if pct >= 0 else f"+{-pct}% larger"
160
+ sys.stderr.write(
161
+ f"slim: lines {len(in_lines)}->{len(lines)}, bytes {in_bytes}->{out_bytes} ({change})\n"
162
+ )
163
+ return 0
164
+
165
+
166
+ if __name__ == "__main__":
167
+ sys.exit(main())