Metadata-Version: 2.5
Name: airanks-acp-agent
Version: 1.0.0
Summary: AIR (airanks) ACP agent — an Agent Connect Protocol server exposing AI optimization rankings (AIR score, tracked stats, AI-file posture, search) for any domain, callable from any ACP hub or catalog. Same auth as every AIR client. https://airanks.net
Project-URL: Homepage, https://airanks.net
Project-URL: Toolbar, https://airanks.net/toolbar
Project-URL: Repository, https://git.shoemoney.ai/shoemoney/airanks-oss
Author: Jeremy Schoemaker
License: MIT
License-File: LICENSE
Keywords: acp,agent connect protocol,ai agent,ai optimization,ai rank,ai visibility,air,airanks,beeai,generative engine optimization,llm seo
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
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: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.11
Requires-Dist: acp-sdk>=1.0.0
Requires-Dist: httpx>=0.27
Requires-Dist: uvicorn<0.36
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
Requires-Dist: pytest>=7; extra == 'dev'
Description-Content-Type: text/markdown

# 🐝 airanks-acp-agent

**AIR (Artificial Intelligence Ranking)** by **airanks** — **AI optimization** made visible. Check
any site's AI Rank free at **[airanks.net](https://airanks.net)** (toolbar:
**[airanks.net/toolbar](https://airanks.net/toolbar)**).

[![License: MIT](https://img.shields.io/badge/license-MIT-brightgreen.svg)](./LICENSE)
[![Python](https://img.shields.io/badge/python-3.10%2B-3776AB?logo=python&logoColor=white)](https://pypi.org/project/airanks-acp-agent/)
[![ACP](https://img.shields.io/badge/protocol-ACP-6929c4)](https://agentcommunicationprotocol.dev)
[![status](https://img.shields.io/badge/status-production%2Fstable-brightgreen)](#-testing)
[![tests](https://img.shields.io/badge/tests-5%20passing-brightgreen)](tests/test_agent.py)
[![auth](https://img.shields.io/badge/auth-shared%20across%20AIR%20clients-0aa)](#-shared-authentication)

This repo turns **[AIR](https://airanks.net)** into a callable [ACP](https://agentcommunicationprotocol.dev)
(Agent Communication Protocol) agent named **`airank`**. Any ACP client, hub, or catalog —
[BeeAI](https://beeai.dev) included — can `POST /runs` and ask `airank` for a site's AI rank the
same way it would call any other agent. 🤖↔️📊

---

## 🗺️ Contents

| | | |
|---|---|---|
| [🧠 What it does](#-what-it-does) | [🏗️ Architecture](#️-architecture) | [🔁 A lookup, step by step](#-a-lookup-step-by-step) |
| [📦 Install](#-install) | [🚀 Quickstart](#-quickstart) | [📋 Behavior reference](#-behavior-reference) |
| [🐝 Registering with BeeAI](#-registering-with-a-beeai--acp-catalog) | [🔐 Shared authentication](#-shared-authentication) | [🗂️ Layout](#️-layout) |
| [🧪 Testing](#-testing) | [🌐 The airanks family](#-the-airanks-family) | [📄 License](#-license) |

---

## 🧠 What it does

**AIR measures AI optimization** — how often, and how well, an AI assistant like ChatGPT cites a
given domain when answering real questions. `airank` is the ACP door into that data:

- 🎯 **Domain lookup** — send a hostname (`stripe.com`), get its AIR score (0–10), percentile,
  tracked citation stats, and AI-file posture (`llms.txt`, `ai.txt`, `robots.txt` AI-agent rules).
- 🔍 **Search** — send a phrase (`payment processing`), get matching domains, brands, and phrases
  that **airanks** tracks.
- ⏳ **Graceful hydration** — a domain brand-new to AIR triggers server-side hydration. `airank`
  polls and streams a "gathering…" progress message instead of guessing or hanging silently.

## 🏗️ Architecture

Four small modules, one job each:

```mermaid
flowchart LR
    subgraph client["📥 Any ACP client"]
        C["BeeAI hub / catalog\ncurl / other agent"]
    end

    subgraph agent_pkg["🐝 airanks_acp_agent"]
        server["server.py\n@server.agent(name=\"airank\")"]
        agentpy["agent.py\nparse → poll → format"]
        api["api.py\nAirClient (httpx)"]
        auth["auth.py\nresolve_token()"]
    end

    AIR[("☁️ AIR API\nairanks.net/api/v1")]

    C -- "POST /runs" --> server
    server -- "answer(text)" --> agentpy
    agentpy -- "domain() / search()" --> api
    api -- "Bearer token?" --> auth
    api -- "GET /v1/domains/{host}\nGET /v1/search?q=" --> AIR
    AIR -. "JSON" .-> api
    agentpy -. "streamed reply chunks" .-> server
    server -. "RunYield" .-> C
```

- 🌐 **`api.py`** — the async `httpx` client (`AirClient`): `GET /v1/domains/{host}`,
  `GET /v1/search`, `GET /user`, plus `normalize_hostname()`.
- 🔑 **`auth.py`** — shared token resolution, identical across every AIR client.
- 🧩 **`agent.py`** — parses the caller's text, polls while a domain is pending, formats the reply.
- 🚪 **`server.py`** — registers the `airank` ACP agent and runs the server.

## 🔁 A lookup, step by step

The one behavior worth diagramming: a **never-before-seen domain**. Rather than fake a score,
`airank` streams a progress chunk and polls until AIR finishes hydrating it (or the ~180s budget
runs out):

```mermaid
sequenceDiagram
    autonumber
    participant U as ACP caller
    participant A as agent.py
    participant API as api.py (AirClient)
    participant AIR as AIR API

    U->>A: "stripe.com"
    A->>API: normalize_hostname() → domain()
    API->>AIR: GET /v1/domains/stripe.com
    AIR-->>API: data.ai_files.status = "pending"
    API-->>A: pending payload
    A-->>U: ⏳ "gathering data now, polling…"
    loop every AIR_POLL_SECONDS (default 20s)
        A->>API: domain()
        API->>AIR: GET /v1/domains/stripe.com
        alt still pending
            AIR-->>API: status = "pending"
        else ready
            AIR-->>API: status = "ready" + score
            API-->>A: final payload
            A-->>U: "**stripe.com** — AIR score 8/10 …"
        else 429 rate limited
            AIR-->>API: 429 + Retry-After
            A->>A: sleep(Retry-After)
        end
    end
```

## 📦 Install

```bash
pip install -e .
```

Requires **Python 3.10+**. Two runtime dependencies: [`acp-sdk`](https://pypi.org/project/acp-sdk/)
(the official BeeAI/Linux Foundation Agent Communication Protocol server SDK) and `httpx`
(`uvicorn<0.36` is pinned transitively — see the comment in `pyproject.toml`).

## 🚀 Quickstart

```bash
airank-acp
# or: python -m airanks_acp_agent.server
```

Starts an ACP server on `http://127.0.0.1:8000` speaking the standard ACP REST contract:
`GET /agents`, `GET /agents/airank`, `POST /runs`, `GET /runs/{run_id}`, `GET /runs/{run_id}/events`.
Override the bind address with `AIR_ACP_HOST` / `AIR_ACP_PORT`.

Call it like any ACP agent:

```bash
curl -s http://127.0.0.1:8000/runs \
  -H 'content-type: application/json' \
  -d '{"agent_name": "airank", "input": [{"role": "user", "parts": [{"content": "stripe.com"}]}]}'
```

## 📋 Behavior reference

| You send | `airank` does | Backing call |
|---|---|---|
| A single word that parses as a hostname (`stripe.com`, `www.Foo.com`) | Domain lookup — AIR score, percentile, tracked stats, AI-file summary. Polls while pending. | `GET /v1/domains/{host}` |
| Anything with a space, or a word that isn't a plausible host (`payment processing`) | Search across domains, brands, and phrases AIR tracks. | `GET /v1/search?q=` |
| Empty input | Returns the built-in help string. | — |

<details>
<summary>⚙️ Environment variables</summary>

| Var | Default | Purpose |
|---|---|---|
| `AIR_API_BASE` | `https://airanks.net/api/v1` | Point at a different API base (staging, a mirror, …). |
| `AIR_API_KEY` | — | Bearer token, always attaches (explicit intent). See [Shared authentication](#-shared-authentication). |
| `AIR_ACP_HOST` | `127.0.0.1` | Bind address for the ACP server. |
| `AIR_ACP_PORT` | `8000` | Bind port for the ACP server. |
| `AIR_POLL_SECONDS` | `20` | Interval between poll attempts while a domain is hydrating. |
| `AIR_POLL_MAX_SECONDS` | `180` | Total budget before giving up and telling the caller to retry. |
| `PLATFORM_URL` | `http://127.0.0.1:8333` | BeeAI platform to self-register with (via `acp-sdk`). |
| `PRODUCTION_MODE` | — | Set `true` to disable BeeAI auto-registration on startup. |

</details>

## 🐝 Registering with a BeeAI / ACP catalog

If a local [BeeAI platform](https://beeai.dev) is running (`PLATFORM_URL`, default
`http://127.0.0.1:8333`), `airank-acp` self-registers as a provider on startup — nothing further
to do. To register with a different hub or catalog by hand, point it at this server's base URL and
`/agents/airank` manifest, e.g.:

```bash
beeai agent add http://127.0.0.1:8000
```

or add a provider entry `{"location": "http://<host>:<port>"}` via that catalog's own API. Set
`PRODUCTION_MODE=true` to disable auto-registration (e.g. behind a reverse proxy in prod).

## 🔐 Shared authentication

One login works across **every** AIR client — the `air` CLI, the browser toolbar, the `airanks`
Python SDK, and this agent. Run `air login` anywhere and this agent picks it up automatically.
Resolution order:

| Order | Source | Behavior |
|---|---|---|
| 1️⃣ | **`AIR_API_KEY` env var** | Explicit intent — always attaches. |
| 2️⃣ | **`~/.config/air/auth.json`** | The file `air login` writes. A token loaded from here only attaches to requests aimed at the host it was saved for. |
| 3️⃣ | **Anonymous** | No token — subject to the anonymous rate limit (honored via `Retry-After`). |

## 🗂️ Layout

| File | Purpose |
|---|---|
| `src/airanks_acp_agent/api.py` | Async `httpx` client for `GET /v1/domains/{host}`, `GET /v1/search`, `GET /user`, plus hostname normalization — ported from the shared [API contract](../API-CONTRACT.md). |
| `src/airanks_acp_agent/auth.py` | Shared token resolution (`AIR_API_KEY` > auth file > anonymous). |
| `src/airanks_acp_agent/agent.py` | Parses the caller's text, polls while pending, formats the reply. |
| `src/airanks_acp_agent/server.py` | Registers the `airank` ACP agent and runs the server. |
| `tests/test_agent.py` | Hostname normalization + response-formatting tests. |

## 🧪 Testing

```bash
pip install -e ".[dev]"
pytest
```

## 🌐 The airanks family

`acp-agent` is one door into AIR among many — same API, same shared login, different front door:

| Repo | What it is |
|---|---|
| [`node-cli`](../node-cli) | `air` — the reference command-line client. |
| [`rust-cli`](../rust-cli) / [`go-cli`](../go-cli) | Zero-dependency Rust / Go builds of `air`. |
| [`chrome-extension`](../chrome-extension) | The AIR browser toolbar — a page's AI Rank while you browse. |
| [`mcp-server`](../mcp-server) | MCP server exposing `air_rank` / `air_files` / `air_search` tools. |
| [`agent-toolkit`](../agent-toolkit) | Wire AIR into Claude, Codex, Cursor, or any MCP-speaking agent. |
| [`acp-zed`](../acp-zed) | This same idea, wrapped for [Zed](https://zed.dev) instead of BeeAI. |
| [`python-sdk`](../python-sdk) / [`js-sdk`](../js-sdk) / [`composer-package`](../composer-package) | Language SDKs — no CLI, just a client class. |
| [`homebrew-tap`](../homebrew-tap) | `brew install` for the `air` CLI. |
| [`claude-skills`](../claude-skills) | Open-source Claude Code skills built on AIR. |

See [`../API-CONTRACT.md`](../API-CONTRACT.md) for the full wire contract every client shares.

## 📄 License

MIT — see [LICENSE](LICENSE).

---

<div align="center">

Made with 🐝 for the AI-optimization era. Powered by **[airanks.net](https://airanks.net)**.

</div>
