Metadata-Version: 2.4
Name: tinker-host-agent
Version: 0.1.2
Summary: Tinker Host Agent — connects host machines to Tinker via Device Agent Protocol (DAP)
Project-URL: Homepage, https://github.com/gencylee/tinker
Project-URL: Repository, https://github.com/gencylee/tinker
Author-email: Tinker <noreply@tinker.dev>
License: MIT
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Systems Administration
Requires-Python: >=3.11
Requires-Dist: pydantic-settings<3.0,>=2.0
Requires-Dist: websockets<15.0,>=13.0
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.8.0; extra == 'dev'
Provides-Extra: gui
Requires-Dist: pillow<12.0,>=10.0; extra == 'gui'
Description-Content-Type: text/markdown

# Tinker Host Agent

A lightweight Python agent that connects your host machine to a [Tinker](https://github.com/gencylee/tinker) instance via WebSocket using the Device Agent Protocol (DAP). Once connected, Tinker can execute shell commands, manage files, take screenshots, control applications, and more on your machine.

## Installation

```bash
pip install tinker-host-agent
```

Or with [uv](https://docs.astral.sh/uv/):

```bash
uv pip install tinker-host-agent
```

For GUI capabilities (screenshot, click, type), install with the `gui` extra:

```bash
pip install "tinker-host-agent[gui]"
```

## Quick Start

Connect to a local Tinker instance:

```bash
tinker-host-agent
```

Connect to a remote Tinker server with authentication:

```bash
tinker-host-agent \
  --url wss://tinker.example.com/ws/device-agent \
  --token YOUR_AUTH_TOKEN
```

Or configure entirely via environment variables:

```bash
export TINKER_HOST_SERVER_URL=wss://tinker.example.com/ws/device-agent
export TINKER_HOST_AUTH_TOKEN=your-token
tinker-host-agent
```

## Configuration

All settings can be configured via environment variables (prefixed with `TINKER_HOST_`) or a `.env` file in the working directory.

### Connection

| Variable | Default | Description |
|---|---|---|
| `TINKER_HOST_SERVER_URL` | `ws://localhost:3000/ws/device-agent` | WebSocket URL of the Tinker backend |
| `TINKER_HOST_AUTH_TOKEN` | (empty) | Authentication token for the server |
| `TINKER_HOST_AGENT_ID` | (auto-generated) | Unique agent identifier |
| `TINKER_HOST_AGENT_NAME` | `hostname (OS)` | Display name shown in Tinker UI |
| `TINKER_HOST_AGENT_TYPE` | `host` | Agent type: `host` or `remote` |

### Reconnection

| Variable | Default | Description |
|---|---|---|
| `TINKER_HOST_RECONNECT_DELAY` | `1.0` | Initial reconnect delay (seconds) |
| `TINKER_HOST_RECONNECT_MAX_DELAY` | `30.0` | Maximum reconnect delay (exponential backoff cap) |

### Security

| Variable | Default | Description |
|---|---|---|
| `TINKER_HOST_ALLOWED_PATHS` | (empty = home dir) | Comma-separated list of allowed filesystem paths |
| `TINKER_HOST_BLOCKED_PATHS` | `~/.ssh,~/.gnupg,~/.aws,~/.config/gcloud` | Comma-separated list of blocked filesystem paths |
| `TINKER_HOST_BLOCKED_COMMANDS` | `rm -rf /,mkfs,dd if=/dev` | Comma-separated list of blocked shell command patterns |

### Capability Toggles

| Variable | Default | Description |
|---|---|---|
| `TINKER_HOST_ENABLE_SHELL` | `true` | Enable shell command execution |
| `TINKER_HOST_ENABLE_FS` | `true` | Enable file system access |
| `TINKER_HOST_ENABLE_GUI` | `true` | Enable GUI automation (screenshot, click, type) |
| `TINKER_HOST_ENABLE_APP` | `true` | Enable application management |
| `TINKER_HOST_ENABLE_CLIPBOARD` | `true` | Enable clipboard access |
| `TINKER_HOST_ENABLE_NETWORK` | `true` | Enable network utilities |

### Plugins

| Variable | Default | Description |
|---|---|---|
| `TINKER_HOST_PLUGIN_DIR` | (empty) | Directory to load custom plugins from |

## Available Capabilities

### shell

Execute commands on the host machine.

- **exec** -- Run a shell command and return stdout/stderr/exit_code
- **kill** -- Send SIGTERM to a process by PID

### fs

Read and write files within allowed paths.

- **read** -- Read a file (max 10MB)
- **write** -- Write content to a file
- **list** -- List directory contents
- **search** -- Recursive glob search

### gui

GUI automation (requires platform tools: `screencapture`/`cliclick` on macOS, `scrot`/`xdotool` on Linux).

- **screenshot** -- Capture the screen as base64 PNG
- **click** -- Click at coordinates
- **type** -- Type text
- **key** -- Press a key or key combination (e.g., `cmd+c`)
- **scroll** -- Scroll by delta
- **window_list** -- List visible windows/applications

### app

Application lifecycle management.

- **launch** -- Open an application by name
- **list** -- List running applications
- **info** -- Get info about an application
- **quit** -- Quit an application

### clipboard

Host clipboard access.

- **get** -- Read clipboard content
- **set** -- Write to clipboard

### network

Network utilities.

- **http** -- Perform a simple HTTP GET request
- **ports** -- List listening TCP/UDP ports
- **ping** -- Ping a host (ICMP)

## Plugin Development

Create custom capabilities by writing a plugin module. Place `.py` files in your `TINKER_HOST_PLUGIN_DIR` directory.

Each plugin must define a class that inherits from `PluginBase`:

```python
from tinker_host.plugins import PluginBase


class MyPlugin(PluginBase):
    namespace = "my_tools"
    actions = ["hello"]
    description = "My custom tools"

    async def handle(self, action: str, params: dict) -> dict:
        match action:
            case "hello":
                name = params.get("name", "world")
                return {"message": f"Hello, {name}!"}
            case _:
                return {"error": f"Unknown action: {action}"}
```

The agent discovers plugins automatically on startup. Each plugin file should contain exactly one `PluginBase` subclass. The plugin's `namespace` becomes available as a capability that Tinker can invoke.

## Security Model

The host agent enforces several layers of security:

1. **Authentication** -- The agent sends an auth token during the WebSocket handshake. The server validates the token and can reject the connection.

2. **Capability approval** -- The server approves or denies each capability during the handshake. Only approved capabilities accept commands.

3. **Path restrictions** -- The `fs` capability restricts file access to `allowed_paths` and blocks sensitive directories (`~/.ssh`, `~/.gnupg`, etc.) by default.

4. **Command blocking** -- The `shell` capability blocks dangerous command patterns (e.g., `rm -rf /`, `mkfs`, `dd if=/dev`).

5. **Request scoping** -- Each command is routed to a specific capability namespace and action. There is no generic "eval" endpoint.

These defaults are conservative. Adjust `TINKER_HOST_ALLOWED_PATHS`, `TINKER_HOST_BLOCKED_PATHS`, and `TINKER_HOST_BLOCKED_COMMANDS` to match your environment.

## CLI Options

```
tinker-host-agent [OPTIONS]

Options:
  --url URL        Tinker server WebSocket URL
  --token TOKEN    Authentication token
  --name NAME      Agent display name
  --id ID          Agent ID (auto-generated if omitted)
  --mode {host,remote}  Agent type
  --no-gui         Disable GUI capability
  --no-shell       Disable shell capability
  -v, --verbose    Verbose (DEBUG) logging
```

## License

MIT
