Metadata-Version: 2.4
Name: etool
Version: 2.2.0
Summary: All-in-one CLI toolbox for office automation: PDF, Word, Excel, images, QR codes, Markdown, LLM and more, with AI-agent-friendly JSON output
Author-email: Allen <jiangyangcreate@gmail.com>
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/jiangyangcreate/etool
Project-URL: Documentation, https://github.com/jiangyangcreate/etool#readme
Project-URL: Source, https://github.com/jiangyangcreate/etool
Project-URL: Issues, https://github.com/jiangyangcreate/etool/issues
Project-URL: Changelog, https://github.com/jiangyangcreate/etool/blob/main/CHANGELOG.md
Keywords: cli,toolbox,office-automation,pdf,docx,excel,image,qrcode,markdown,jupyter,llm,ai-agent,cross-platform
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: End Users/Desktop
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Office/Business
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Utilities
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: schedule>=1.1.0
Requires-Dist: speedtest-cli
Requires-Dist: pillow
Requires-Dist: pypdf
Requires-Dist: openpyxl
Requires-Dist: python-docx
Requires-Dist: qrcode
Requires-Dist: markdown
Requires-Dist: beautifulsoup4
Provides-Extra: pdf-images
Requires-Dist: pymupdf>=1.23.0; extra == "pdf-images"
Provides-Extra: qr-decode
Requires-Dist: opencv-python-headless; extra == "qr-decode"
Provides-Extra: all
Requires-Dist: pymupdf>=1.23.0; extra == "all"
Requires-Dist: opencv-python-headless; extra == "all"
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Dynamic: license-file

<div align="center">

# 🧰 etool

**One install. Dozens of everyday automation commands. Built for humans *and* AI agents.**

