Metadata-Version: 2.4
Name: chainwake
Version: 0.3.0
Summary: Bittensor chain monitoring for AI agents.
Project-URL: Homepage, https://github.com/taostat/chainwake
Project-URL: Documentation, https://github.com/taostat/chainwake/tree/main/docs
Project-URL: Repository, https://github.com/taostat/chainwake
Project-URL: Issues, https://github.com/taostat/chainwake/issues
Author-email: Blockmachine <hello@blockmachine.io>
License-Expression: MIT
License-File: LICENSE
Keywords: agent,bittensor,blockmachine,chain,mcp,substrate,taostat,taostats,watcher
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Monitoring
Classifier: Typing :: Typed
Requires-Python: <3.15,>=3.13
Requires-Dist: anyio==4.14.2
Requires-Dist: apprise==1.12.0
Requires-Dist: async-substrate-interface==2.2.1
Requires-Dist: cyclopts==4.22.2
Requires-Dist: httpx[http2]==0.28.1
Requires-Dist: mcp==1.28.1
Requires-Dist: pydantic==2.13.4
Requires-Dist: starlette==1.3.1
Requires-Dist: structlog==26.1.0
Requires-Dist: tenacity==9.1.4
Requires-Dist: uvicorn==0.51.0
Requires-Dist: websockets==16.1.1
Description-Content-Type: text/markdown

# Blockmachine Chainwake

<!-- mcp-name: io.github.taostat/chainwake -->

Bittensor chain monitoring for AI agents.

Chainwake includes a watcher CLI and MCP server. A watcher invocation suspends
until a Bittensor chain-state condition fires, then exits with a structured
JSON payload. It is
observation-only: no keys, no signing, no extrinsic submission.

Agents spawn a chainwake watcher as a subprocess and parse stdout to decide what
to do next. The exit code is for shell-level control flow; the JSON is where
the detail lives. Bittensor is the launch chain.

---

## Install

Install the stable CLI in an isolated environment with uv:

```sh
uv tool install chainwake
```

Or install it in the active Python environment:

```sh
pip install chainwake
```

To try unreleased code or contribute, install the current GitHub source:

```sh
uv tool install git+https://github.com/taostat/chainwake.git
```

---

## Quickstart

All three examples run against a live Bittensor node. Set `CHAINWAKE_BT_RPC_URL`
or pass `--rpc-url`; the default is `wss://rpc.blockmachine.io`.
Enable shell completion: `chainwake --install-completion`.

### 1. Threshold — wait until subnet price drops below 0.05 TAO

```sh
chainwake bt subnet 19 price --below 0.05
```

Add `--durable` when the watcher must outlive the shell or agent turn that
created it:

```sh
chainwake --json --durable \
  --context "Tell me the observed price and block." \
  bt subnet 19 price --below 0.05
```

Chainwake persists the validated watcher, starts its local supervisor, prints
the job id, and exits immediately. A host can wait without polling the chain:

```sh
chainwake --json jobs wait <job-id>
```

The completion contains the supplied `context` and the normal output-v1
watcher result. Use `jobs list`, `jobs show`, and `jobs cancel` to manage
persisted jobs. Because job arguments are stored locally, durable mode rejects
literal `--api-key` and `--rpc-url` values; configure
`CHAINWAKE_BT_API_KEY` or `CHAINWAKE_BT_RPC_URL` in the supervisor environment.

Subscribes to subnet 19's TAO and alpha pool reserves and recomputes the price
when either changes. Exits 0 when the price crosses below 0.05 TAO, with stdout:

```json
{
  "schema_version": 1,
  "status": "matched",
  "watcher": { "chain": "bt", "resource": "subnet", "resource_id": "19",
               "sub_resource": "pool.price", "name": null,
               "primitive": "threshold",
               "invocation": ["chainwake", "bt", "subnet", "19", "price",
                              "--below", "0.05"] },
  "condition": { "operator": "below", "target": 0.05 },
  "observed": { "path": "subnet.19.pool.price", "value": 0.0487,
                "block": 4291820, "block_hash": "0xabc...",
                "timestamp": "2026-05-06T10:00:00Z" },
  "budget": { "runtime_ms": 3210, "rpc_calls": 3, "estimated_ru_consumed": 3 },
  "process": { "pid": 12345, "started_at": "2026-05-06T09:59:57Z" }
}
```

`budget.estimated_ru_consumed` and `--max-ru` form a registry-estimated
observation budget, not a provider billing cap. They intentionally exclude
connection bootstrap, transient retries, and RPCs hidden inside the SDK.

### 2. Delta — wait until price drops 5% within a 1-hour window

```sh
chainwake bt subnet 19 price --drop-pct 5 --window-time 1h
```

Fires when the oldest price in the rolling 1-hour window is more than 5%
above the current price. The window controls which samples are compared; it
does not impose a runtime limit. `--max-runtime` alone controls when an
unmatched watcher exits with `timeout`.

Omit the window to keep the first successful observation as an unexpiring
baseline for this watcher:

```sh
chainwake bt subnet 28 burnrate --move-pct 1
```

### 3. Event — wait for a new subnet to be registered

```sh
chainwake bt event --type subnet-registered --max-runtime 24h
```

Subscribes to subnet registration events. Exits 0 on the first match with
the event's decoded args in `observed`. Exits 1 if no event fires within
24 hours.

---

## Exit codes

Agents parse JSON for detail; exit codes are for shell-level control flow.

| Code | `status` field              | Meaning                                        |
|------|-----------------------------|------------------------------------------------|
| `0`  | `matched`                   | Condition fired                                |
| `1`  | `stopped` / `timeout` / `budget_exhausted` | Watcher finished without a match  |
| `2`  | `user_error`                | Invalid args, unknown resource                 |
| `3`  | `provider_error` / `auth_error` | RPC unavailable, or credentials/access required |
| `4`  | `internal_error`            | Bug in chainwake                               |

