evolver-tools 1.5.0__tar.gz → 3.0.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.
- evolver_tools-3.0.0/PKG-INFO +112 -0
- evolver_tools-3.0.0/README.md +90 -0
- evolver_tools-3.0.0/pyproject.toml +36 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/__init__.py +2 -0
- evolver_tools-3.0.0/src/evolver_tools/autoreg.py +88 -0
- evolver_tools-3.0.0/src/evolver_tools/cli.py +66 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/__init__.py +3 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/agent_b_tool.py +9 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/api_tester.py +237 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/ascii_gen.py +150 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/audit_log.py +269 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/b64/__init__.py +9 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/backup.py +87 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/banner/__init__.py +9 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/banner/banner.py +484 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/banner.py +77 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/bookmark.py +284 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/cal_tool/__init__.py +9 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/cal_tool/cli.py +8 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/cert_check.py +265 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/changelog_gen/__init__.py +9 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/changelog_gen/changelog_gen.py +199 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/changelog_gen.py +79 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/chart_cli/__init__.py +8 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/checksum_dir.py +309 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/clipboard/__init__.py +9 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/clipboard/clipboard.py +146 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/colorize.py +200 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/colors/__init__.py +7 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/config_validator.py +320 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/cron/__init__.py +8 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/crontab_helper.py +433 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/csv_stats/__init__.py +13 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/csv_stats/cli.py +8 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/db_schema.py +217 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/dep_graph.py +258 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/diff_csv.py +203 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/diff_tool/__init__.py +7 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/dirsize/__init__.py +8 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/disk_usage/__init__.py +9 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/disk_usage/disk_usage.py +176 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/dt_convert.py +8 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/env_manager.py +250 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/envcheck/__init__.py +8 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/excel2csv.py +240 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/ff/__init__.py +8 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/figlet_cli.py +150 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/figlet_tool.py +276 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/file_encrypt.py +184 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/find_dups/__init__.py +15 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/find_dups/cli.py +8 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/firewall_rule.py +280 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/fmt/__init__.py +9 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/fmt/fmt.py +143 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/git_branch_cleaner.py +242 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/hashsum/__init__.py +8 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/html2markdown.py +158 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/html2md.py +383 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/http_live/__init__.py +8 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/image_meta.py +326 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/ini_parser/__init__.py +9 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/ini_parser/ini_parser.py +167 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/ipcalc/__init__.py +8 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/ipinfo/__init__.py +10 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/join.py +32 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/joke.py +99 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/jq_lite/__init__.py +8 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/json2csv/__init__.py +10 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/json_pretty/__init__.py +9 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/json_pretty/json_pretty.py +121 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/jsonql/__init__.py +8 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/license_cli/__init__.py +9 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/license_cli/cli.py +8 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/log_analyzer.py +284 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/log_tail.py +125 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/markdown_check/__init__.py +8 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/morse.py +130 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/nb/__init__.py +8 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/net_speed.py +89 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/note_taker.py +124 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/otp_gen.py +237 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/passgen/__init__.py +8 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/password_strength.py +177 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/portcheck/__init__.py +10 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/portcheck/__main__.py +8 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/pr_tool/__init__.py +9 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/pr_tool/pr_tool.py +118 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/process_kill.py +260 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/progress_bar.py +54 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/project_doctor/__init__.py +8 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/qrcode.py +129 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/quote_tool/__init__.py +9 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/quote_tool/quote.py +211 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/rainbow.py +80 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/reminder.py +102 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/ren/__init__.py +8 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/restore.py +119 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/scan_ports.py +109 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/service_check.py +218 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/shuffle.py +37 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/siege_lite/__init__.py +8 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/smellfinder/__init__.py +8 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/sort/__init__.py +9 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/sort/sort.py +180 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/spinner.py +89 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/split.py +39 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/split_tool/__init__.py +9 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/split_tool/split.py +190 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/sql2csv.py +611 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/sqlite_cli/__init__.py +8 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/ssh_key_gen.py +130 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/ssl_check.py +127 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/stopwatch.py +102 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/sysmon/__init__.py +8 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/timer/__init__.py +8 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/timer_pro/__init__.py +9 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/timer_pro/timer_pro.py +233 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/timer_pro.py +83 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/todo_cli.py +178 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/treedir/__init__.py +10 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/treedir/__main__.py +8 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/uniq_tool/__init__.py +9 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/uniq_tool/uniq.py +134 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/urlparse_tool/__init__.py +11 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/urlparse_tool/cli.py +8 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/uuid_tool/__init__.py +8 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/weather_cli.py +53 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/web_summary/__init__.py +8 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/wordcount/__init__.py +10 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/wordcount/__main__.py +8 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/xml2json.py +91 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/yaml2json/__init__.py +9 -0
- evolver_tools-3.0.0/src/evolver_tools/vendor/yaml2json/yaml2json.py +311 -0
- evolver_tools-3.0.0/src/evolver_tools.egg-info/PKG-INFO +112 -0
- evolver_tools-3.0.0/src/evolver_tools.egg-info/SOURCES.txt +164 -0
- evolver_tools-3.0.0/src/evolver_tools.egg-info/entry_points.txt +2 -0
- evolver_tools-1.5.0/PKG-INFO +0 -107
- evolver_tools-1.5.0/README.md +0 -85
- evolver_tools-1.5.0/pyproject.toml +0 -72
- evolver_tools-1.5.0/src/evolver_tools/cli.py +0 -93
- evolver_tools-1.5.0/src/evolver_tools/vendor/b64/__init__.py +0 -2
- evolver_tools-1.5.0/src/evolver_tools/vendor/cal_tool/__init__.py +0 -1
- evolver_tools-1.5.0/src/evolver_tools/vendor/csv_stats/__init__.py +0 -5
- evolver_tools-1.5.0/src/evolver_tools/vendor/find_dups/__init__.py +0 -7
- evolver_tools-1.5.0/src/evolver_tools/vendor/ipinfo/__init__.py +0 -3
- evolver_tools-1.5.0/src/evolver_tools/vendor/json2csv/__init__.py +0 -3
- evolver_tools-1.5.0/src/evolver_tools/vendor/license_cli/__init__.py +0 -1
- evolver_tools-1.5.0/src/evolver_tools/vendor/portcheck/__init__.py +0 -2
- evolver_tools-1.5.0/src/evolver_tools/vendor/treedir/__init__.py +0 -2
- evolver_tools-1.5.0/src/evolver_tools/vendor/urlparse_tool/__init__.py +0 -3
- evolver_tools-1.5.0/src/evolver_tools/vendor/wordcount/__init__.py +0 -2
- evolver_tools-1.5.0/src/evolver_tools.egg-info/PKG-INFO +0 -107
- evolver_tools-1.5.0/src/evolver_tools.egg-info/SOURCES.txt +0 -80
- evolver_tools-1.5.0/src/evolver_tools.egg-info/entry_points.txt +0 -38
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/LICENSE +0 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/setup.cfg +0 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/__main__.py +0 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/b64/b64.py +0 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/chart_cli/__main__.py +0 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/colors/__main__.py +0 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/cron/__main__.py +0 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/csv_stats/__main__.py +0 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/csv_stats/analyzer.py +0 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/diff_tool/__main__.py +0 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/ff/__main__.py +0 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/hashsum/__main__.py +0 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/http_live/__main__.py +0 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/ipcalc/__main__.py +0 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/ipinfo/__main__.py +0 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/jq_lite/__main__.py +0 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/json2csv/__main__.py +0 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/jsonql/__main__.py +0 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/license_cli/__main__.py +0 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/nb/__main__.py +0 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/project_doctor/__main__.py +0 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/ren/__main__.py +0 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/siege_lite/__main__.py +0 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/smellfinder/__main__.py +0 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/sqlite_cli/__main__.py +0 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/sysmon/__main__.py +0 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/uuid_tool/__main__.py +0 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools/vendor/web_summary/__main__.py +0 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools.egg-info/dependency_links.txt +0 -0
- {evolver_tools-1.5.0 → evolver_tools-3.0.0}/src/evolver_tools.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: evolver-tools
|
|
3
|
+
Version: 3.0.0
|
|
4
|
+
Summary: 100 essential CLI tools - one pip install
|
|
5
|
+
Author: EVOLVER
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://evolver-dev.github.io/evolver-tools
|
|
8
|
+
Project-URL: Repository, https://github.com/evolver-dev/evolver-tools
|
|
9
|
+
Keywords: cli,devops,productivity,developer-tools,terminal
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: System Administrators
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Topic :: Utilities
|
|
18
|
+
Requires-Python: >=3.8
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# EVOLVER Tools
|
|
24
|
+
|
|
25
|
+
**50 essential CLI tools — one `pip install`.**
|
|
26
|
+
|
|
27
|
+
Zero-dependency (49/50), cross-platform, production-ready.
|
|
28
|
+
Systems ops, data processing, dev tools, security, and creativity.
|
|
29
|
+
~170KB total — one install, not 50.
|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install evolver-tools
|
|
35
|
+
evtool list # Show all 50 tools
|
|
36
|
+
evtool ff < data.txt # Fuzzy search through data
|
|
37
|
+
evtool sysmon # Launch system monitor
|
|
38
|
+
evtool sort -n data.txt # Numeric sort
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Tools
|
|
42
|
+
|
|
43
|
+
### Ops
|
|
44
|
+
|
|
45
|
+
| Tool | Description | Selling point |
|
|
46
|
+
|------|-------------|---------------|
|
|
47
|
+
| **sysmon** | Real-time system monitor (curses TUI — CPU/mem/disk/net/processes) | See your server breathe |
|
|
48
|
+
| **dirsize** | Recursive directory space analyzer | Find what's eating your disk |
|
|
49
|
+
| **envcheck** | Environment variable validator (missing keys, formats) | Stop .env typos from breaking prod |
|
|
50
|
+
| **portcheck** | TCP port scanner & service detection | Port open? Service running? |
|
|
51
|
+
| **siege-lite** | HTTP load tester (concurrency, latency percentile) | How many req/s can your app handle? |
|
|
52
|
+
| **http-live** | SSE hot-reload HTTP server for development | Edit code, see changes. No refresh. |
|
|
53
|
+
| **ipinfo** | Public IP & geolocation lookup | Where's this server? |
|
|
54
|
+
| **hashsum** | File hash verification (MD5/SHA-1/256/512/BLAKE2, auto-detect) | Verify downloads in one command |
|
|
55
|
+
| **find-dups** | Find duplicate files by SHA256 hash, size, or name | Reclaim gigabytes |
|
|
56
|
+
|
|
57
|
+
### Developer
|
|
58
|
+
|
|
59
|
+
| Tool | Description | Selling point |
|
|
60
|
+
|------|-------------|---------------|
|
|
61
|
+
| **smellfinder** | Python code smell detector (AST-based, 10+ patterns) | Lint without pip install pylint |
|
|
62
|
+
| **project-doctor** | Project health checker (meta, structure, quality) | Project checkup in one command |
|
|
63
|
+
| **license-cli** | Open-source license generator/validator | License your repo instantly |
|
|
64
|
+
| **markdown-check** | Markdown format validator & style checker | Docs that don't embarrass you |
|
|
65
|
+
| **sqlite-cli** | SQLite query tool — CSV/JSON/table output | Query .db files without a GUI |
|
|
66
|
+
| **b64** | Base64 encode/decode with auto-detection | Paste, pipe, done |
|
|
67
|
+
| **jsonql** | Zero-dep JSON query tool (SQL-like syntax) | `jsonql "SELECT name FROM data WHERE age > 18"` |
|
|
68
|
+
| **jq-lite** | jq-style JSON query — filter, extract, transform | jq without installing jq |
|
|
69
|
+
| **urlparse** | URL parser & debugger | What's in that URL? |
|
|
70
|
+
| **colors** | 256-color table & HEX↔RGB conversion | Design that terminal theme |
|
|
71
|
+
| **fmt** | Code/text formatter — trailing whitespace, EOF newline, indent | Clean files, one command |
|
|
72
|
+
|
|
73
|
+
### Data & Analysis
|
|
74
|
+
|
|
75
|
+
| Tool | Description | Selling point |
|
|
76
|
+
|------|-------------|---------------|
|
|
77
|
+
| **csv-stats** | CSV column analysis — histograms, frequencies, correlations | Understand your CSV in seconds |
|
|
78
|
+
| **json2csv** | JSON to CSV converter with nested key flattening | API response → spreadsheet |
|
|
79
|
+
| **chart-cli** | Terminal chart generator — bar, line, pie, histogram | Charts without leaving the terminal |
|
|
80
|
+
| **cal** | Calendar & date calculator | What day is 45 days from now? |
|
|
81
|
+
| **web-summary** | Web page content extractor (title, body, links) | Read the web from your terminal |
|
|
82
|
+
| **yaml2json** | Convert YAML to JSON (zero dependencies, basic YAML subset) | Config files \u2192 pipeable JSON |
|
|
83
|
+
| **sort** | Line sorting — alpha, numeric, reverse, unique, by column | Sort data without `sort(1)` |
|
|
84
|
+
|
|
85
|
+
### Productivity
|
|
86
|
+
|
|
87
|
+
| Tool | Description | Selling point |
|
|
88
|
+
|------|-------------|---------------|
|
|
89
|
+
| **ff** | Interactive fuzzy finder (fzf, pure Python curses TUI) | Search files, history, anything |
|
|
90
|
+
| **nb** | Command-line notebook (JSON storage, full-text search) | Notes in your terminal |
|
|
91
|
+
| **ren** | Batch file renamer (prefix/suffix/regex/numbering) | Rename 100 files in one command |
|
|
92
|
+
| **timer** | Countdown timer & stopwatch with desktop notifications | Pomodoro in your terminal |
|
|
93
|
+
| **treedir** | Directory tree visualizer with depth control | `tree` on every OS |
|
|
94
|
+
| **wordcount** | Enhanced word/char/line counter with language detection | wc on steroids |
|
|
95
|
+
| **dt** | Date/time format converter (timestamps, timezones) | `dt 1735689600` → human date |
|
|
96
|
+
|
|
97
|
+
### Security
|
|
98
|
+
|
|
99
|
+
| Tool | Description | Selling point |
|
|
100
|
+
|------|-------------|---------------|
|
|
101
|
+
| **passgen** | Password generator with entropy display & charset rules | Generate passwords that don't suck |
|
|
102
|
+
| **uuid** | UUID generator (v1/v3/v4/v5/v7) | v4, v7, any UUID in one command |
|
|
103
|
+
| **cron** | Cron expression parser & next-run calculator | "What does 0 2 * * 1 actually run?" |
|
|
104
|
+
|
|
105
|
+
## Requirements
|
|
106
|
+
|
|
107
|
+
- Python 3.8+
|
|
108
|
+
- No external dependencies (38 of 39 tools use stdlib only; ipinfo hits ip-api.com)
|
|
109
|
+
|
|
110
|
+
## License
|
|
111
|
+
|
|
112
|
+
MIT
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# EVOLVER Tools
|
|
2
|
+
|
|
3
|
+
**50 essential CLI tools — one `pip install`.**
|
|
4
|
+
|
|
5
|
+
Zero-dependency (49/50), cross-platform, production-ready.
|
|
6
|
+
Systems ops, data processing, dev tools, security, and creativity.
|
|
7
|
+
~170KB total — one install, not 50.
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install evolver-tools
|
|
13
|
+
evtool list # Show all 50 tools
|
|
14
|
+
evtool ff < data.txt # Fuzzy search through data
|
|
15
|
+
evtool sysmon # Launch system monitor
|
|
16
|
+
evtool sort -n data.txt # Numeric sort
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Tools
|
|
20
|
+
|
|
21
|
+
### Ops
|
|
22
|
+
|
|
23
|
+
| Tool | Description | Selling point |
|
|
24
|
+
|------|-------------|---------------|
|
|
25
|
+
| **sysmon** | Real-time system monitor (curses TUI — CPU/mem/disk/net/processes) | See your server breathe |
|
|
26
|
+
| **dirsize** | Recursive directory space analyzer | Find what's eating your disk |
|
|
27
|
+
| **envcheck** | Environment variable validator (missing keys, formats) | Stop .env typos from breaking prod |
|
|
28
|
+
| **portcheck** | TCP port scanner & service detection | Port open? Service running? |
|
|
29
|
+
| **siege-lite** | HTTP load tester (concurrency, latency percentile) | How many req/s can your app handle? |
|
|
30
|
+
| **http-live** | SSE hot-reload HTTP server for development | Edit code, see changes. No refresh. |
|
|
31
|
+
| **ipinfo** | Public IP & geolocation lookup | Where's this server? |
|
|
32
|
+
| **hashsum** | File hash verification (MD5/SHA-1/256/512/BLAKE2, auto-detect) | Verify downloads in one command |
|
|
33
|
+
| **find-dups** | Find duplicate files by SHA256 hash, size, or name | Reclaim gigabytes |
|
|
34
|
+
|
|
35
|
+
### Developer
|
|
36
|
+
|
|
37
|
+
| Tool | Description | Selling point |
|
|
38
|
+
|------|-------------|---------------|
|
|
39
|
+
| **smellfinder** | Python code smell detector (AST-based, 10+ patterns) | Lint without pip install pylint |
|
|
40
|
+
| **project-doctor** | Project health checker (meta, structure, quality) | Project checkup in one command |
|
|
41
|
+
| **license-cli** | Open-source license generator/validator | License your repo instantly |
|
|
42
|
+
| **markdown-check** | Markdown format validator & style checker | Docs that don't embarrass you |
|
|
43
|
+
| **sqlite-cli** | SQLite query tool — CSV/JSON/table output | Query .db files without a GUI |
|
|
44
|
+
| **b64** | Base64 encode/decode with auto-detection | Paste, pipe, done |
|
|
45
|
+
| **jsonql** | Zero-dep JSON query tool (SQL-like syntax) | `jsonql "SELECT name FROM data WHERE age > 18"` |
|
|
46
|
+
| **jq-lite** | jq-style JSON query — filter, extract, transform | jq without installing jq |
|
|
47
|
+
| **urlparse** | URL parser & debugger | What's in that URL? |
|
|
48
|
+
| **colors** | 256-color table & HEX↔RGB conversion | Design that terminal theme |
|
|
49
|
+
| **fmt** | Code/text formatter — trailing whitespace, EOF newline, indent | Clean files, one command |
|
|
50
|
+
|
|
51
|
+
### Data & Analysis
|
|
52
|
+
|
|
53
|
+
| Tool | Description | Selling point |
|
|
54
|
+
|------|-------------|---------------|
|
|
55
|
+
| **csv-stats** | CSV column analysis — histograms, frequencies, correlations | Understand your CSV in seconds |
|
|
56
|
+
| **json2csv** | JSON to CSV converter with nested key flattening | API response → spreadsheet |
|
|
57
|
+
| **chart-cli** | Terminal chart generator — bar, line, pie, histogram | Charts without leaving the terminal |
|
|
58
|
+
| **cal** | Calendar & date calculator | What day is 45 days from now? |
|
|
59
|
+
| **web-summary** | Web page content extractor (title, body, links) | Read the web from your terminal |
|
|
60
|
+
| **yaml2json** | Convert YAML to JSON (zero dependencies, basic YAML subset) | Config files \u2192 pipeable JSON |
|
|
61
|
+
| **sort** | Line sorting — alpha, numeric, reverse, unique, by column | Sort data without `sort(1)` |
|
|
62
|
+
|
|
63
|
+
### Productivity
|
|
64
|
+
|
|
65
|
+
| Tool | Description | Selling point |
|
|
66
|
+
|------|-------------|---------------|
|
|
67
|
+
| **ff** | Interactive fuzzy finder (fzf, pure Python curses TUI) | Search files, history, anything |
|
|
68
|
+
| **nb** | Command-line notebook (JSON storage, full-text search) | Notes in your terminal |
|
|
69
|
+
| **ren** | Batch file renamer (prefix/suffix/regex/numbering) | Rename 100 files in one command |
|
|
70
|
+
| **timer** | Countdown timer & stopwatch with desktop notifications | Pomodoro in your terminal |
|
|
71
|
+
| **treedir** | Directory tree visualizer with depth control | `tree` on every OS |
|
|
72
|
+
| **wordcount** | Enhanced word/char/line counter with language detection | wc on steroids |
|
|
73
|
+
| **dt** | Date/time format converter (timestamps, timezones) | `dt 1735689600` → human date |
|
|
74
|
+
|
|
75
|
+
### Security
|
|
76
|
+
|
|
77
|
+
| Tool | Description | Selling point |
|
|
78
|
+
|------|-------------|---------------|
|
|
79
|
+
| **passgen** | Password generator with entropy display & charset rules | Generate passwords that don't suck |
|
|
80
|
+
| **uuid** | UUID generator (v1/v3/v4/v5/v7) | v4, v7, any UUID in one command |
|
|
81
|
+
| **cron** | Cron expression parser & next-run calculator | "What does 0 2 * * 1 actually run?" |
|
|
82
|
+
|
|
83
|
+
## Requirements
|
|
84
|
+
|
|
85
|
+
- Python 3.8+
|
|
86
|
+
- No external dependencies (38 of 39 tools use stdlib only; ipinfo hits ip-api.com)
|
|
87
|
+
|
|
88
|
+
## License
|
|
89
|
+
|
|
90
|
+
MIT
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "evolver-tools"
|
|
7
|
+
version = "3.0.0"
|
|
8
|
+
description = "100 essential CLI tools - one pip install"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.8"
|
|
12
|
+
keywords = ["cli", "devops", "productivity", "developer-tools", "terminal"]
|
|
13
|
+
authors = [
|
|
14
|
+
{name = "EVOLVER"},
|
|
15
|
+
]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 5 - Production/Stable",
|
|
18
|
+
"Environment :: Console",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"Intended Audience :: System Administrators",
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
24
|
+
"Topic :: Utilities",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Homepage = "https://evolver-dev.github.io/evolver-tools"
|
|
29
|
+
Repository = "https://github.com/evolver-dev/evolver-tools"
|
|
30
|
+
|
|
31
|
+
[project.scripts]
|
|
32
|
+
evtool = "evolver_tools.cli:main"
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.packages.find]
|
|
35
|
+
where = ["src"]
|
|
36
|
+
include = ["evolver_tools*"]
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Auto-discovery module for EVOLVER tools.
|
|
3
|
+
|
|
4
|
+
Scans all vendor modules for TOOL_META exports and returns them
|
|
5
|
+
as a unified registry. Enables parallel agent workflows: each vendor
|
|
6
|
+
tool defines its own TOOL_META, eliminating merge conflicts on cli.py.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
from evolver_tools.autoreg import auto_discover
|
|
10
|
+
tools = auto_discover() # Returns dict[str, dict]
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import importlib
|
|
14
|
+
import os
|
|
15
|
+
import pkgutil
|
|
16
|
+
|
|
17
|
+
# Cache after first discovery
|
|
18
|
+
_TOOLS_CACHE = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _vendor_dir():
|
|
22
|
+
"""Return the absolute path to the vendor directory."""
|
|
23
|
+
# vendor is a namespace package (no __init__.py), so __file__ is None
|
|
24
|
+
# Walk up from autoreg.py location
|
|
25
|
+
here = os.path.dirname(os.path.abspath(__file__))
|
|
26
|
+
return os.path.join(here, "vendor")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def auto_discover():
|
|
30
|
+
"""Scan all vendor modules and return a unified TOOLS dict.
|
|
31
|
+
|
|
32
|
+
Each vendor module (file or package) that exports TOOL_META dict
|
|
33
|
+
is discovered automatically. No manual registration needed.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
dict[str, dict]: Tool name -> {module, func, desc}
|
|
37
|
+
"""
|
|
38
|
+
global _TOOLS_CACHE
|
|
39
|
+
if _TOOLS_CACHE is not None:
|
|
40
|
+
return _TOOLS_CACHE
|
|
41
|
+
|
|
42
|
+
tools = {}
|
|
43
|
+
vendor_dir = _vendor_dir()
|
|
44
|
+
|
|
45
|
+
for importer, modname, ispkg in pkgutil.iter_modules([vendor_dir]):
|
|
46
|
+
try:
|
|
47
|
+
mod = importlib.import_module(f"evolver_tools.vendor.{modname}")
|
|
48
|
+
except Exception:
|
|
49
|
+
continue
|
|
50
|
+
|
|
51
|
+
meta = getattr(mod, "TOOL_META", None)
|
|
52
|
+
if meta is None:
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
tool_name = meta.get("name")
|
|
56
|
+
if not tool_name:
|
|
57
|
+
continue
|
|
58
|
+
|
|
59
|
+
func_name = meta.get("func", "main")
|
|
60
|
+
desc = meta.get("desc", "")
|
|
61
|
+
|
|
62
|
+
# If the package has a submodule (e.g. cal_tool/cli.py), use that
|
|
63
|
+
submodule = meta.get("submodule", "")
|
|
64
|
+
if submodule:
|
|
65
|
+
module_path = f"evolver_tools.vendor.{modname}.{submodule}"
|
|
66
|
+
else:
|
|
67
|
+
module_path = f"evolver_tools.vendor.{modname}"
|
|
68
|
+
|
|
69
|
+
tools[tool_name] = {
|
|
70
|
+
"module": module_path,
|
|
71
|
+
"func": func_name,
|
|
72
|
+
"desc": desc,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
_TOOLS_CACHE = tools
|
|
76
|
+
return tools
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def list_tools():
|
|
80
|
+
"""Print all discovered tools to stdout."""
|
|
81
|
+
tools = auto_discover()
|
|
82
|
+
print(f"Discovered {len(tools)} tools:")
|
|
83
|
+
for name in sorted(tools.keys()):
|
|
84
|
+
print(f" - {name}")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
if __name__ == "__main__":
|
|
88
|
+
list_tools()
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""evolver CLI - Unified interface for all EVOLVER tools.
|
|
3
|
+
|
|
4
|
+
Tools are auto-discovered via TOOL_META in each vendor module.
|
|
5
|
+
No manual registration needed — just add TOOL_META to your vendor file.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import sys
|
|
9
|
+
import importlib
|
|
10
|
+
|
|
11
|
+
from evolver_tools.autoreg import auto_discover
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def list_tools():
|
|
15
|
+
"""Display all available tools."""
|
|
16
|
+
tools = auto_discover()
|
|
17
|
+
print(f'\x1b[1;36m===== EVOLVER Tools v3.0.0 =====\x1b[0m')
|
|
18
|
+
print()
|
|
19
|
+
for name, info in sorted(tools.items()):
|
|
20
|
+
print(f' \033[1;33m{name:<18}\033[0m {info["desc"]}')
|
|
21
|
+
print()
|
|
22
|
+
print(f' Total: {len(tools)} tools')
|
|
23
|
+
print()
|
|
24
|
+
print('Usage: evolver <toolname> [args...]')
|
|
25
|
+
print(' evolver list')
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def run_tool(tool_name, args):
|
|
29
|
+
tools = auto_discover()
|
|
30
|
+
if tool_name not in tools:
|
|
31
|
+
print(f'Unknown tool: {tool_name}')
|
|
32
|
+
sys.exit(1)
|
|
33
|
+
info = tools[tool_name]
|
|
34
|
+
mod_path = info["module"]
|
|
35
|
+
func_name = info["func"]
|
|
36
|
+
old_argv = sys.argv
|
|
37
|
+
sys.argv = [tool_name] + args
|
|
38
|
+
try:
|
|
39
|
+
mod = importlib.import_module(mod_path)
|
|
40
|
+
func = getattr(mod, func_name)
|
|
41
|
+
result = func()
|
|
42
|
+
if result is not None:
|
|
43
|
+
print(result)
|
|
44
|
+
except KeyboardInterrupt:
|
|
45
|
+
pass
|
|
46
|
+
except Exception as e:
|
|
47
|
+
print(f'Error running {tool_name}: {e}', file=sys.stderr)
|
|
48
|
+
sys.exit(1)
|
|
49
|
+
finally:
|
|
50
|
+
sys.argv = old_argv
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def main():
|
|
54
|
+
if len(sys.argv) < 2 or sys.argv[1] in ("-h", "--help"):
|
|
55
|
+
list_tools()
|
|
56
|
+
return
|
|
57
|
+
tool_name = sys.argv[1]
|
|
58
|
+
args = sys.argv[2:]
|
|
59
|
+
if tool_name == "list":
|
|
60
|
+
list_tools()
|
|
61
|
+
return
|
|
62
|
+
run_tool(tool_name, args)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
if __name__ == "__main__":
|
|
66
|
+
main()
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""api-tester — HTTP API testing tool with method, headers, body, JSON, and timing support."""
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
import os
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import time
|
|
9
|
+
import urllib.request
|
|
10
|
+
import urllib.error
|
|
11
|
+
import urllib.parse
|
|
12
|
+
import ssl
|
|
13
|
+
|
|
14
|
+
METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def build_request(method, url, headers=None, data=None, json_data=None, timeout=30):
|
|
18
|
+
"""Build and execute an HTTP request."""
|
|
19
|
+
if json_data is not None:
|
|
20
|
+
data = json.dumps(json_data).encode('utf-8')
|
|
21
|
+
if headers is None:
|
|
22
|
+
headers = {}
|
|
23
|
+
if 'Content-Type' not in {k.lower() for k in headers}:
|
|
24
|
+
headers['Content-Type'] = 'application/json'
|
|
25
|
+
elif data is not None and isinstance(data, str):
|
|
26
|
+
data = data.encode('utf-8')
|
|
27
|
+
|
|
28
|
+
req = urllib.request.Request(url, data=data, method=method.upper())
|
|
29
|
+
if headers:
|
|
30
|
+
for k, v in headers.items():
|
|
31
|
+
req.add_header(k, v)
|
|
32
|
+
|
|
33
|
+
ctx = ssl.create_default_context()
|
|
34
|
+
ctx.check_hostname = False
|
|
35
|
+
ctx.verify_mode = ssl.CERT_NONE
|
|
36
|
+
return req, ctx
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def do_request(method, url, headers=None, data=None, json_data=None, timeout=30):
|
|
40
|
+
"""Execute a single request and return result dict."""
|
|
41
|
+
req, ctx = build_request(method, url, headers, data, json_data, timeout)
|
|
42
|
+
start = time.time()
|
|
43
|
+
try:
|
|
44
|
+
resp = urllib.request.urlopen(req, context=ctx, timeout=timeout)
|
|
45
|
+
elapsed = time.time() - start
|
|
46
|
+
body = resp.read()
|
|
47
|
+
content_type = resp.headers.get('Content-Type', '')
|
|
48
|
+
if 'application/json' in content_type:
|
|
49
|
+
try:
|
|
50
|
+
body_parsed = json.loads(body)
|
|
51
|
+
body_str = json.dumps(body_parsed, indent=2)
|
|
52
|
+
except (json.JSONDecodeError, ValueError):
|
|
53
|
+
body_str = body.decode('utf-8', errors='replace')
|
|
54
|
+
else:
|
|
55
|
+
body_str = body.decode('utf-8', errors='replace')
|
|
56
|
+
return {
|
|
57
|
+
'status': resp.status,
|
|
58
|
+
'reason': resp.reason,
|
|
59
|
+
'headers': dict(resp.headers),
|
|
60
|
+
'body': body_str,
|
|
61
|
+
'elapsed': elapsed,
|
|
62
|
+
'error': None,
|
|
63
|
+
}
|
|
64
|
+
except urllib.error.HTTPError as e:
|
|
65
|
+
elapsed = time.time() - start
|
|
66
|
+
body = e.read()
|
|
67
|
+
body_str = body.decode('utf-8', errors='replace')
|
|
68
|
+
return {
|
|
69
|
+
'status': e.code,
|
|
70
|
+
'reason': e.reason,
|
|
71
|
+
'headers': dict(e.headers),
|
|
72
|
+
'body': body_str,
|
|
73
|
+
'elapsed': elapsed,
|
|
74
|
+
'error': None,
|
|
75
|
+
}
|
|
76
|
+
except urllib.error.URLError as e:
|
|
77
|
+
return {
|
|
78
|
+
'status': 0,
|
|
79
|
+
'reason': str(e.reason),
|
|
80
|
+
'headers': {},
|
|
81
|
+
'body': '',
|
|
82
|
+
'elapsed': 0,
|
|
83
|
+
'error': str(e.reason),
|
|
84
|
+
}
|
|
85
|
+
except Exception as e:
|
|
86
|
+
return {
|
|
87
|
+
'status': 0,
|
|
88
|
+
'reason': str(e),
|
|
89
|
+
'headers': {},
|
|
90
|
+
'body': '',
|
|
91
|
+
'elapsed': 0,
|
|
92
|
+
'error': str(e),
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def print_result(result, show_headers=True, show_body=True, verbose=False):
|
|
97
|
+
"""Print a formatted request result."""
|
|
98
|
+
elapsed = result['elapsed']
|
|
99
|
+
status = result['status']
|
|
100
|
+
|
|
101
|
+
if result['error']:
|
|
102
|
+
print(f"\033[91mERROR: {result['error']}\033[0m", file=sys.stderr)
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
if status >= 200 and status < 300:
|
|
106
|
+
color = '\033[92m'
|
|
107
|
+
elif status >= 300 and status < 400:
|
|
108
|
+
color = '\033[93m'
|
|
109
|
+
else:
|
|
110
|
+
color = '\033[91m'
|
|
111
|
+
print(f"{color}{status} {result['reason']}\033[0m (\033[94m{elapsed:.3f}s\033[0m)")
|
|
112
|
+
|
|
113
|
+
if show_headers and result['headers']:
|
|
114
|
+
print(f"\n\033[1mHeaders:\033[0m")
|
|
115
|
+
for k, v in result['headers'].items():
|
|
116
|
+
if verbose or k.lower() not in ('set-cookie', 'x-powered-by'):
|
|
117
|
+
print(f" \033[33m{k}\033[0m: {v}")
|
|
118
|
+
|
|
119
|
+
if show_body and result['body']:
|
|
120
|
+
print(f"\n\033[1mBody:\033[0m")
|
|
121
|
+
print(result['body'])
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def benchmark(method, url, count, headers=None, data=None, json_data=None, timeout=30):
|
|
125
|
+
"""Run multiple requests and show stats."""
|
|
126
|
+
times = []
|
|
127
|
+
statuses = {}
|
|
128
|
+
errors = 0
|
|
129
|
+
|
|
130
|
+
print(f"\033[1mBenchmarking: {method} {url}\033[0m")
|
|
131
|
+
print(f" Requests: {count}")
|
|
132
|
+
print()
|
|
133
|
+
|
|
134
|
+
for i in range(count):
|
|
135
|
+
result = do_request(method, url, headers, data, json_data, timeout)
|
|
136
|
+
times.append(result['elapsed'])
|
|
137
|
+
if result['error']:
|
|
138
|
+
errors += 1
|
|
139
|
+
else:
|
|
140
|
+
s = result['status']
|
|
141
|
+
statuses[s] = statuses.get(s, 0) + 1
|
|
142
|
+
|
|
143
|
+
elapsed_str = f"\033[94m{result['elapsed']:.3f}s\033[0m"
|
|
144
|
+
if result['error']:
|
|
145
|
+
print(f" [{i+1}/{count}] \033[91mERROR\033[0m - {result['error']} ({elapsed_str})")
|
|
146
|
+
else:
|
|
147
|
+
print(f" [{i+1}/{count}] {result['status']} ({elapsed_str})")
|
|
148
|
+
|
|
149
|
+
if times:
|
|
150
|
+
print(f"\n\033[1mSummary:\033[0m")
|
|
151
|
+
print(f" Total time: \033[94m{sum(times):.3f}s\033[0m")
|
|
152
|
+
print(f" Avg time: \033[94m{sum(times)/len(times):.3f}s\033[0m")
|
|
153
|
+
print(f" Min time: \033[94m{min(times):.3f}s\033[0m")
|
|
154
|
+
print(f" Max time: \033[94m{max(times):.3f}s\033[0m")
|
|
155
|
+
if len(times) > 1:
|
|
156
|
+
variance = sum((t - sum(times)/len(times))**2 for t in times) / len(times)
|
|
157
|
+
print(f" Std dev: \033[94m{variance**0.5:.3f}s\033[0m")
|
|
158
|
+
print(f" Errors: \033[91m{errors}\033[0m" if errors else f" Errors: 0")
|
|
159
|
+
if statuses:
|
|
160
|
+
status_str = ', '.join(f"{k}={v}" for k, v in sorted(statuses.items()))
|
|
161
|
+
print(f" Statuses: {status_str}")
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def main():
|
|
165
|
+
parser = argparse.ArgumentParser(
|
|
166
|
+
description='HTTP API testing tool. Send requests and inspect responses.',
|
|
167
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
168
|
+
epilog="""
|
|
169
|
+
Examples:
|
|
170
|
+
api-tester GET https://api.example.com/users
|
|
171
|
+
api-tester POST https://api.example.com/users -H "Content-Type: application/json" -d '{"name":"test"}'
|
|
172
|
+
api-tester --benchmark 5 GET https://example.com
|
|
173
|
+
api-tester PUT https://api.example.com/users/1 -j '{"name":"updated"}'
|
|
174
|
+
api-tester DELETE https://api.example.com/users/1
|
|
175
|
+
""",
|
|
176
|
+
)
|
|
177
|
+
parser.add_argument('method', nargs='?', choices=METHODS, help='HTTP method')
|
|
178
|
+
parser.add_argument('url', nargs='?', help='Target URL')
|
|
179
|
+
parser.add_argument('-H', '--header', action='append', dest='headers', default=[],
|
|
180
|
+
help='Request header (key:value, repeatable)')
|
|
181
|
+
parser.add_argument('-d', '--data', help='Request body data (string)')
|
|
182
|
+
parser.add_argument('-j', '--json', dest='json_data', help='JSON body (parsed)')
|
|
183
|
+
parser.add_argument('--benchmark', type=int, metavar='N', help='Run N requests for benchmarking')
|
|
184
|
+
parser.add_argument('--timeout', type=int, default=30, help='Request timeout in seconds')
|
|
185
|
+
parser.add_argument('--no-headers', action='store_true', help='Hide response headers')
|
|
186
|
+
parser.add_argument('--no-body', action='store_true', help='Hide response body')
|
|
187
|
+
parser.add_argument('--verbose', '-v', action='store_true', help='Show all headers including verbose ones')
|
|
188
|
+
|
|
189
|
+
args = parser.parse_args()
|
|
190
|
+
|
|
191
|
+
if not args.method or not args.url:
|
|
192
|
+
parser.print_help()
|
|
193
|
+
sys.exit(1)
|
|
194
|
+
|
|
195
|
+
headers = {}
|
|
196
|
+
for h in args.headers:
|
|
197
|
+
if ':' in h:
|
|
198
|
+
k, v = h.split(':', 1)
|
|
199
|
+
headers[k.strip()] = v.strip()
|
|
200
|
+
else:
|
|
201
|
+
print(f"Invalid header format: {h} (expected key:value)", file=sys.stderr)
|
|
202
|
+
sys.exit(1)
|
|
203
|
+
|
|
204
|
+
json_data = None
|
|
205
|
+
if args.json_data:
|
|
206
|
+
try:
|
|
207
|
+
json_data = json.loads(args.json_data)
|
|
208
|
+
except json.JSONDecodeError as e:
|
|
209
|
+
print(f"Invalid JSON data: {e}", file=sys.stderr)
|
|
210
|
+
sys.exit(1)
|
|
211
|
+
|
|
212
|
+
data = args.data
|
|
213
|
+
|
|
214
|
+
try:
|
|
215
|
+
if args.benchmark:
|
|
216
|
+
benchmark(args.method, args.url, args.benchmark, headers, data, json_data, args.timeout)
|
|
217
|
+
else:
|
|
218
|
+
result = do_request(args.method, args.url, headers, data, json_data, args.timeout)
|
|
219
|
+
print_result(result, show_headers=not args.no_headers, show_body=not args.no_body, verbose=args.verbose)
|
|
220
|
+
except KeyboardInterrupt:
|
|
221
|
+
print("\nAborted by user.", file=sys.stderr)
|
|
222
|
+
sys.exit(1)
|
|
223
|
+
except Exception as e:
|
|
224
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
225
|
+
sys.exit(1)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
# === Auto-registration metadata ===
|
|
230
|
+
TOOL_META = {
|
|
231
|
+
"name": "api-tester",
|
|
232
|
+
"func": "main",
|
|
233
|
+
"desc": 'HTTP API testing tool',
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if __name__ == '__main__':
|
|
237
|
+
main()
|