[![PyPI](https://img.shields.io/pypi/v/etool)](https://pypi.org/project/etool/)
[![Python](https://img.shields.io/pypi/pyversions/etool)](https://pypi.org/project/etool/)
[![Downloads](https://img.shields.io/pypi/dm/etool)](https://pypi.org/project/etool/)
[![CI](https://github.com/jiangyangcreate/etool/actions/workflows/python-app.yml/badge.svg)](https://github.com/jiangyangcreate/etool/actions/workflows/python-app.yml)
[![License](https://img.shields.io/pypi/l/etool)](https://github.com/jiangyangcreate/etool/blob/main/LICENSE)

English | [中文](https://github.com/jiangyangcreate/etool/blob/main/README_CN.md)

</div>

`etool` turns everyday office and developer chores into one-line shell commands (or Python calls): merge / split / encrypt PDFs, extract images from Word documents, convert Markdown to Word / HTML / Excel, batch-convert photos to WebP, generate and decode QR codes, merge Jupyter notebooks, test network / disk speed, chat with any OpenAI-compatible LLM, render cheat-sheet wallpapers, and more.

Add `--json` to any command and it prints exactly **one machine-readable JSON envelope** — which makes `etool` a drop-in tool layer for AI agents, scripts, and CI pipelines.

## Why etool?

- 🧩 **All-in-one** — PDF, Word, Excel, image, QR code, Markdown, Jupyter, web, LLM, and network utilities behind a single `etool` command. Every feature is also importable as a plain Python API.
- 🤖 **AI-agent friendly** — with `--json`, every command emits one envelope: `{"ok": true, "data": ...}` or `{"ok": false, "error": {code, message, details}}`, with stable machine-readable error codes. Ideal for function calling and automated pipelines.
- 🪶 **Light by default** — the default install is ~15 MB. Heavy binary dependencies (PyMuPDF, OpenCV) are opt-in extras: `etool[all]`.
- 🖥️ **Cross-platform** — one codebase for Windows, macOS, and Linux, CI-tested on all three. Python 3.10+.

## Install

```bash
pip install -U etool          # lightweight core (~15 MB) — covers almost everything
pip install -U "etool[all]"   # + PDF → PNG rasterization and QR decoding
```

| Extra | Enables | Adds |
|---|---|---|
| `etool[pdf-images]` | `etool pdf to-images` (PDF → PNG) | PyMuPDF |
| `etool[qr-decode]` | `etool qrcode decode` (offline QR recognition) | OpenCV (headless) |
| `etool[all]` | everything above | both |

Run it without installing anything (via [uv](https://docs.astral.sh/uv/)), or install it as an isolated CLI app:

```bash
uvx etool qrcode generate --text "https://example.com" --out qr.png
pipx install etool
```

After install, the `etool` entry point is on your `PATH`; `python -m etool ...` also works.

## 60-second tour

```bash
# Merge two PDFs
etool pdf merge --out merged.pdf part1.pdf part2.pdf

# Turn a Markdown note into a Word document
etool md to-docx notes.md --out notes.docx

# Batch-convert a folder of photos to lossless WebP
etool image rename-webp ./photos

# Generate a QR code
etool qrcode generate --text "https://example.com" --out qr.png

# Fetch a web page as clean, readable text
etool web fetch-text https://example.com

# Chat with any OpenAI-compatible model (stdlib HTTP, no SDK needed)
etool llm chat "Why is the sky blue?" --system "Answer in one sentence."
```

Need structured output for a script or an agent? Add `--json`:

```bash
$ etool --json web mask-ip 8.8.4.4
{
  "ok": true,
  "data": {
    "masked": "8.8.x.4",
    "is_public": true
  }
}
```

`etool` can even render a cheat-sheet wallpaper for any tool — this image was generated by `etool cheatsheet generate`:

![Git cheat sheet wallpaper generated by etool](https://raw.githubusercontent.com/jiangyangcreate/etool/main/docs/cheatsheet-git.png)

## What's inside

| Domain | Command | What you get |
|---|---|---|
| PDF | `etool pdf` | merge, split, encrypt / decrypt, watermark, insert, PDF → PNG |
| Word | `etool docx` | replace text, swap page orientation, extract embedded images |
| Excel | `etool excel` | copy workbook formatting to a new file |
| Images | `etool image` | stitch left-right / top-bottom, pad to square, 3×3 grid crop, batch → WebP |
| QR codes | `etool qrcode` | generate, decode offline |
| Markdown | `etool md` | → Word, → HTML, tables → Excel |
| Jupyter | `etool ipynb` | merge notebooks, notebook → Markdown |
| Web | `etool web` | page → readable text, RSS / Atom parsing, IP masking |
| LLM | `etool llm` | chat, summarize, outline via any OpenAI-compatible API |
| Cheat sheets | `etool cheatsheet` | render a command cheat-sheet wallpaper PNG |
| Speed | `etool speed` | network / disk / memory speed tests |
| Passwords | `etool password` | random passwords, arbitrary base conversion |
| Misc | `etool stdlib` / `install-reqs` / `scheduler` / `email` | stdlib usage analysis, bulk pip install, schedule parsing, SMTP mail |

## Command reference

With **`--json`**, stdout is one JSON document per invocation (pretty-printed, 2-space indent):

- Success: `{"ok": true, "data": { ... }}`
- Failure: `{"ok": false, "error": {"code", "message", "details"}}`

Without `--json`, output is human-readable (errors go to stderr). Below, **Input** is the command; **Output** shows typical **`--json`** stdout (values such as paths, passwords, and timings are illustrative).

<details>
<summary><b>Version</b> — <code>etool version</code></summary>

**Input**

```bash
etool --json version
```

**Output**

```json
{"ok": true, "data": {"version": "2.2.0"}}
```

</details>

<details>
<summary><b>PDF</b> — merge · split · encrypt · watermark · rasterize — <code>etool pdf</code></summary>

**Merge**

```bash
etool --json pdf merge --out merged.pdf part1.pdf part2.pdf
```

```json
{"ok": true, "data": {"merged": "merged.pdf", "log": "merged: part1.pdf\nmerged: part2.pdf\nmerged file saved as: merged.pdf"}}
```

**Split by page chunk size**

```bash
etool --json pdf split-pages --pages 3 document.pdf
```

```json
{"ok": true, "data": {"source": "document.pdf", "log": "generated: document_part_by_page1.pdf\n..."}}
```

**Split into N parts**

```bash
etool --json pdf split-num --parts 2 document.pdf
```

```json
{"ok": true, "data": {"source": "document.pdf", "log": "..."}}
```

**Encrypt / decrypt**

```bash
etool --json pdf encrypt --password secret doc.pdf --out doc_encrypted.pdf
etool --json pdf decrypt --password secret doc_encrypted.pdf --out doc_clear.pdf
```

```json
{"ok": true, "data": {"log": "encrypted file saved as: doc_encrypted.pdf"}}
```

**Insert another PDF after a page index**

```bash
etool --json pdf insert --pdf1 a.pdf --pdf2 b.pdf --after-page 0 --out out.pdf
```

```json
{"ok": true, "data": {"output": "out.pdf", "log": "inserted file saved as: out.pdf"}}
```

**Watermark**

```bash
etool --json pdf watermark --target folder_or_file.pdf --watermark wm.pdf --out-dir watermarked
```

```json
{"ok": true, "data": {"log": "..."}}
```

**PDF → PNG images** (requires `etool[pdf-images]`)

```bash
etool --json pdf to-images --input doc.pdf --out-dir png_out --dpi 2
```

```json
{"ok": true, "data": {"log": "found 1 PDF file(s)\n..."}}
```

</details>

<details>
<summary><b>Word</b> — replace text · swap orientation · extract images — <code>etool docx</code></summary>

**Replace text**

```bash
etool --json docx replace --path report.docx --old foo --new bar
```

```json
{"ok": true, "data": {"path": "report.docx"}}
```

**Swap page dimensions (landscape ↔ portrait style)**

```bash
etool --json docx swap-dimensions --input in.docx --output out.docx
```

```json
{"ok": true, "data": {"path": "out.docx"}}
```

**Extract embedded images**

```bash
etool --json docx extract-images --input in.docx --out-dir ./img_out
```

```json
{"ok": true, "data": {"path": "./img_out"}}
```

</details>

<details>
<summary><b>Excel</b> — copy template formatting — <code>etool excel</code></summary>

**Copy formatting from a template workbook**

```bash
etool --json excel copy-format --source template.xlsx --output out.xlsx
```

```json
{"ok": true, "data": {"path": "out.xlsx"}}
```

</details>

<details>
<summary><b>Images</b> — stitch · pad · grid crop · WebP — <code>etool image</code></summary>

**Merge left–right / top–bottom**

```bash
etool --json image merge-lr left.png right.png --out lr.png
etool --json image merge-ud top.png bottom.png --out ud.png
```

```json
{"ok": true, "data": {"path": "lr.png"}}
```

**Pad to square / 3×3 grid crop / batch rename to WebP**

```bash
etool --json image fill-square photo.jpg --out square.jpg
etool --json image cut-grid photo.jpg
etool --json image rename-webp ./shots --remove-original
```

```json
{"ok": true, "data": {"paths": ["photo_cut00.jpg", "..."]}}
```

</details>

<details>
<summary><b>QR codes</b> — generate · decode — <code>etool qrcode</code></summary>

**Generate**

```bash
etool --json qrcode generate --text "https://example.com" --out qr.png
```

```json
{"ok": true, "data": {"path": "qr.png"}}
```

**Decode (local OpenCV; requires `etool[qr-decode]`)**

```bash
etool --json qrcode decode qr.png
```

```json
{"ok": true, "data": {"text": "https://example.com"}}
```

</details>

<details>
<summary><b>Jupyter</b> — merge notebooks · to Markdown — <code>etool ipynb</code></summary>

**Merge all `.ipynb` in a directory**

```bash
etool --json ipynb merge-dir ./notebooks/
```

```json
{"ok": true, "data": {"path": "./notebooks.ipynb"}}
```

**Notebook → Markdown file**

```bash
etool --json ipynb to-markdown analysis.ipynb --out-dir ./md_out
```

```json
{"ok": true, "data": {"path": "analysis.md"}}
```

</details>

<details>
<summary><b>Markdown</b> — to Word · to HTML · tables to Excel — <code>etool md</code></summary>

```bash
etool --json md to-docx notes.md --out notes.docx
etool --json md to-html notes.md --out notes.html
etool --json md tables-to-xlsx tables.md --out tables.xlsx
```

```json
{"ok": true, "data": {"message": "Converted Markdown to Word document: notes.docx"}}
```

</details>

<details>
<summary><b>LLM</b> — chat · summarize · outline — <code>etool llm</code></summary>

Works with any OpenAI-compatible endpoint, using stdlib HTTP only (no SDK dependency). Credentials come from `--api-key` / `--base-url` / `--model`, or the `ETOOL_LLM_API_KEY` / `ETOOL_LLM_BASE_URL` / `ETOOL_LLM_MODEL` environment variables (standard `OPENAI_*` variables also work). Reasoning-model `<think>...</think>` blocks are stripped automatically.

**Chat**

```bash
etool --json llm chat "Why is the sky blue?" --system "Answer in one sentence."
```

```json
{"ok": true, "data": {"text": "Because air molecules scatter blue light more strongly than red."}}
```

**Summarize** (replies in the same language as the input; text inline or via `--file`)

```bash
etool --json llm summarize --file article.txt --min-words 50 --max-words 150
```

```json
{"ok": true, "data": {"summary": "..."}}
```

**Outline** (structure text into `main_title` / `sections` / `points` JSON)

```bash
etool --json llm outline --file article.txt
```

```json
{"ok": true, "data": {"outline": {"main_title": "...", "sections": [{"title": "...", "points": ["...", "..."]}]}}}
```

</details>

<details>
<summary><b>Web</b> — page to text · RSS / Atom · IP masking — <code>etool web</code></summary>

**Fetch a page as readable text** (drops script/style noise)

```bash
etool --json web fetch-text https://example.com
```

```json
{"ok": true, "data": {"text": "Example Domain\n..."}}
```

**Parse an RSS 2.0 / Atom feed** (URL, local XML file, or raw XML string)

```bash
etool --json web rss https://example.com/feed.xml --limit 2
```

```json
{"ok": true, "data": {"entries": [{"title": "...", "link": "...", "published": "...", "summary": "..."}]}}
```

**Mask an IP for display**

```bash
etool --json web mask-ip 8.8.4.4
```

```json
{"ok": true, "data": {"masked": "8.8.x.4", "is_public": true}}
```

</details>

<details>
<summary><b>Cheat-sheet wallpaper</b> — render a command cheat sheet PNG — <code>etool cheatsheet</code></summary>

Render a command cheat-sheet PNG (up to 3×3 category cards; by default the left quarter is kept clear for desktop icons — tune with `--left-margin-ratio`, 0 disables). Data comes from a JSON file (`--data`) or is generated by an LLM (`--keyword`, needs the LLM configuration above).

```bash
etool --json cheatsheet generate --keyword git --out git.png --width 1920 --height 1080
etool --json cheatsheet generate --data uv.json --title "UV Cheat Sheet" --out uv.png
```

`uv.json` shape:

```json
{"categories": [{"name": "Basics", "commands": [{"command": "uv sync", "description": "install deps"}]}]}
```

```json
{"ok": true, "data": {"path": "git.png"}}
```

</details>

<details>
<summary><b>Speed</b> — network · disk · memory — <code>etool speed</code></summary>

**Network** (uses speedtest-cli; needs internet, can be slow)

```bash
etool --json speed network
```

```json
{"ok": true, "data": {"report": "\n network test result:\ndownload speed: ... Mbps\n..."}}
```

**Disk**

```bash
etool --json speed disk --file-size-mb 10
```

```json
{"ok": true, "data": {"report": "\n disk test result:\nread speed: ... MB/s\nwrite speed: ... MB/s\n"}}
```

**Memory** (stdlib buffer test)

```bash
etool --json speed memory --size-mb 32
```

```json
{"ok": true, "data": {"report": "\n memory test result:\nread speed: ... MB/s\nwrite speed: ... MB/s"}}
```

</details>

<details>
<summary><b>Passwords</b> — random · base conversion — <code>etool password</code></summary>

**Random password**

```bash
etool --json password random --length 16
```

```json
{"ok": true, "data": {"password": "xYz9...16chars"}}
```

**Base conversion**

```bash
etool --json password convert-base --from-base 16 --to-base 2 A1F
```

```json
{"ok": true, "data": {"result": "101000011111"}}
```

</details>

<details>
<summary><b>Stdlib usage analysis</b> — <code>etool stdlib</code></summary>

One subcommand: `stdlib analyze DIR`. By default the envelope puts the nested counts under `data.result` (JSON object). With `--json-string`, the same analysis is returned as a single formatted JSON **text** under `data.json` (useful when you want one string field instead of nested JSON).

```bash
etool --json stdlib analyze ./src
etool --json stdlib analyze ./src --json-string
```

```json
{"ok": true, "data": {"result": {"os": {"path.join": 12, "listdir": 3}}}}
```

```json
{"ok": true, "data": {"json": "{\n  \"os\": {\n    \"path.join\": 12\n  }\n}"}}
```

</details>

<details>
<summary><b>Install requirements</b> — <code>etool install-reqs</code></summary>

Uses `python -m pip install` internally.

```bash
etool --json install-reqs --file requirements.txt --failed-file failed.txt --retry 2
```

```json
{"ok": true, "data": {"success": true}}
```

On failure:

```json
{"ok": false, "error": {"code": "RUNTIME_ERROR", "message": "some packages failed to install", "details": {}}}
```

</details>

<details>
<summary><b>Scheduler</b> — parse schedule expressions — <code>etool scheduler</code></summary>

```bash
etool --json scheduler parse 120
etool --json scheduler parse '"08:00"'
```

```json
{"ok": true, "data": {"log": "Execute every 120 seconds"}}
```

</details>

<details>
<summary><b>Email</b> — send via SMTP — <code>etool email</code></summary>

Do not paste real passwords into shell history; prefer environment-specific secrets in automation.

```bash
etool --json email send \
  --sender you@example.com \
  --password "$SMTP_PASSWORD" \
  --recipient other@example.com \
  --message "Hello" \
  --subject "Test"
```

```json
{"ok": true, "data": {"result": "send success"}}
```

</details>

## Use it from Python

Every CLI feature maps to a `Manager*` class with plain static methods:

```python
from etool import ManagerPdf, ManagerImage, ManagerQrcode, ManagerMd

ManagerPdf.merge_pdfs(["part1.pdf", "part2.pdf"], "merged.pdf")
ManagerImage.fill_image("photo.jpg")                      # pad to square
ManagerQrcode.generate_qrcode("https://example.com", "qr.png")
ManagerMd.convert_md_to_docx("notes.md", "notes.docx")
```

For structured envelopes in your own code:

```python
from etool import ok, err, EtoolError, ErrorCode

payload = ok({"path": "/tmp/out.pdf"})
failure = err(EtoolError(ErrorCode.VALIDATION_ERROR, "bad input", {"field": "x"}))
```

Missing optional dependencies never break the package: each manager is imported defensively, and `etool.get_import_status()` reports what is available.

## For AI agents

`etool --json <command>` is designed to be called by agents and scripts:

- stdout always carries exactly **one** JSON document (valid JSON, 2-space indent);
- `ok` is the single success flag to branch on;
- error codes are a stable contract:

| Code | Meaning |
|---|---|
| `VALIDATION_ERROR` | bad or missing input |
| `NOT_FOUND` | file or resource not found |
| `IO_ERROR` | read / write failure |
| `DEPENDENCY_ERROR` | optional dependency missing (`details.install` tells you what to install) |
| `RUNTIME_ERROR` | any other failure |

```json
{"ok": false, "error": {"code": "DEPENDENCY_ERROR", "message": "QR decoding requires OpenCV", "details": {"install": "pip install \"etool[qr-decode]\""}}}
```

## Development

With [uv](https://docs.astral.sh/uv/) (`uv.lock` is committed; the dev group includes the heavy optional deps so the full test suite runs):

```bash
uv sync
uv run pytest tests/test_etool.py -v
```

With pip:

```bash
pip install -e ".[all,dev]"
pytest tests/test_etool.py -v
```

See [CHANGELOG.md](https://github.com/jiangyangcreate/etool/blob/main/CHANGELOG.md) for release history (including the platform-specific features deliberately removed in 2.0 to keep etool fully cross-platform).

## Contributing

Issues and pull requests are welcome: [github.com/jiangyangcreate/etool](https://github.com/jiangyangcreate/etool). If etool saves you time, a ⭐ helps more people discover it.

[![Star History Chart](https://api.star-history.com/svg?repos=jiangyangcreate/etool&type=Date)](https://star-history.com/#jiangyangcreate/etool&Date)

## License

[Apache-2.0](https://github.com/jiangyangcreate/etool/blob/main/LICENSE)
