Metadata-Version: 2.5
Name: ptn
Version: 1.1.1
Summary: Web-based terminal accessible from phone via Cloudflare Tunnel
Project-URL: Homepage, https://github.com/lyehe/porterminal
Project-URL: Repository, https://github.com/lyehe/porterminal
Project-URL: Documentation, https://github.com/lyehe/porterminal/tree/master/docs
Project-URL: Issues, https://github.com/lyehe/porterminal/issues
Author: Porterminal Contributors
Maintainer: Porterminal Contributors
License: AGPL-3.0-or-later
License-File: LICENSE
Keywords: cloudflare,mobile,pty,terminal,tunnel,web,websocket,xterm
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Web Environment
Classifier: Framework :: FastAPI
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: System :: Shells
Classifier: Topic :: Terminals :: Terminal Emulators/X Terminals
Requires-Python: >=3.12
Requires-Dist: bcrypt>=4.0.0
Requires-Dist: cryptography>=50.0.0
Requires-Dist: fastapi>=0.141.1
Requires-Dist: mcp<3,>=2.0.0
Requires-Dist: pydantic>=2.0
Requires-Dist: pyte>=0.8.2
Requires-Dist: pywinpty>=2.0.15; sys_platform == 'win32'
Requires-Dist: pyyaml>=6.0.2
Requires-Dist: qrcode>=7.4
Requires-Dist: rich>=13.0
Requires-Dist: starlette>=1.3.1
Requires-Dist: tyro>=0.9.0
Requires-Dist: uvicorn[standard]>=0.24.0
Provides-Extra: dev
Requires-Dist: watchfiles>=0.21.0; extra == 'dev'
Description-Content-Type: text/markdown

<p align="center">
  <a href="https://github.com/lyehe/porterminal">
    <img src="assets/banner.jpg" alt="Porterminal - Vibe Code From Anywhere" width="600">
  </a>
</p>

<p align="center">
  <a href="https://pypi.org/project/ptn/"><img src="https://img.shields.io/pypi/v/ptn?style=flat-square&logo=pypi&logoColor=white&label=PyPI" alt="PyPI"></a>
  <a href="https://pypi.org/project/ptn/"><img src="https://img.shields.io/pypi/pyversions/ptn?style=flat-square&logo=python&logoColor=white" alt="Python"></a>
  <a href="https://pypi.org/project/ptn/"><img src="https://img.shields.io/pypi/dm/ptn?style=flat-square&label=Downloads" alt="Downloads"></a>
  <a href="https://github.com/lyehe/porterminal/blob/master/LICENSE"><img src="https://img.shields.io/github/license/lyehe/porterminal?style=flat-square" alt="License"></a>
  <a href="https://github.com/lyehe/porterminal/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/lyehe/porterminal/ci.yml?branch=master&style=flat-square&logo=github&label=CI" alt="CI"></a>
</p>



<p align="center">
  <b>Hand a computer to an agent, full control, and watch it.</b><br>
  One command, one URL. (Also a slick terminal for your own phone.)
</p>

<p align="center">
  <b>1.</b> <code>uvx ptn</code><br>
  <b>2.</b> Hand the URL to an AI agent, or scan the QR yourself<br>
  <b>3.</b> Watch it work in any browser, and take over anytime<br>
</p>

<p align="center">
  <img src="assets/demo.gif" alt="Porterminal demo" width="320">
</p>

