Metadata-Version: 2.4
Name: qirabot
Version: 0.5.4
Summary: Official Python SDK for Qirabot - AI-powered device automation
Author-email: Qirabot Team <support@qirabot.com>
License-Expression: MIT
Project-URL: Homepage, https://qirabot.com
Project-URL: Documentation, https://app.qirabot.com/docs/sdk/python
Project-URL: Repository, https://github.com/qirabot/python-sdk
Project-URL: Bug Tracker, https://github.com/qirabot/python-sdk/issues
Keywords: qirabot,automation,testing,ai,sdk,rpa
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
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: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx>=0.27.0
Requires-Dist: websockets>=13.0
Provides-Extra: dev
Requires-Dist: pytest>=7.0.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
Requires-Dist: mypy>=1.0.0; extra == "dev"
Requires-Dist: ruff>=0.1.0; extra == "dev"
Dynamic: license-file

# Qirabot Python SDK

Official Python SDK for [Qirabot](https://qirabot.com) - AI-powered device automation platform.

Automate mobile and web devices with natural language or structured actions. Let AI see the screen, click, type, extract data, and verify results.

## Installation

```bash
pip install qirabot
```

Requires Python 3.10+.

## Quick Start

### Interactive Mode

Step-by-step control with real-time feedback via WebSocket:

```python
from qirabot import Qirabot

bot = Qirabot("qk_your_api_key", base_url="https://app.qirabot.com")

with bot.task("device-id", name="wiki-extract") as t:
    t.navigate("https://en.wikipedia.org")
    t.type("Search Wikipedia input", "Artificial intelligence")
    t.click("Search button")
    t.wait_for("Wikipedia article page is visible", timeout_ms=10000)

    summary = t.extract("Get the first paragraph of the article")
    print(f"Summary: {summary}")

    t.verify("The article title contains 'Artificial intelligence'")
```

### Submit Mode

Fire-and-forget execution with polling:

```python
from qirabot import Qirabot, Action

bot = Qirabot("qk_your_api_key")

# Single AI instruction — let AI handle the entire workflow
task_id = bot.submit(
    "device-id",
    name="hn-top-stories",
    instruction="Go to news.ycombinator.com, extract the top 3 story titles and their scores",
)
result = bot.wait(task_id, timeout=120)
print(f"Status: {result.status}, Steps: {len(result.steps)}")

# Composed actions — precise control over each step
task_id = bot.submit("device-id", name="github-trending", actions=[
    Action.navigate("https://github.com/trending"),
    Action.wait_for("Trending repositories page is loaded"),
    Action.extract("Get the names and descriptions of the top 5 trending repositories", variable="repos"),
    Action.take_screenshot(),
])
result = bot.wait(task_id)
for step in result.steps:
    if step.output:
        print(step.output)
```

### Download Images

Download screenshots from completed tasks:

```python
# Download a single step screenshot
bot.download_screenshot(task_id, step=1, path="step_1.png")

# Get screenshot bytes without saving to file
img_bytes = bot.download_screenshot(task_id, step=1)

# Download all screenshots as a ZIP archive
bot.download_images(task_id, path="task_images.zip")
```

## Actions

| Action | Android | iOS | Chrome | Desktop | Sandbox |
|--------|:-------:|:---:|:------:|:-------:|:-------:|
| `click` | ✓ | ✓ | ✓ | ✓ | ✓ |
| `double_click` | ✓ | ✓ | ✓ | ✓ | ✓ |
| `type_text` | ✓ | ✓ | ✓ | ✓ | ✓ |
| `type_direct` | ✓ | ✓ | ✓ | ✓ | ✓ |
| `clear_text` | ✓ | ✓ | ✓ | ✓ | ✓ |
| `press_key` | ✓ | ✓ | ✓ | ✓ | ✓ |
| `scroll` | ✓ | ✓ | ✓ | ✓ | ✓ |
| `scroll_at` | ✓ | ✓ | ✓ | ✓ | ✓ |
| `wait` | ✓ | ✓ | ✓ | ✓ | ✓ |
| `wait_for` | ✓ | ✓ | ✓ | ✓ | ✓ |
| `take_screenshot` | ✓ | ✓ | ✓ | ✓ | ✓ |
| `ai` | ✓ | ✓ | ✓ | ✓ | ✓ |
| `extract` | ✓ | ✓ | ✓ | ✓ | ✓ |
| `verify` | ✓ | ✓ | ✓ | ✓ | ✓ |
| `search` | ✓ | ✓ | ✓ | ✓ | ✓ |
| `hover` | | | ✓ | ✓ | ✓ |
| `right_click` | | | | ✓ | ✓ |
| `drag` | | | | ✓ | ✓ |
| `navigate` | | | ✓ | | ✓ |
| `go_back` | | | ✓ | | |
| `swipe` | ✓ | ✓ | | | |
| `start_app` | ✓ | ✓ | | ✓ | |
| `stop_app` | ✓ | ✓ | | ✓ | |

## Events

Listen for real-time step and screenshot events in interactive mode:

```python
from qirabot import StepEvent, ScreenshotEvent

with bot.task("device-id") as t:
    t.on("step", lambda s: print(f"Step {s.number}: {s.action} -> {s.status}"))
    t.on("screenshot", lambda s: s.save(f"screenshots/step_{s.number}.png"))

    t.click("Login button")
```

## Workflow Integration

Interactive mode keeps the device connection alive across steps. You can run your own Python code between any two device actions — read files, validate data, branch on results, write reports:

```python
import json
import os
from qirabot import Qirabot

bot = Qirabot(os.environ["QIRA_API_KEY"])

# Test data — ready to run, no extra files needed
test_cases = [
    {"url": "https://github.com",       "expect_keyword": "GitHub"},
    {"url": "https://www.wikipedia.org", "expect_keyword": "Wikipedia"},
    {"url": "https://news.ycombinator.com", "expect_keyword": "Hacker News"},
]

results = []

with bot.task("device-id", name="heading-check") as t:
    for case in test_cases:
        # Device: navigate to the page
        t.navigate(case["url"])
        t.wait_for("Page has finished loading", timeout_ms=15000)

        # Device: extract the page heading
        heading = t.extract("Get the main heading or site title text")

        # Your code: validate between steps
        passed = case["expect_keyword"].lower() in heading.lower()
        results.append({"url": case["url"], "heading": heading, "passed": passed})
        print(f"{'PASS' if passed else 'FAIL'} {case['url']} -> {heading}")

        # Conditional: screenshot only on failure
        if not passed:
            t.screenshot(path=f"{case['expect_keyword']}_fail.png")

print(json.dumps(results, indent=2, ensure_ascii=False))
```

Submit mode returns structured step results for post-processing:

```python
import csv

task_id = bot.submit(
    "device-id",
    name="product-hunt-scrape",
    instruction="Go to producthunt.com, extract the top 5 products with their names and taglines",
)
result = bot.wait(task_id)

with open("steps.csv", "w", newline="") as f:
    writer = csv.DictWriter(f, fieldnames=["step", "action", "status", "duration_ms"])
    writer.writeheader()
    for step in result.steps:
        writer.writerow({
            "step": step.number,
            "action": step.action,
            "status": step.status,
            "duration_ms": step.step_duration_ms,
        })
```

## Error Handling

```python
from qirabot import ActionError, QirabotTimeoutError, DeviceOfflineError

try:
    with bot.task("device-id", name="error-demo") as t:
        t.navigate("https://httpstat.us/500")
        t.verify("Page shows a success message")
except ActionError as e:
    print(f"Action failed: {e}")
except DeviceOfflineError:
    print("Device is offline")
except QirabotTimeoutError:
    print("Operation timed out")
```

## License

MIT