For watcher automation, pass `--json`; every watcher exit then emits a JSON
payload on stdout, including watcher errors. Without that flag, an interactive
TTY uses human-readable output automatically. Stderr carries diagnostics and
is not part of the stable contract.

---

## Output schema

Every watcher invocation run with `--json` emits exactly one JSON object on
exit. Piped stdout also selects JSON automatically, but agents should use the
explicit flag instead of depending on TTY detection. MCP configuration helpers,
help/version output, and parsing before a watcher is selected are outside this
watcher-envelope guarantee and use their documented formats. The schema is at
[`schemas/output-v1.json`](https://github.com/taostat/chainwake/blob/main/schemas/output-v1.json).

`schema_version: 1` identifies a closed contract.
Consumers must validate payloads against the matching schema and reject unknown fields.
Any output shape change—including an additive field or status value—requires a new `schema_version` and a new schema file.
The Chainwake package version is independent of the output-schema version; do
not infer schema compatibility from the package version.

---

## Adapters (`--out`)

By default Chainwake uses human output on an interactive TTY and indented JSON
when stdout is piped. Automation should pass `--json` explicitly. Use `--out`
to redirect output:

```sh
# NDJSON to stdout, keep running after each match
chainwake bt subnet 19 price --below 0.05 --out stream

# Append NDJSON to a file
chainwake bt subnet 19 price --below 0.05 --out file:///tmp/watchers.ndjson

# Notify via Telegram (any apprise URI)
chainwake bt subnet 19 price --below 0.05 --out "tgram://bottoken/chatid"
```

`--out` is repeatable. Apprise supports ~100 notification destinations
(Telegram, Discord, Slack, email, webhooks, and more). See the
[apprise URI reference](https://github.com/caronc/apprise/wiki) for the full
list.

---

## MCP server

chainwake includes a built-in MCP server that exposes chain watchers as MCP
tools for Hermes, OpenClaw, Claude Desktop, Cursor, and other MCP clients. Its
exit-oriented catalogue maps each wired command to a tool; one filtered event
tool covers both curated friendly names and raw `Module.Event` types. See
[the MCP guide](https://github.com/taostat/chainwake/blob/main/docs/mcp.md).

```sh
chainwake mcp serve --stdio
chainwake mcp config hermes
chainwake mcp config openclaw
```

---

## Configuration

| Method | Precedence | Example |
|--------|-----------|---------|
| `--rpc-url` flag | Highest | `--rpc-url wss://my-node:9944` |
| `CHAINWAKE_BT_RPC_URL` env var | Middle | `export CHAINWAKE_BT_RPC_URL=wss://...` |
| Default (Blockmachine RPC) | Lowest | `wss://rpc.blockmachine.io` |

Blockmachine is the default RPC provider for Bittensor at launch. The
free tier works anonymously, so no configuration is needed for a first
watcher. No API key is required. The architecture also supports any
Substrate-compatible WebSocket endpoint.

`--api-key` is accepted by every command for higher-limit Blockmachine access
or other authenticated RPC endpoints.

`--max-runtime` accepts duration strings: `30s`, `10m`, `2h`, `1d`. Default is unbounded.
Agent and automation calls should always set an explicit bounded runtime.

---

## Agent integration

An agent starts a Chainwake process and ends its turn. While Chainwake watches
the chain, no model or agent loop runs. When the condition matches (or the
watch reaches a limit), Chainwake exits; a capable host wakes the originating
agent with the JSON result.

Hermes users install `taostat/chainwake` from the dashboard's Plugins page.
OpenClaw users install `@blockmachine/chainwake` from its Skills UI or with:

```sh
openclaw skills install @blockmachine/chainwake --global
```

Both integrations use native background-process completion notifications; they
do not poll and do not keep an MCP request pending. See the
[agent integration guide](https://github.com/taostat/chainwake/blob/main/docs/agent-integration.md).

Pass `--json` for this flow. Stdout is one valid JSON object, the exit code is
always 0–4, and stderr is not parsed. Without `--json`, an interactive TTY is
rendered as human-readable text.

```python
import json, subprocess

proc = subprocess.run(
    [
        "chainwake", "bt", "subnet", "19", "price",
        "--below", "0.05", "--max-runtime", "5m",
        "--json",
    ],
    capture_output=True, text=True,
)
payload = json.loads(proc.stdout)
if proc.returncode == 0:
    print(f"Matched at block {payload['observed']['block']}")
elif proc.returncode == 1:
    print(f"No match: {payload['reason']}")  # timeout or budget_exhausted
elif payload["status"] == "auth_error":
    raise RuntimeError(
        "RPC access needs operator action; do not retry unchanged credentials"
    )
```

For MCP tools, parallel wakes, detached processes, and restart durability, see
[Agent integration](https://github.com/taostat/chainwake/blob/main/docs/agent-integration.md)
and
[MCP integration](https://github.com/taostat/chainwake/blob/main/docs/mcp.md).

---

## Status

chainwake is in active development. See
[`PROGRESS.md`](https://github.com/taostat/chainwake/blob/main/PROGRESS.md) for
in-flight work.

---

## License — MIT

---

## Links

- [README](https://github.com/taostat/chainwake/blob/main/README.md)
- [Historical design proposal (archived)](https://github.com/taostat/chainwake/blob/main/spec.md)
- [JSON output schema](https://github.com/taostat/chainwake/blob/main/schemas/output-v1.json)
- [apprise URI reference](https://github.com/caronc/apprise/wiki)
- [GitHub Issues](https://github.com/taostat/chainwake/issues)