> [!WARNING]
> **That complete URL is full access to this computer.** It contains a random per-launch access code, and anyone (or any AI agent) you hand it to gets a real shell on your machine. Treat the URL and QR code like a secret, only share them with people and agents you trust, and read [Security](#security) before you point Porterminal at anything important.

## Why

I need something dangerously easy to remote access a computer.

**ngrok** requires registration and the free tier sucks. **Cloudflare Tunnel** is excellent plumbing, but by itself it only gives you a tunnel, not a phone-friendly terminal. **Tailscale** is great when you own both ends, but it still means joining devices to a private network. **Termius** requires complicated setup: port forwarding, firewall rules, key management...

So I built something simpler: **run a command, scan a QR, start typing.**

Then it clicked: the same trick (one command, one URL) is the easiest way to give an AI agent a real terminal on *any* computer. No MCP server to write, no SSH keys, no Docker, no config. Run `uvx ptn`, hand over the URL, and the agent runs commands, reads the screen, and answers prompts on that machine. And because it's a web terminal, you can open the same session in any browser to watch it work live, or grab the keyboard and take over.

## Features

- **Hand a computer to an agent, full control, and watch it** - Give an AI agent the URL and it gets a real terminal on the machine via MCP or plain REST. Open the same session in any browser to watch it work live, and grab the keyboard whenever you want. No keys, no Docker. The agent learns how from `<url>/llms.txt` and `<url>/.well-known/mcp.json`. See [Agent access](#agent-access-mcp--rest).
- **One command, instant access** - `uvx ptn` and you (or an agent) get a real terminal on this machine. No SSH, no port forwarding, no config files. Cloudflare tunnel + QR code.
- **Actually usable on mobile** - Touch-optimized with momentum scrolling, pinch-to-zoom, swipe gestures, and modifier keys (Ctrl, Alt).
- **Full terminal apps** - vim, htop, less, tmux all work correctly with proper alt-screen buffer handling.
- **Persistent multi-tab sessions** - Sessions survive disconnects. Close the browser, switch networks, reconnect from another device, and your shell and running processes are still there. You and an agent can share one session: watch it work, or take over.
- **Cross-platform** - Windows (PowerShell, CMD, WSL), Linux/macOS (Bash, Zsh, Fish, Nushell, and any shell via `$SHELL`). Auto-detects your shells.
- **Hard to guess by default** - Every launch adds an independent 128-bit random access path. The bare tunnel hostname and every wrong path return 404. The URL is hidden on screen, but the QR contains the complete credential, so keep both private. Press `c` to copy agent instructions and URL, or `u` to copy the URL only.

## Install

| Method | Install | Update |
|--------|---------|--------|
| **uvx** (no install) | `uvx ptn` | `uvx ptn@latest` |
| **uv tool** | `uv tool install ptn` | `uv tool upgrade ptn` |
| **pipx** | `pipx install ptn` | `pipx upgrade ptn` |
| **pip** | `pip install ptn` | `pip install -U ptn` |

**One-line install (uv + ptn):**

| OS | Command |
|----|---------|
| **Windows** | `powershell -ExecutionPolicy ByPass -c "irm https://raw.githubusercontent.com/lyehe/porterminal/master/install.ps1 \| iex"` |
| **macOS/Linux** | `curl -LsSf https://raw.githubusercontent.com/lyehe/porterminal/master/install.sh \| sh` |

Requires Python 3.12+ and [cloudflared](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/) (auto-installed if missing).

## Usage

```bash
ptn                    # Start in current directory
ptn ~/projects/myapp   # Start in specific folder
```

| Flag | Description |
|------|-------------|
| `-n, --no-tunnel` | Local network only (no Cloudflare tunnel) |
| `-b, --background` | Run in background and return immediately |
| `-p, --password` | Prompt for password to protect this session |
| `-sp, --save-password` | Save or clear password in config |
| `-tp, --toggle-password` | Set password requirement (on/off/toggle) |
| `-v, --verbose` | Show detailed startup logs |
| `-i, --init` | Create `.ptn/ptn.yaml` with auto-discovered project scripts as buttons |
| `-if, --init-from URL/PATH` | Create `.ptn/ptn.yaml` from a URL or local file |
| `-c, --compose` | Enable compose mode by default |
| `-k, --keep-qr` | Keep the QR code visible after the first connection |
| `-u, --check-update` | Check if a newer version is available |
| `-V, --version` | Show version |

**While running:** with a tunnel active, the connection URL is hidden on screen for privacy. Press **`c`** to copy agent instructions and URL, including `/mcp`, `/api/agent/run`, and `/llms.txt`; press **`u`** to copy the URL only; or scan the QR to connect. `Ctrl+C` stops the server.

## Agent access (MCP + REST)

The same URL also works for AI agents. MCP-capable clients can use **`<url>/mcp`** (Streamable HTTP) for native typed tools. Agents that cannot register an MCP server can use the REST fallback at **`<url>/api/agent/run`** with ordinary HTTP requests. Either path creates a persistent agent shell, shown as a 🤖 tab you can watch and take over from your phone.

Hand the agent the complete generated URL, including its access code. MCP clients can auto-discover the server from **`<url>/.well-known/mcp.json`** (the MCP `server.json` descriptor), and there's a human/agent-readable **`<url>/llms.txt`** with usage. The base page also includes accessibility-visible hints for browser-driving agents, while the human UI stays compact. Example client config:

```json
{
  "mcpServers": {
    "porterminal": { "url": "https://<your-tunnel>.trycloudflare.com/<access-code>/mcp" }
  }
}
```

MCP tools: `run_command` (clean output + exit code), `read_screen`, `send_keys`, `send_signal` (Ctrl-C / EOF).

REST fallback:

```bash
curl -s -X POST https://<your-tunnel>.trycloudflare.com/<access-code>/api/agent/run \
  -H "content-type: application/json" \
  -d '{"command":"echo hello","timeout":30}'
```

The response includes a `session_id`; reuse it with `<url>/api/agent/screen`,
`<url>/api/agent/keys`, `<url>/api/agent/signal`, and
`DELETE <url>/api/agent/session`.

When you open Porterminal on your phone, the top-right copy button copies the same agent-ready share text. Browser-only agents also get a fallback on the base page: a DOM-readable **Terminal screen** mirror and a clearly labeled **Terminal input**.

> **Security:** `<url>` means the complete generated URL, including its random access code. The bare tunnel hostname exposes nothing, but anyone (or any agent) with the complete URL gets full, non-elevated shell access. See [docs/agent-access.md](docs/agent-access.md).

## Mobile Gestures

| Gesture | Action |
|---------|--------|
| **Tap** | Focus terminal, clear selection |
| **Long-press** | Start text selection |
| **Double-tap** | Select word |
| **Swipe left/right** | Arrow keys (← →) |
| **Scroll** | Momentum scrolling with physics |
| **Pinch** | Zoom text (10-24px) |

**Modifier keys** (Ctrl, Alt, Shift): Tap once for sticky (one keystroke), double-tap for lock.

**Compose mode** (▤ button): Toggle a text input field where you can type or dictate, edit your text with full mobile editing features (autocorrect, suggestions, cursor positioning), then send to terminal. Useful for longer commands or voice input.

## Configuration

Run `ptn --init` to create a starter config. It auto-discovers project scripts from `package.json`, `pyproject.toml`, or `Makefile` and adds them as buttons:

```bash
ptn -i
# Created: .ptn/ptn.yaml
# Discovered 3 project script(s): build, dev, test
```

Or create `ptn.yaml` manually:

```yaml
# Terminal settings
terminal:
  default_shell: nu              # Default shell ID
  shells:                        # Custom shell definitions
    - id: nu
      name: Nushell
      command: nu
      args: []

# Custom buttons (appear in toolbar)
# row: 1 = default row, 2+ = additional rows
buttons:
  - label: "claude"
    send:
      - "claude"
      - 100        # delay in ms
      - "\r"
  - label: "build"
    send: "npm run build\r"
    row: 2         # second button row

# Update checker settings
update:
  notify_on_startup: true   # Show update notification
  check_interval: 86400     # Seconds between checks (default: 24h)

# Security settings
security:
  require_password: true    # Always require password at startup
  password_hash: ""         # Saved password hash (use ptn -sp to set)
  max_auth_attempts: 5      # Max failed attempts before disconnect
```

Config is searched in order: `$PORTERMINAL_CONFIG_PATH`, `./ptn.yaml`, `./.ptn/ptn.yaml`, `~/.ptn/ptn.yaml`.

## Security

Every launch creates a new 128-bit random path such as
`https://<tunnel>.trycloudflare.com/<access-code>/`. All browser, WebSocket,
MCP, REST, health, and static routes require that exact prefix; the bare host
and wrong paths return 404. This makes brute-forcing a discovered tunnel
hostname impractical.

The complete generated URL is still a bearer credential: anyone who gets it
has shell access. Restart Porterminal to rotate the code if it leaks. The
optional password adds authentication to browser WebSockets, but MCP and REST
continue to trust the complete URL so agents can use the one-link workflow.

A browser remembers a successful password in plaintext storage scoped to that
complete launch URL. Saving a password for a newer launch on the same origin
retires older Porterminal password entries; clearing or rejecting a remembered
password removes them all without touching other browser storage. Consequently,
concurrent launches on the same origin may prompt again, while an already
authenticated connection remains connected.

**From the UI:** Open Settings (gear icon) and use the Security section to set/change password and toggle password requirement. Changes require server restart.

**From CLI:**

```bash
# One-time password (prompt each session)
ptn -p

# Save password to config (no prompt needed)
ptn -sp
# Password: ****
# Confirm password: ****

# Clear saved password (enter empty password)
ptn -sp
# Password: [press Enter]

# Set or toggle password requirement
ptn -tp          # Toggle on/off
```

See [docs/security.md](docs/security.md) for details.

## Troubleshooting

**Connection fails?** Use the complete generated URL, including its access code. Cloudflare tunnel issues can also be resolved by restarting the server (`Ctrl+C`, then `ptn`) for a fresh tunnel and access path.

**`uvx ptn` still runs an older version?** An existing `uv tool` installation
can take precedence. Run `uv tool upgrade ptn`, or bypass installed tools with
`uvx --isolated ptn@latest`.

**Shell not detected?** Set your `$SHELL` environment variable or configure shells in `ptn.yaml`.

## Contributing

**This project does not accept external contributions** (pull requests or code
changes) for security reasons (see [CONTRIBUTING.md](CONTRIBUTING.md)). You're
welcome to fork and run your own copy under [AGPL-3.0](LICENSE).

Run from source:

```bash
git clone https://github.com/lyehe/porterminal
cd porterminal
uv sync --frozen
uv run --frozen ptn
```

## License

[AGPL-3.0](LICENSE)
