#  PET-P
**[中文](./readme_cn.md)** | English
[](./LICENSE.txt)
Python RPA toolkit with 80+ processors orchestrating browser automation, AI/LLM (11 providers), databases, SSH, email, and HTTP tasks. Configurable pipelines with cron scheduling and loops. Runs as wxPython GUI, headless service, or Docker container. Built-in MCP Tool Server (Streamable-HTTP) for AI agent integration.
```
Pipeline 1:n Execution
Execution 1:n Task
Task 1:1 Processor
```
**Links:** [Web Intro](https://petp.tail138025.ts.net/?lang=en) | [Web App](./webapp/README.md) | [Changelog](./CHANGELOG.md)
---
## Quick Start
### 1. Install Python 3.14
Download from [python.org](https://www.python.org/downloads/). On Windows, check "Add Python to PATH".
### 2. Install wxPython (GUI only)
```bash
pip install wxPython # 4.3.1+ supports Python 3.12–3.14
```
### 3. Install Dependencies
```bash
pip install -U uv
uv pip install -r requirements.txt # Full (GUI)
# or: uv pip install -r requirements-nogui.txt # Headless
# or: uv pip install -r requirements-docker.txt # Docker
```
Custom install — pick only what you need
```bash
uv pip install -r requirements/core.txt -r requirements/ssh-sftp.txt -r requirements/http-client.txt
```
See `requirements/` directory for all available groups: `ai-deepseek.txt`, `ai-gemini.txt`, `ai-ollama.txt`, `database.txt`, `excel-data.txt`, `mcp.txt`, `ocr.txt`, `web-automation.txt`, etc.
### 4. Run
```bash
python PETP.py # GUI
python PETP_background.py # Headless service (port 8866)
```
---
## Screenshots
### macOS

### Windows

**MCP Tool Server — integrate with Claude Code, Cursor, and other AI agents:**

---
## Features
| Category | Capabilities |
|----------|-------------|
| **Browser Automation** (Selenium) | Navigate, click, key-in, collect, batch find, iFrame, cookies, screenshot. SAP Ariba Angular form controls: yes/no radio, cascading tree picker, single- & multi-select dropdowns, Material date picker. Chrome DevTools Recorder import. |
| **SSH / SFTP** (Paramiko) | SSH/SFTP sessions, remote commands, file upload/download. |
| **File & Folder** | Open, write, delete, read, find, watch & auto-move, ZIP/UNZIP. |
| **Data & Spreadsheet** | CSV/Excel read & write, collect, filter, group-by, mapping, masking, merge. Chinese almanac (CNLunar). |
| **Database** | MySQL, PostgreSQL, SAP HANA, SQLite — unified `DB_ACCESS` processor. |
| **AI / LLM** (11 providers) | DeepSeek, Gemini, Ollama, Zhipu, Anthropic, Hyperspace, Qianfan, MiniMax, Doubao, Moonshot, OpenAI-compatible. Setup + Q&A + MCP tool calling. |
| **AI Execution Generator** | Natural language → task flow generation. Multi-turn chat, Processor browser, selective context, connection caching. |
| **MCP** | Standard MCP Tool Server (Streamable-HTTP). MCP client for all LLM providers. OOTB tools: weather query, daily almanac. |
| **HTTP / Network** | Configurable requests, response extraction, OAuth2/PKCE, Basic Auth, XSRF. |
| **Email** | SMTP send (CC/BCC, HTML, attachments). IMAP receive (filter, attachment download). |
| **OCR & Captcha** | Image text extraction (paddleocr/rapidocr/easyocr). Captcha solving (ddddocr). |
| **Mouse & GUI** (PyAutoGUI) | Click, scroll, position query. |
| **Execution Control** | Init params, nested execution, conditional stop/jump, `IF_ELSE` branching, loops, shell commands. |
| **Portable Runtime** | Self-contained, lightweight engine split out of PETP — runs a single Execution headless (incl. Selenium), decoupled from GUI/HTTP. Ideal for RPA: author & test in the GUI, run lightweight in `portable/`. `cp -r` into any Python project or `cf push` to Cloud Foundry; GUI-authored YAML runs unchanged. |
| **Theme** | 9 themes (System auto + 8 named) with live switching. |
---
## 🤖 AI Execution Generator
> **Highlight** — Generate and modify PETP task flows through natural language conversation with LLM. Supports [11 LLM providers](./docs/configuration.md#ai-assistant-configuration) including [Hyperspace](./hyperspace_llm_guide.md), Anthropic, DeepSeek, Zhipu, Gemini, Ollama, and more.
**Entry Points:**
- Create Execution → **"AI Generate"** template
- Right-click taskGrid → **"AI Assist"** (modify existing) — pre-selects only the processors used in the current Execution to minimize token usage
- MCP Editor → **"AI"** button (auto-generate tool description)
**Highlights:**
- **Multi-turn chat** — ask questions, generate flows, modify tasks incrementally
- **Processor browser** — expandable TreeListCtrl with full documentation, search & filter
- **Selective context** — only checked Processors are sent to LLM (saves tokens)
- **Connection caching** — first-time validation, then instant reuse across sessions
- **11 LLM providers** — minimal config: just set `ai_provider` in petpconfig.yaml
- **429 rate-limit defense** — exponential backoff (2s / 4s / 8s), UI throttle ≥ 3s, per-call token accounting log
**Token controls** (new):
- `ai_max_response_tokens` — hard upper bound on model output length (default 8192)
- `ai_max_request_tokens` — local pre-flight cap on request size (default 60000); over-limit prompts are rejected before hitting the provider
**AI-Powered MCP Tool Publishing:**
- One-click generation of `mcp_desc` JSON for exposing Executions as MCP tools
- Auto-extracts input parameters from `INITIAL_PARAMS` and output keys from result tasks
- Generates AI-agent-friendly descriptions that help LLMs understand when to call the tool
- Smart merge — new fields are added without overwriting existing configuration
- Progress dialog with live status and preview before applying
**AI Error Analysis & Auto-Fix:**
- On execution failure, AI automatically analyzes the error with full context (failed task, surrounding tasks, traceback)
- Pinpoints root cause and suggests specific fixes
- One-click **"Open AI Assist"** pre-fills the diagnosis — continue fixing in multi-turn chat
**Vision Model Support (Ollama):**
- `AI_LLM_QANDA` accepts `image_path` parameter for multimodal prompts
- Works with Ollama vision models (gemma4, llava, moondream, etc.)
- Image path supports expressions — dynamically reference files from previous tasks in `data_chain`
**Configuration** (only `ai_provider` required, rest auto-fills from provider defaults):
```yaml
application:
ai_provider: zhipu # or: deepseek, anthropic, hyperspace, gemini, ollama, etc.
ai_model: "" # empty = provider default (e.g. GLM-5)
ai_api_key: "" # empty = read from default env var (e.g. ZHIPU_ACCESS_KEY)
ai_base_url: "" # empty = provider default URL
ai_max_response_tokens: 8192
ai_max_request_tokens: 60000
```
See [Configuration Docs](./docs/configuration.md#ai-assistant-configuration) for full provider list and details.
---
## Dynamic Function (`_fn`) & Expression Enhancements
All dynamic function parameters (`_fn`, `_func`, `_func_body`, `lambda_*`) receive the Processor instance as `p`, enabling full access to PETP utilities inside custom code:
```python
# In _fn function body — p is the Processor instance
result = p.get_data("my_key")
p.populate_data("output", processed_value)
today = p.str_to_date("2026-05-11")
```
**Available `p` methods in both expression and _fn contexts:**
| Method | Description |
|--------|-------------|
| `p.get_data(key)` | Read from data_chain |
| `p.get_deep_data([keys])` | Nested data access |
| `p.get_data_chain()` | Get entire data_chain dict |
| `p.populate_data(k, v)` | Write to data_chain |
| `p.get_now_str()` | Current timestamp (YYYYMMDDHHmmss) |
| `p.get_now_in_str(fmt)` | Current time with custom format |
| `p.str_to_date(s, fmt)` | Parse date string to date object |
| `p.get_rdir()` / `p.get_ddir()` / `p.get_tdir()` | Resource/Download/Test directories |
| `p.expression2str(s)` | Evaluate f-string expression |
| `p.str2dict(s)` / `p.json2dict(s)` | String parsing utilities |
**Edit Complex Value — Handy Tool button:**
- Right-click any property → "Edit Complex Value" includes a Handy Tool button
- Automatically detects whether the parameter is an expression or `_fn` function body
- Expression context: snippets wrapped in `{p.xxx()}` for f-string evaluation
- Function body context: raw `p.xxx()` calls plus `import` statements
---
## Running Modes
| Mode | Command | GUI | Use Case |
|------|---------|-----|----------|
| Desktop | `python PETP.py` | Yes | Interactive development |
| Background | `python PETP_background.py` | No | CLI, HTTP/MCP service |
| Docker | `docker run -p 8866:8866 petp` | No | Server deployment |
| Portable | `python portable/petp_run.py NAME` | No | Copy into another project / Cloud Foundry |
```bash
# Run one execution and exit
python PETP_background.py --run-execution ENDECODER --no-http
# Run execution with initial data
python PETP_background.py --run-execution MY_EXEC --init-data '{"key":"value"}' --no-http
# Run pipeline and exit
python PETP_background.py --run-pipeline DAILY_REPORT --no-http
# Run pipeline with initial data
python PETP_background.py --run-pipeline MY_PIPELINE --init-data '{"param":"value"}' --no-http
# Start HTTP/MCP service (default port 8866)
python PETP_background.py
# Custom port and auth token
python PETP_background.py --http-port 9090 --http-token my-secret-token
# Headless Selenium (Chrome without visible window)
python PETP_background.py --run-execution BROWSER_TASK --headless --no-http
# Override log level
python PETP_background.py --log-level DEBUG
# GUI-processor policy: skip (ignore) or abort (fail on GUI tasks)
python PETP_background.py --run-execution HAS_GUI_TASK --ui-policy skip --no-http
python PETP_background.py --run-execution HAS_GUI_TASK --ui-policy abort --no-http
# Stop a running background instance
python PETP_background.py --stop
```
All CLI arguments
| Argument | Default | Description |
|----------|---------|-------------|
| `--run-execution NAME` | — | Run execution on startup, then continue to HTTP or exit |
| `--run-pipeline NAME` | — | Run pipeline on startup (rejects if already running) |
| `--init-data JSON` | `{}` | Inject JSON object into `data_chain` before run |
| `--no-http` | off | Exit after immediate job finishes (no HTTP server) |
| `--headless` | off | Headless Selenium browser (auto-enabled in Docker) |
| `--stop` | — | Stop running background instance (by PID) |
| `--http-port PORT` | `8866` | HTTP/MCP service port |
| `--http-token TOKEN` | from config | Bearer token for HTTP API auth |
| `--ui-policy {skip,abort}` | `skip` | `skip`: silently skip GUI-only tasks; `abort`: fail execution |
| `--log-level LEVEL` | from config | `DEBUG`, `INFO`, `WARNING`, `ERROR` |
| `--nogui-enabled {true,false}` | `true` | Set to `false` to disable background mode (exits immediately) |
**Notes:**
- `--run-execution` and `--run-pipeline` can be combined — both will run in sequence
- Pipeline reentrant protection: if the same pipeline is already running, a second call returns `{"ok": false, "error": "Pipeline already running"}`
- Cron-enabled pipelines (`cronEnabled: true` in YAML) auto-register as scheduled jobs instead of running immediately
Helper scripts for long-running sessions: see `scripts/macos/start_petp.sh` and `scripts/windows/start_petp.ps1`.
### Portable Runtime
`portable/` is a self-contained unit that runs a single Execution headlessly, decoupled from the GUI and HTTP layers. Copy `portable/` into any Python project, or `cf push` it to Cloud Foundry.
```python
from portable.petp_run import run
result = run("MY_EXECUTION", {"key": "value"}) # {"ok", "data", "error", "meta"}
```
Workflow: author the Execution in the desktop GUI → `Ctrl+S` → copy the YAML into `portable/core/executions/` → `python portable/petp_run.py NAME`. The engine and YAML format are shared, so GUI-authored Executions run unchanged.
Chrome binaries (`chrome-headless-shell` + `chromedriver`, from [Chrome for Testing](https://googlechromelabs.github.io/chrome-for-testing/#stable)) are placed under `portable/webdriver//` and are not committed to git. On Cloud Foundry, `manifest.yml` + `apt.yml` (apt-buildpack) supply the required system `.so` libraries. See [`portable/README.md`](./portable/README.md) for full details.
---
## MCP & AI Integration
PETP exposes executions as MCP tools via Streamable-HTTP on port 8866.
> **🔒 Auth is fail-closed.** When `http_request_token` is unset in `petpconfig.yaml`, every protected endpoint (`/petp/*`, `/mcp`) returns `501 Not Configured`. Set a token before exposing the server (e.g. via Tailscale Funnel). Send it as `Authorization: Bearer ` on every request.
> **🔒 Token from the environment.** `http_request_token` supports `${ENV_VAR}` references — set `http_request_token: ${PETP_HTTP_TOKEN}` and export `PETP_HTTP_TOKEN` to keep the real token out of the YAML. Generate a strong token with `python tools/gen_token.py` (32 bytes, URL-safe). `ai_api_key` uses the same `${ENV}` mechanism (`utils/SecretUtil.py`).
> **🔒 OAuth2 / JWT (resource server).** Set `auth_mode: oauth2` to validate inbound `Authorization: Bearer` JWTs locally against a JWKS endpoint instead of the static token — no IdP SDK required:
> ```yaml
> application:
> auth_mode: oauth2
> oauth2_jwks_url: https://idp.example.com/.well-known/jwks.json
> oauth2_audience: petp # optional aud check
> oauth2_issuer: https://idp.example.com # optional iss check
> ```
> With `auth_mode: oauth2` and a valid `oauth2_jwks_url`, the static `http_request_token` is ignored. `auth_mode: static` (default) keeps the existing token check. See `httpservice/auth/OAuth2JwtProvider.py`.
> **🔒 Security hardening (Phase 2).**
> - **`CMD` processor**: defaults to `shlex.split` (no shell). Set `shell="yes"` only for trusted commands needing pipes/redirects.
> - **Dynamic `_fn` / `lambda_*` parameters**: run in a sandbox with `__import__`, `open`, `eval`, `exec`, `compile`, `getattr`, `hasattr` removed. Whitelisted modules: `re`, `json`, `datetime`, `math`.
> - **Encrypted password salt**: override the public default by setting env `PETP_SALT` or writing `~/.petp/secret` (POSIX mode `0600`). The default salt is logged with a WARNING — `cryptocode` ciphertext is not actually secret unless you set a custom salt.
> - **Path traversal guard** (opt-in): set `PETP_PATH_ALLOW_ROOTS=/path1:/path2` to confine all file IO processors (`READ_*`, `WRITE_*`, `OPEN_FILE`, `FILE_DELETE`, `UNZIP`) to a whitelist of root directories. Default off — preserves existing yaml using absolute paths.
> - **Request size limits**: HTTP body capped at 4 MiB (`PETP_MAX_BODY_BYTES`); JSON-RPC batch arrays capped at 64 items (`PETP_MAX_BATCH_ITEMS`). Both return `413` / `400` with no body parsing.
> - **Log redaction** (default on): values of sensitive keys (`api_key`, `password`, `token`, `authorization`, `secret`, ...) are masked as `***REDACTED***` in `process start` / `[Type] input` log lines. Disable with `PETP_LOG_REDACT=off` for ad-hoc debugging.
> - **Safe YAML deserialization**: execution/pipeline YAML is loaded via `PETPSafeLoader` (built on `SafeLoader`), which registers constructors for **only** the four project classes — `Execution`, `Task`, `Pipeline`, `Loop`. `yaml.Loader` / `UnsafeLoader` / `FullLoader` are never used, so `!!python/object/apply:...` tags cannot execute arbitrary code at load time — any unregistered tag raises `ConstructorError`. The `!!python/object:...` tags you see in the YAML are cosmetic, not an open `pickle`-style loader.
**Performance (headless/Docker):**
- Shared thread pool for concurrent tool calls (no per-request executor overhead)
- Static execution cache — zero filesystem I/O after startup (no stat/mtime checks)
- Processor class pre-loading on server start (eliminates cold-start latency)
- Real-time task-level SSE progress notifications during long-running `tools/call`
- Cached outputSchema parsing (same tool re-called without re-parsing mcp_desc JSON)
**Built-in MCP Tools:**
| Tool | Description | Input |
|------|-------------|-------|
| `T_WEATHER_QUERY` | Query real-time weather (wttr.in) | `city` (e.g. "上海", "Tokyo") |
| `T_DAILY_ALMANAC` | Chinese almanac + holiday info (cnlunar + holiday-cn) | none (uses today) |
**Claude Code / Cursor / any MCP client:**
```json
{
"mcpServers": {
"petp": {
"type": "http",
"url": "http://localhost:8866/mcp"
}
}
}
```
**HTTP API (no MCP client needed):**
```bash
# List tools
curl -H "Authorization: Bearer $TOKEN" http://localhost:8866/petp/tools
# Trigger execution
curl -X POST http://localhost:8866/petp/exec \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"action":"execution","params":{"execution":"MY_EXEC"},"wait_for_result":"true"}'
# Trigger pipeline (sync)
curl -X POST http://localhost:8866/petp/exec \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"action":"pipeline","params":{"pipeline":"MY_PIPELINE"},"wait_for_result":"true"}'
# Trigger pipeline (async — poll with /petp/result)
curl -X POST http://localhost:8866/petp/exec \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"action":"pipeline","params":{"pipeline":"MY_PIPELINE"},"wait_for_result":"false"}'
```
| Endpoint | Description |
|----------|-------------|
| `GET /health` | Health check |
| `GET,POST /mcp` | MCP Tool Server (Streamable-HTTP) |
| `GET /petp/tools` | List exposed tools |
| `POST /petp/exec` | Trigger execution or pipeline |
| `GET /petp/result?request_id=` | Poll async result |
---
## Build & Docker
```bash
# Standalone executable
python build/PETP_build.py # → dist/PETP.app or dist/PETP.exe
# Docker — Background service (headless, port 8866)
./build/script/docker_build_bg.sh # build + export tar
./build/script/docker_build_bg.sh --run # build + start container
./build/script/docker_build_bg.sh --no-tar # build only
./build/script/docker_build_bg.sh --push repo:tag # push to registry
./build/script/docker_build_bg.sh --dirty # use working dir (skip git archive)
# Docker — Web App (port 5555)
./build/script/docker_build_webapp.sh # build + export tar
./build/script/docker_build_webapp.sh --run # build + start container
./build/script/docker_build_webapp.sh --dirty # use working dir (skip git archive)
```
> **Note:** Build scripts default to `git archive` mode — only git-tracked and staged files are included in the Docker context. Use `--dirty` to include all working directory files (relies on `.dockerignore`).
### Deploy to NAS
```bash
# Deploy BG image (scp + docker load + start container)
./build/script/deploy_bg_to_nas.sh
./build/script/deploy_bg_to_nas.sh --no-start # only transfer + load
./build/script/deploy_bg_to_nas.sh --keep-tar # don't delete remote tar
# Deploy Webapp image
./build/script/deploy_webapp_to_nas.sh
# Override NAS connection / port mapping
NAS_HOST=10.0.0.5 NAS_USER=myuser HOST_PORT=9090 ./build/script/deploy_webapp_to_nas.sh
```
| Environment Variable | Default | Description |
|---|---|---|
| `NAS_HOST` | `192.168.1.100` | NAS IP or hostname |
| `NAS_USER` | `admin` | SSH username |
| `NAS_PORT` | `22` | SSH port |
| `NAS_DOCKER_DIR` | `/tmp` | Remote temp dir for tar |
| `HOST_PORT` | `8866` (BG) / `8088` (Webapp) | Host port mapping |
### Tailscale Funnel (run on NAS)
```bash
# Refresh funnel routes (reset + reconfigure)
sudo ./build/script/tailscale_funnel_refresh.sh
# Check current status only
./build/script/tailscale_funnel_refresh.sh --status
```
Routes configured:
- `/` → `localhost:8088` (webapp)
- `/mcp` → `localhost:8866` (petp background)
---
## Project Structure
| Directory | Description |
|-----------|-------------|
| `core/executions/` | YAML execution definitions |
| `core/processors/` | 80+ processor implementations (one `.py` per task type) |
| `core/pipelines/` | YAML pipeline definitions |
| `core/runtime/` | Background runtime logic |
| `httpservice/` | HTTP server & MCP handler |
| `mvp/` | GUI layer (Model-View-Presenter, wxPython) |
| `config/` | Runtime configuration (`petpconfig.yaml`) |
| `docs/` | Detailed guides ([EN](docs/) / [中文](docs/screenshots_cn.md)) |
| `webapp/` | Flask web app ([docs](./webapp/README.md)) |
| `scripts/` | macOS & Windows launcher scripts |
| `requirements/` | Modular dependency groups |
| `build/` | PyInstaller & Docker build scripts |
| `tools/` | Maintenance scripts (migration, sync) |
| `testcoverage/` | Tests and smoke scripts (incl. `petp_http_endpoints.http` for HTTP/MCP endpoint testing) |
---
## Troubleshooting
| Problem | Solution |
|---------|----------|
| `ModuleNotFoundError` | Install the corresponding `requirements/*.txt` group |
| wxPython import error | Ensure the version matches your Python version (`pip install wxPython`) |
| ChromeDriver mismatch | Download from [Chrome for Testing](https://googlechromelabs.github.io/chrome-for-testing/) |
| Port 8866 in use | Change `http_port` in `config/petpconfig.yaml` |
---
## Documentation
| Guide | Description |
|-------|-------------|
| [Screenshots & Visual Tour](docs/screenshots.md) | All platform screenshots with context |
| [Running Modes & Scripts](docs/running-modes.md) | Background mode, Docker, helper scripts, env vars |
| [Dependencies](docs/dependencies.md) | Modular groups, custom install, lock files |
| [Configuration Reference](docs/configuration.md) | Config keys, CLI arguments, runtime result format |
| [MCP & HTTP API](docs/mcp-integration.md) | Full API reference, MCP inspector, tool schema |
| [Pipeline & Cron](docs/pipeline-cron.md) | Cron scheduling, YAML format, examples |
| [Parameter Migration](docs/migration.md) | Rename guide for pre-2026-05 users |
---
## Acknowledgements
- [wxPython](https://www.wxpython.org/) & [wxGlade](https://wxglade.sourceforge.net/)
- [Selenium](https://selenium-python.readthedocs.io/) & [ChromeDriver](https://googlechromelabs.github.io/chrome-for-testing/)
---
[Full Changelog →](./CHANGELOG.md)