trajrl 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- trajrl-0.2.0/PKG-INFO +206 -0
- trajrl-0.2.0/README.md +195 -0
- trajrl-0.2.0/pyproject.toml +19 -0
- trajrl-0.2.0/setup.cfg +4 -0
- trajrl-0.2.0/trajrl/__init__.py +1 -0
- trajrl-0.2.0/trajrl/api.py +185 -0
- trajrl-0.2.0/trajrl/cli.py +224 -0
- trajrl-0.2.0/trajrl/display.py +480 -0
- trajrl-0.2.0/trajrl.egg-info/PKG-INFO +206 -0
- trajrl-0.2.0/trajrl.egg-info/SOURCES.txt +12 -0
- trajrl-0.2.0/trajrl.egg-info/dependency_links.txt +1 -0
- trajrl-0.2.0/trajrl.egg-info/entry_points.txt +2 -0
- trajrl-0.2.0/trajrl.egg-info/requires.txt +3 -0
- trajrl-0.2.0/trajrl.egg-info/top_level.txt +1 -0
trajrl-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: trajrl
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: CLI for TrajectoryRL, Bittensor subnet 11 — agent-friendly access to live validator, miner, and evaluation data.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: typer>=0.9
|
|
9
|
+
Requires-Dist: httpx>=0.25
|
|
10
|
+
Requires-Dist: rich>=13.0
|
|
11
|
+
|
|
12
|
+
# trajrl
|
|
13
|
+
|
|
14
|
+
CLI for the [TrajectoryRL subnet](https://trajrl.com) (Bittensor SN11). Query live validator, miner, and evaluation data from the terminal.
|
|
15
|
+
|
|
16
|
+
Designed for AI agents (Claude Code, Cursor) and humans alike — outputs JSON when piped, Rich tables when interactive.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install trajrl
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Commands
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
trajrl status # Network health overview
|
|
28
|
+
trajrl validators # List all validators
|
|
29
|
+
trajrl scores <validator_hotkey> # Per-miner scores from a validator
|
|
30
|
+
trajrl scores --uid <uid> # Query by validator UID instead
|
|
31
|
+
trajrl miner <hotkey> # Miner detail + diagnostics
|
|
32
|
+
trajrl miner --uid <uid> # Query by miner UID instead
|
|
33
|
+
trajrl pack <hotkey> <pack_hash> # Pack evaluation detail
|
|
34
|
+
trajrl submissions [--failed] # Recent pack submissions
|
|
35
|
+
trajrl eval-history <validator> # List eval cycle IDs for a validator
|
|
36
|
+
trajrl eval-history <v> --from <date> # Filter by date range
|
|
37
|
+
trajrl cycle-log <validator> # Download and display a cycle log
|
|
38
|
+
trajrl cycle-log <v> --format summary # Show parsed summary tables
|
|
39
|
+
trajrl logs [--type cycle|miner] # Eval log archives
|
|
40
|
+
trajrl --version # Show CLI version
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Global Options
|
|
44
|
+
|
|
45
|
+
Every command accepts:
|
|
46
|
+
|
|
47
|
+
| Option | Description |
|
|
48
|
+
|--------|-------------|
|
|
49
|
+
| `--json` / `-j` | Force JSON output (auto-enabled when stdout is piped) |
|
|
50
|
+
| `--base-url URL` | Override API base (default: `https://trajrl.com`, env: `TRAJRL_BASE_URL`) |
|
|
51
|
+
| `--version` / `-v` | Show CLI version and exit |
|
|
52
|
+
|
|
53
|
+
### New in v0.2.0
|
|
54
|
+
|
|
55
|
+
- **UID support**: Query validators and miners by UID instead of hotkey
|
|
56
|
+
```bash
|
|
57
|
+
trajrl miner --uid 65 # Instead of full hotkey
|
|
58
|
+
trajrl scores --uid 221 # Query validator by UID
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- **Date filtering**: Filter eval history by date range
|
|
62
|
+
```bash
|
|
63
|
+
trajrl eval-history 5Cd6h... --from 2026-03-25 --to 2026-03-26
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
- **Cycle log summary**: Parse cycle logs into structured tables
|
|
67
|
+
```bash
|
|
68
|
+
trajrl cycle-log 5Cd6h... --format summary
|
|
69
|
+
```
|
|
70
|
+
Shows: eval metrics, winner info, top qualified miners in tables instead of raw text
|
|
71
|
+
|
|
72
|
+
- **Version command**: Check your CLI version
|
|
73
|
+
```bash
|
|
74
|
+
trajrl --version
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Usage Examples
|
|
78
|
+
|
|
79
|
+
### Quick network check
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
trajrl status
|
|
83
|
+
```
|
|
84
|
+
```
|
|
85
|
+
╭──────────────────── Network Status ────────────────────╮
|
|
86
|
+
│ Validators: 7 total, 7 active (seen <1h) │
|
|
87
|
+
│ LLM Models: zhipu/glm-5 (3), chutes/GLM-5-TEE (3) │
|
|
88
|
+
│ Latest Eval: 7h ago │
|
|
89
|
+
│ Submissions: 65 passed, 35 failed (last batch) │
|
|
90
|
+
╰────────────────────────────────────────────────────────╯
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### List validators
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
trajrl validators
|
|
97
|
+
```
|
|
98
|
+
```
|
|
99
|
+
Hotkey UID Version LLM Model Last Eval Last Seen
|
|
100
|
+
5Cd6h…sn11 29 0.2.7 chutes/zai-org/GLM-5… 7h ago 2m ago
|
|
101
|
+
5EcgNd…797f 221 0.2.7 zhipu/glm-5 10h ago 6m ago
|
|
102
|
+
...
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Inspect a miner
|
|
106
|
+
|
|
107
|
+
By hotkey:
|
|
108
|
+
```bash
|
|
109
|
+
trajrl miner 5HMgR6LnNqUAtaKRwa6bLF4Vy4KBf7TaxCLehyff9mWPhSHt
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Or by UID (v0.2.0+):
|
|
113
|
+
```bash
|
|
114
|
+
trajrl miner --uid 65
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Shows rank, qualification status, cost, scenario breakdown, per-validator reports, recent submissions, and ban records.
|
|
118
|
+
|
|
119
|
+
### View failed submissions
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
trajrl submissions --failed
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Investigate a validator's eval cycle
|
|
126
|
+
|
|
127
|
+
First, list recent eval cycles for a validator:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
trajrl eval-history 5Cd6h...
|
|
131
|
+
```
|
|
132
|
+
```
|
|
133
|
+
Eval IDs (5) — 5Cd6h…sn11
|
|
134
|
+
Eval ID Validator Block Logs Created
|
|
135
|
+
20260325_060012 5Cd6h…sn11 421890 12 3h ago
|
|
136
|
+
20260324_060008 5Cd6h…sn11 421530 11 1d ago
|
|
137
|
+
20260323_060015 5Cd6h…sn11 421170 13 2d ago
|
|
138
|
+
...
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Then fetch the full cycle log for a specific eval:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
trajrl cycle-log 5Cd6h... --eval-id 20260325_060012
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Or just grab the latest one:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
trajrl cycle-log 5Cd6h...
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The cycle log contains the complete eval cycle output: metagraph sync, miner enumeration, per-miner eval timing, WEIGHT RESULTS, and set_weights status.
|
|
154
|
+
|
|
155
|
+
> **Note:** Eval IDs are defined by each validator independently (typically a timestamp like `20260325_060012`). They are **not** globally unique — the same eval ID from different validators refers to different evaluation cycles. Always pair an eval ID with a specific validator hotkey.
|
|
156
|
+
|
|
157
|
+
### Filter eval logs
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
trajrl logs --type cycle --limit 5
|
|
161
|
+
trajrl logs --validator 5Cd6h... --type miner
|
|
162
|
+
trajrl logs --eval-id 20260324_000340
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### JSON output for agents
|
|
166
|
+
|
|
167
|
+
Pipe to any tool — JSON is automatic:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
trajrl validators | jq '.validators[].hotkey'
|
|
171
|
+
trajrl scores 5Cd6h... --json | python3 -c "
|
|
172
|
+
import sys, json
|
|
173
|
+
d = json.load(sys.stdin)
|
|
174
|
+
for e in d['entries'][:5]:
|
|
175
|
+
print(f\"{e['minerHotkey'][:12]} qual={e['qualified']} cost={e['costUsd']}\")
|
|
176
|
+
"
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Force JSON in an interactive terminal:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
trajrl miner 5HMgR6... --json
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## API Reference
|
|
186
|
+
|
|
187
|
+
All data comes from the [TrajectoryRL Public API](https://trajrl.com) — read-only, no authentication required.
|
|
188
|
+
|
|
189
|
+
| Endpoint | CLI Command |
|
|
190
|
+
|----------|-------------|
|
|
191
|
+
| `GET /api/validators` | `trajrl validators` |
|
|
192
|
+
| `GET /api/scores/by-validator?validator=` | `trajrl scores <hotkey>` |
|
|
193
|
+
| `GET /api/miners/:hotkey` | `trajrl miner <hotkey>` |
|
|
194
|
+
| `GET /api/miners/:hotkey/packs/:hash` | `trajrl pack <hotkey> <hash>` |
|
|
195
|
+
| `GET /api/submissions` | `trajrl submissions` |
|
|
196
|
+
| `GET /api/eval-logs` | `trajrl logs` |
|
|
197
|
+
| `GET /api/eval-logs?log_type=cycle` | `trajrl eval-history <hotkey>` |
|
|
198
|
+
| `GET /api/eval-logs` + GCS download | `trajrl cycle-log <hotkey>` |
|
|
199
|
+
|
|
200
|
+
## Development
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
git clone <repo> && cd trajrl
|
|
204
|
+
pip install -e .
|
|
205
|
+
trajrl --help
|
|
206
|
+
```
|
trajrl-0.2.0/README.md
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# trajrl
|
|
2
|
+
|
|
3
|
+
CLI for the [TrajectoryRL subnet](https://trajrl.com) (Bittensor SN11). Query live validator, miner, and evaluation data from the terminal.
|
|
4
|
+
|
|
5
|
+
Designed for AI agents (Claude Code, Cursor) and humans alike — outputs JSON when piped, Rich tables when interactive.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install trajrl
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Commands
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
trajrl status # Network health overview
|
|
17
|
+
trajrl validators # List all validators
|
|
18
|
+
trajrl scores <validator_hotkey> # Per-miner scores from a validator
|
|
19
|
+
trajrl scores --uid <uid> # Query by validator UID instead
|
|
20
|
+
trajrl miner <hotkey> # Miner detail + diagnostics
|
|
21
|
+
trajrl miner --uid <uid> # Query by miner UID instead
|
|
22
|
+
trajrl pack <hotkey> <pack_hash> # Pack evaluation detail
|
|
23
|
+
trajrl submissions [--failed] # Recent pack submissions
|
|
24
|
+
trajrl eval-history <validator> # List eval cycle IDs for a validator
|
|
25
|
+
trajrl eval-history <v> --from <date> # Filter by date range
|
|
26
|
+
trajrl cycle-log <validator> # Download and display a cycle log
|
|
27
|
+
trajrl cycle-log <v> --format summary # Show parsed summary tables
|
|
28
|
+
trajrl logs [--type cycle|miner] # Eval log archives
|
|
29
|
+
trajrl --version # Show CLI version
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Global Options
|
|
33
|
+
|
|
34
|
+
Every command accepts:
|
|
35
|
+
|
|
36
|
+
| Option | Description |
|
|
37
|
+
|--------|-------------|
|
|
38
|
+
| `--json` / `-j` | Force JSON output (auto-enabled when stdout is piped) |
|
|
39
|
+
| `--base-url URL` | Override API base (default: `https://trajrl.com`, env: `TRAJRL_BASE_URL`) |
|
|
40
|
+
| `--version` / `-v` | Show CLI version and exit |
|
|
41
|
+
|
|
42
|
+
### New in v0.2.0
|
|
43
|
+
|
|
44
|
+
- **UID support**: Query validators and miners by UID instead of hotkey
|
|
45
|
+
```bash
|
|
46
|
+
trajrl miner --uid 65 # Instead of full hotkey
|
|
47
|
+
trajrl scores --uid 221 # Query validator by UID
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
- **Date filtering**: Filter eval history by date range
|
|
51
|
+
```bash
|
|
52
|
+
trajrl eval-history 5Cd6h... --from 2026-03-25 --to 2026-03-26
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
- **Cycle log summary**: Parse cycle logs into structured tables
|
|
56
|
+
```bash
|
|
57
|
+
trajrl cycle-log 5Cd6h... --format summary
|
|
58
|
+
```
|
|
59
|
+
Shows: eval metrics, winner info, top qualified miners in tables instead of raw text
|
|
60
|
+
|
|
61
|
+
- **Version command**: Check your CLI version
|
|
62
|
+
```bash
|
|
63
|
+
trajrl --version
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Usage Examples
|
|
67
|
+
|
|
68
|
+
### Quick network check
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
trajrl status
|
|
72
|
+
```
|
|
73
|
+
```
|
|
74
|
+
╭──────────────────── Network Status ────────────────────╮
|
|
75
|
+
│ Validators: 7 total, 7 active (seen <1h) │
|
|
76
|
+
│ LLM Models: zhipu/glm-5 (3), chutes/GLM-5-TEE (3) │
|
|
77
|
+
│ Latest Eval: 7h ago │
|
|
78
|
+
│ Submissions: 65 passed, 35 failed (last batch) │
|
|
79
|
+
╰────────────────────────────────────────────────────────╯
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### List validators
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
trajrl validators
|
|
86
|
+
```
|
|
87
|
+
```
|
|
88
|
+
Hotkey UID Version LLM Model Last Eval Last Seen
|
|
89
|
+
5Cd6h…sn11 29 0.2.7 chutes/zai-org/GLM-5… 7h ago 2m ago
|
|
90
|
+
5EcgNd…797f 221 0.2.7 zhipu/glm-5 10h ago 6m ago
|
|
91
|
+
...
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Inspect a miner
|
|
95
|
+
|
|
96
|
+
By hotkey:
|
|
97
|
+
```bash
|
|
98
|
+
trajrl miner 5HMgR6LnNqUAtaKRwa6bLF4Vy4KBf7TaxCLehyff9mWPhSHt
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Or by UID (v0.2.0+):
|
|
102
|
+
```bash
|
|
103
|
+
trajrl miner --uid 65
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Shows rank, qualification status, cost, scenario breakdown, per-validator reports, recent submissions, and ban records.
|
|
107
|
+
|
|
108
|
+
### View failed submissions
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
trajrl submissions --failed
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Investigate a validator's eval cycle
|
|
115
|
+
|
|
116
|
+
First, list recent eval cycles for a validator:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
trajrl eval-history 5Cd6h...
|
|
120
|
+
```
|
|
121
|
+
```
|
|
122
|
+
Eval IDs (5) — 5Cd6h…sn11
|
|
123
|
+
Eval ID Validator Block Logs Created
|
|
124
|
+
20260325_060012 5Cd6h…sn11 421890 12 3h ago
|
|
125
|
+
20260324_060008 5Cd6h…sn11 421530 11 1d ago
|
|
126
|
+
20260323_060015 5Cd6h…sn11 421170 13 2d ago
|
|
127
|
+
...
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Then fetch the full cycle log for a specific eval:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
trajrl cycle-log 5Cd6h... --eval-id 20260325_060012
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Or just grab the latest one:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
trajrl cycle-log 5Cd6h...
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The cycle log contains the complete eval cycle output: metagraph sync, miner enumeration, per-miner eval timing, WEIGHT RESULTS, and set_weights status.
|
|
143
|
+
|
|
144
|
+
> **Note:** Eval IDs are defined by each validator independently (typically a timestamp like `20260325_060012`). They are **not** globally unique — the same eval ID from different validators refers to different evaluation cycles. Always pair an eval ID with a specific validator hotkey.
|
|
145
|
+
|
|
146
|
+
### Filter eval logs
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
trajrl logs --type cycle --limit 5
|
|
150
|
+
trajrl logs --validator 5Cd6h... --type miner
|
|
151
|
+
trajrl logs --eval-id 20260324_000340
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### JSON output for agents
|
|
155
|
+
|
|
156
|
+
Pipe to any tool — JSON is automatic:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
trajrl validators | jq '.validators[].hotkey'
|
|
160
|
+
trajrl scores 5Cd6h... --json | python3 -c "
|
|
161
|
+
import sys, json
|
|
162
|
+
d = json.load(sys.stdin)
|
|
163
|
+
for e in d['entries'][:5]:
|
|
164
|
+
print(f\"{e['minerHotkey'][:12]} qual={e['qualified']} cost={e['costUsd']}\")
|
|
165
|
+
"
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Force JSON in an interactive terminal:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
trajrl miner 5HMgR6... --json
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## API Reference
|
|
175
|
+
|
|
176
|
+
All data comes from the [TrajectoryRL Public API](https://trajrl.com) — read-only, no authentication required.
|
|
177
|
+
|
|
178
|
+
| Endpoint | CLI Command |
|
|
179
|
+
|----------|-------------|
|
|
180
|
+
| `GET /api/validators` | `trajrl validators` |
|
|
181
|
+
| `GET /api/scores/by-validator?validator=` | `trajrl scores <hotkey>` |
|
|
182
|
+
| `GET /api/miners/:hotkey` | `trajrl miner <hotkey>` |
|
|
183
|
+
| `GET /api/miners/:hotkey/packs/:hash` | `trajrl pack <hotkey> <hash>` |
|
|
184
|
+
| `GET /api/submissions` | `trajrl submissions` |
|
|
185
|
+
| `GET /api/eval-logs` | `trajrl logs` |
|
|
186
|
+
| `GET /api/eval-logs?log_type=cycle` | `trajrl eval-history <hotkey>` |
|
|
187
|
+
| `GET /api/eval-logs` + GCS download | `trajrl cycle-log <hotkey>` |
|
|
188
|
+
|
|
189
|
+
## Development
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
git clone <repo> && cd trajrl
|
|
193
|
+
pip install -e .
|
|
194
|
+
trajrl --help
|
|
195
|
+
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "trajrl"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "CLI for TrajectoryRL, Bittensor subnet 11 — agent-friendly access to live validator, miner, and evaluation data."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"typer>=0.9",
|
|
14
|
+
"httpx>=0.25",
|
|
15
|
+
"rich>=13.0",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.scripts]
|
|
19
|
+
trajrl = "trajrl.cli:app"
|
trajrl-0.2.0/setup.cfg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""Typed HTTP client for the TrajectoryRL public API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import gzip
|
|
6
|
+
import io
|
|
7
|
+
import tarfile
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import httpx
|
|
12
|
+
|
|
13
|
+
DEFAULT_BASE_URL = "https://trajrl.com"
|
|
14
|
+
_TIMEOUT = 30.0
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def extract_cycle_log(archive_bytes: bytes) -> str | None:
|
|
18
|
+
"""Extract validator.log text from a cycle log archive (tar.gz or gzip)."""
|
|
19
|
+
try:
|
|
20
|
+
buf = io.BytesIO(archive_bytes)
|
|
21
|
+
with tarfile.open(fileobj=buf, mode="r:gz") as tar:
|
|
22
|
+
for member in tar.getmembers():
|
|
23
|
+
if member.name.endswith("validator.log"):
|
|
24
|
+
f = tar.extractfile(member)
|
|
25
|
+
if f:
|
|
26
|
+
return f.read().decode("utf-8", errors="replace")
|
|
27
|
+
except Exception:
|
|
28
|
+
pass
|
|
29
|
+
try:
|
|
30
|
+
return gzip.decompress(archive_bytes).decode("utf-8", errors="replace")
|
|
31
|
+
except Exception:
|
|
32
|
+
pass
|
|
33
|
+
return archive_bytes.decode("utf-8", errors="replace")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class TrajRLClient:
|
|
38
|
+
base_url: str = DEFAULT_BASE_URL
|
|
39
|
+
_client: httpx.Client = field(init=False, repr=False)
|
|
40
|
+
|
|
41
|
+
def __post_init__(self) -> None:
|
|
42
|
+
self._client = httpx.Client(
|
|
43
|
+
base_url=self.base_url.rstrip("/"),
|
|
44
|
+
timeout=_TIMEOUT,
|
|
45
|
+
headers={"Accept": "application/json"},
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# -- endpoints ---------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
def validators(self) -> dict[str, Any]:
|
|
51
|
+
"""GET /api/validators"""
|
|
52
|
+
return self._get("/api/validators")
|
|
53
|
+
|
|
54
|
+
def scores_by_validator(self, validator: str | None = None, uid: int | None = None) -> dict[str, Any]:
|
|
55
|
+
"""GET /api/scores/by-validator?validator=<hotkey> or resolve UID to hotkey."""
|
|
56
|
+
if uid is not None:
|
|
57
|
+
# Resolve UID to hotkey via validators endpoint
|
|
58
|
+
validators_data = self.validators()
|
|
59
|
+
for vali in validators_data.get("validators", []):
|
|
60
|
+
if vali.get("uid") == uid:
|
|
61
|
+
validator = vali.get("hotkey")
|
|
62
|
+
if validator:
|
|
63
|
+
return self._get("/api/scores/by-validator", params={"validator": validator})
|
|
64
|
+
raise ValueError(f"Could not find validator with UID {uid}")
|
|
65
|
+
if validator is None:
|
|
66
|
+
raise ValueError("Either validator hotkey or uid must be provided")
|
|
67
|
+
return self._get("/api/scores/by-validator", params={"validator": validator})
|
|
68
|
+
|
|
69
|
+
def miner(self, hotkey: str | None = None, uid: int | None = None) -> dict[str, Any]:
|
|
70
|
+
"""GET /api/miners/:hotkey or resolve UID to hotkey first."""
|
|
71
|
+
if uid is not None:
|
|
72
|
+
# Resolve UID to hotkey via validators endpoint
|
|
73
|
+
validators_data = self.validators()
|
|
74
|
+
# Search through validator scores to find miner by UID
|
|
75
|
+
# This is a fallback - ideally the API would support UID lookup
|
|
76
|
+
for vali in validators_data.get("validators", []):
|
|
77
|
+
vali_key = vali.get("hotkey")
|
|
78
|
+
if vali_key:
|
|
79
|
+
try:
|
|
80
|
+
scores = self.scores_by_validator(vali_key)
|
|
81
|
+
for entry in scores.get("entries", []):
|
|
82
|
+
if entry.get("uid") == uid:
|
|
83
|
+
hotkey = entry.get("minerHotkey")
|
|
84
|
+
if hotkey:
|
|
85
|
+
return self._get(f"/api/miners/{hotkey}")
|
|
86
|
+
except Exception:
|
|
87
|
+
continue
|
|
88
|
+
raise ValueError(f"Could not resolve UID {uid} to a miner hotkey")
|
|
89
|
+
if hotkey is None:
|
|
90
|
+
raise ValueError("Either hotkey or uid must be provided")
|
|
91
|
+
return self._get(f"/api/miners/{hotkey}")
|
|
92
|
+
|
|
93
|
+
def pack(self, hotkey: str, pack_hash: str) -> dict[str, Any]:
|
|
94
|
+
"""GET /api/miners/:hotkey/packs/:packHash"""
|
|
95
|
+
return self._get(f"/api/miners/{hotkey}/packs/{pack_hash}")
|
|
96
|
+
|
|
97
|
+
def submissions(self, limit: int | None = None) -> dict[str, Any]:
|
|
98
|
+
"""GET /api/submissions"""
|
|
99
|
+
return self._get("/api/submissions", params=_compact({"limit": limit}))
|
|
100
|
+
|
|
101
|
+
def eval_logs(
|
|
102
|
+
self,
|
|
103
|
+
*,
|
|
104
|
+
validator: str | None = None,
|
|
105
|
+
miner: str | None = None,
|
|
106
|
+
log_type: str | None = None,
|
|
107
|
+
eval_id: str | None = None,
|
|
108
|
+
pack_hash: str | None = None,
|
|
109
|
+
from_date: str | None = None,
|
|
110
|
+
to_date: str | None = None,
|
|
111
|
+
limit: int | None = None,
|
|
112
|
+
offset: int | None = None,
|
|
113
|
+
) -> dict[str, Any]:
|
|
114
|
+
"""GET /api/eval-logs
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
from_date: ISO 8601 date/datetime string (e.g. "2026-03-25" or "2026-03-25T00:00:00Z")
|
|
118
|
+
to_date: ISO 8601 date/datetime string (e.g. "2026-03-26" or "2026-03-26T23:59:59Z")
|
|
119
|
+
"""
|
|
120
|
+
params = _compact({
|
|
121
|
+
"validator": validator,
|
|
122
|
+
"miner": miner,
|
|
123
|
+
"type": log_type,
|
|
124
|
+
"eval_id": eval_id,
|
|
125
|
+
"pack_hash": pack_hash,
|
|
126
|
+
"from": from_date,
|
|
127
|
+
"to": to_date,
|
|
128
|
+
"limit": limit,
|
|
129
|
+
"offset": offset,
|
|
130
|
+
})
|
|
131
|
+
return self._get("/api/eval-logs", params=params)
|
|
132
|
+
|
|
133
|
+
def cycle_log(
|
|
134
|
+
self,
|
|
135
|
+
validator: str,
|
|
136
|
+
*,
|
|
137
|
+
eval_id: str | None = None,
|
|
138
|
+
) -> dict[str, Any]:
|
|
139
|
+
"""Fetch the latest cycle log text for a validator.
|
|
140
|
+
|
|
141
|
+
Uses the eval-logs endpoint to find the cycle log entry, then
|
|
142
|
+
downloads and extracts the validator.log from the archive.
|
|
143
|
+
|
|
144
|
+
Returns ``{"log_entry": <metadata dict>, "text": <log text>}``.
|
|
145
|
+
Raises ``ValueError`` when no usable cycle log is found.
|
|
146
|
+
"""
|
|
147
|
+
params: dict[str, Any] = {"validator": validator, "log_type": "cycle"}
|
|
148
|
+
if eval_id is not None:
|
|
149
|
+
params["eval_id"] = eval_id
|
|
150
|
+
else:
|
|
151
|
+
params["limit"] = 5
|
|
152
|
+
|
|
153
|
+
data = self.eval_logs(**params)
|
|
154
|
+
logs = data.get("logs", [])
|
|
155
|
+
if not logs:
|
|
156
|
+
raise ValueError("No cycle logs found for this validator")
|
|
157
|
+
|
|
158
|
+
log_entry = logs[0]
|
|
159
|
+
gcs_url = log_entry.get("gcsUrl")
|
|
160
|
+
if not gcs_url:
|
|
161
|
+
raise ValueError("Cycle log has no download URL")
|
|
162
|
+
|
|
163
|
+
try:
|
|
164
|
+
resp = httpx.get(gcs_url, timeout=30, follow_redirects=True)
|
|
165
|
+
resp.raise_for_status()
|
|
166
|
+
except Exception as exc:
|
|
167
|
+
raise ValueError(f"Failed to download cycle log: {exc}") from exc
|
|
168
|
+
|
|
169
|
+
text = extract_cycle_log(resp.content)
|
|
170
|
+
if not text:
|
|
171
|
+
raise ValueError("Failed to extract validator.log from archive")
|
|
172
|
+
|
|
173
|
+
return {"log_entry": log_entry, "text": text}
|
|
174
|
+
|
|
175
|
+
# -- internal ----------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
def _get(self, path: str, params: dict | None = None) -> dict[str, Any]:
|
|
178
|
+
resp = self._client.get(path, params=params)
|
|
179
|
+
resp.raise_for_status()
|
|
180
|
+
return resp.json()
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _compact(d: dict) -> dict:
|
|
184
|
+
"""Remove None values from a dict."""
|
|
185
|
+
return {k: v for k, v in d.items() if v is not None}
|