hte-cli 0.1.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.
- hte_cli-0.1.0/.gitignore +43 -0
- hte_cli-0.1.0/PKG-INFO +82 -0
- hte_cli-0.1.0/README.md +57 -0
- hte_cli-0.1.0/pyproject.toml +47 -0
- hte_cli-0.1.0/src/hte_cli/__init__.py +24 -0
- hte_cli-0.1.0/src/hte_cli/__main__.py +6 -0
- hte_cli-0.1.0/src/hte_cli/api_client.py +228 -0
- hte_cli-0.1.0/src/hte_cli/cli.py +496 -0
- hte_cli-0.1.0/src/hte_cli/config.py +109 -0
- hte_cli-0.1.0/src/hte_cli/errors.py +91 -0
- hte_cli-0.1.0/src/hte_cli/events.py +128 -0
- hte_cli-0.1.0/src/hte_cli/runner.py +315 -0
hte_cli-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
.env
|
|
2
|
+
|
|
3
|
+
docs/build/
|
|
4
|
+
logs/
|
|
5
|
+
data/*
|
|
6
|
+
!data/keep
|
|
7
|
+
results/
|
|
8
|
+
docs/working-docs
|
|
9
|
+
|
|
10
|
+
# Python-generated files
|
|
11
|
+
__pycache__/
|
|
12
|
+
*.py[oc]
|
|
13
|
+
build/
|
|
14
|
+
dist/
|
|
15
|
+
wheels/
|
|
16
|
+
*.egg-info
|
|
17
|
+
|
|
18
|
+
# Virtual environments
|
|
19
|
+
.venv
|
|
20
|
+
|
|
21
|
+
# cloned repos
|
|
22
|
+
third-party/**
|
|
23
|
+
# Continuous Claude cache (local only)
|
|
24
|
+
.claude/cache/
|
|
25
|
+
.claude/tsc-cache/
|
|
26
|
+
.claude/hooks/node_modules/
|
|
27
|
+
.claude/settings.local.json
|
|
28
|
+
|
|
29
|
+
# Continuity ledgers (session-specific, not shared)
|
|
30
|
+
CONTINUITY_CLAUDE-*
|
|
31
|
+
|
|
32
|
+
# Thoughts directory (user-specific research and plans)
|
|
33
|
+
thoughts/
|
|
34
|
+
!thoughts/shared/
|
|
35
|
+
!thoughts/shared/.gitkeep
|
|
36
|
+
!thoughts/shared/plans/.gitkeep
|
|
37
|
+
!thoughts/shared/handoffs/.gitkeep
|
|
38
|
+
|
|
39
|
+
# Web UI
|
|
40
|
+
web/frontend/node_modules/
|
|
41
|
+
web/**/*.db
|
|
42
|
+
web/backups/
|
|
43
|
+
*.db
|
hte_cli-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hte-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Human Time-to-Completion Evaluation CLI
|
|
5
|
+
Project-URL: Homepage, https://github.com/sean-peters-au/lyptus-mono
|
|
6
|
+
Author: Lyptus Research
|
|
7
|
+
License: MIT
|
|
8
|
+
Keywords: cli,cybersecurity,evaluation,human-baseline
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Requires-Python: >=3.11
|
|
17
|
+
Requires-Dist: click>=8.0
|
|
18
|
+
Requires-Dist: httpx>=0.24
|
|
19
|
+
Requires-Dist: inspect-ai>=0.3.89
|
|
20
|
+
Requires-Dist: keyring>=24.0
|
|
21
|
+
Requires-Dist: platformdirs>=4.0
|
|
22
|
+
Requires-Dist: pydantic>=2.0
|
|
23
|
+
Requires-Dist: rich>=13.0
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# hte-cli
|
|
27
|
+
|
|
28
|
+
Human Time-to-Completion Evaluation CLI - A tool for running assigned cybersecurity tasks with timing and result tracking.
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Recommended (pipx)
|
|
34
|
+
pipx install hte-cli
|
|
35
|
+
|
|
36
|
+
# Or with pip
|
|
37
|
+
pip install hte-cli
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Quick Start
|
|
41
|
+
|
|
42
|
+
1. **Login** (get credentials from your coordinator):
|
|
43
|
+
```bash
|
|
44
|
+
hte-cli auth login
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
2. **View your assigned tasks**:
|
|
48
|
+
```bash
|
|
49
|
+
hte-cli tasks list
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
3. **Run a task**:
|
|
53
|
+
```bash
|
|
54
|
+
hte-cli tasks run
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Commands
|
|
58
|
+
|
|
59
|
+
- `hte-cli auth login` - Authenticate with the API
|
|
60
|
+
- `hte-cli auth status` - Check authentication status
|
|
61
|
+
- `hte-cli tasks list` - List your pending tasks
|
|
62
|
+
- `hte-cli tasks run [TASK_ID]` - Run a task (defaults to highest priority)
|
|
63
|
+
- `hte-cli tasks pull-images` - Pre-pull Docker images for upcoming tasks
|
|
64
|
+
- `hte-cli version` - Show version info
|
|
65
|
+
|
|
66
|
+
## Requirements
|
|
67
|
+
|
|
68
|
+
- Python 3.11+
|
|
69
|
+
- Docker (for running tasks)
|
|
70
|
+
|
|
71
|
+
## Configuration
|
|
72
|
+
|
|
73
|
+
Set `HTE_API_URL` environment variable to use a custom API endpoint:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
export HTE_API_URL="http://your-server.com/api/v1/cli"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Support
|
|
80
|
+
|
|
81
|
+
For issues, contact your study coordinator or open an issue at:
|
|
82
|
+
https://github.com/sean-peters-au/lyptus-mono
|
hte_cli-0.1.0/README.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# hte-cli
|
|
2
|
+
|
|
3
|
+
Human Time-to-Completion Evaluation CLI - A tool for running assigned cybersecurity tasks with timing and result tracking.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Recommended (pipx)
|
|
9
|
+
pipx install hte-cli
|
|
10
|
+
|
|
11
|
+
# Or with pip
|
|
12
|
+
pip install hte-cli
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
1. **Login** (get credentials from your coordinator):
|
|
18
|
+
```bash
|
|
19
|
+
hte-cli auth login
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
2. **View your assigned tasks**:
|
|
23
|
+
```bash
|
|
24
|
+
hte-cli tasks list
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
3. **Run a task**:
|
|
28
|
+
```bash
|
|
29
|
+
hte-cli tasks run
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Commands
|
|
33
|
+
|
|
34
|
+
- `hte-cli auth login` - Authenticate with the API
|
|
35
|
+
- `hte-cli auth status` - Check authentication status
|
|
36
|
+
- `hte-cli tasks list` - List your pending tasks
|
|
37
|
+
- `hte-cli tasks run [TASK_ID]` - Run a task (defaults to highest priority)
|
|
38
|
+
- `hte-cli tasks pull-images` - Pre-pull Docker images for upcoming tasks
|
|
39
|
+
- `hte-cli version` - Show version info
|
|
40
|
+
|
|
41
|
+
## Requirements
|
|
42
|
+
|
|
43
|
+
- Python 3.11+
|
|
44
|
+
- Docker (for running tasks)
|
|
45
|
+
|
|
46
|
+
## Configuration
|
|
47
|
+
|
|
48
|
+
Set `HTE_API_URL` environment variable to use a custom API endpoint:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
export HTE_API_URL="http://your-server.com/api/v1/cli"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Support
|
|
55
|
+
|
|
56
|
+
For issues, contact your study coordinator or open an issue at:
|
|
57
|
+
https://github.com/sean-peters-au/lyptus-mono
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "hte-cli"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Human Time-to-Completion Evaluation CLI"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
license = { text = "MIT" }
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "Lyptus Research" }
|
|
10
|
+
]
|
|
11
|
+
keywords = ["cli", "evaluation", "human-baseline", "cybersecurity"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 3 - Alpha",
|
|
14
|
+
"Environment :: Console",
|
|
15
|
+
"Intended Audience :: Science/Research",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
dependencies = [
|
|
23
|
+
"click>=8.0",
|
|
24
|
+
"httpx>=0.24",
|
|
25
|
+
"pydantic>=2.0",
|
|
26
|
+
"rich>=13.0",
|
|
27
|
+
"platformdirs>=4.0",
|
|
28
|
+
"keyring>=24.0",
|
|
29
|
+
"inspect-ai>=0.3.89",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.scripts]
|
|
33
|
+
hte-cli = "hte_cli:main"
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
Homepage = "https://github.com/sean-peters-au/lyptus-mono"
|
|
37
|
+
|
|
38
|
+
[build-system]
|
|
39
|
+
requires = ["hatchling"]
|
|
40
|
+
build-backend = "hatchling.build"
|
|
41
|
+
|
|
42
|
+
[tool.hatch.build.targets.wheel]
|
|
43
|
+
packages = ["src/hte_cli"]
|
|
44
|
+
|
|
45
|
+
[tool.ruff]
|
|
46
|
+
line-length = 100
|
|
47
|
+
target-version = "py311"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""HTE-CLI: Human Time-to-Completion Evaluation CLI.
|
|
2
|
+
|
|
3
|
+
A standalone CLI for experts to run assigned tasks via Docker + Inspect's human_cli,
|
|
4
|
+
with results synced back to the web API.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
__version__ = "0.1.0"
|
|
10
|
+
|
|
11
|
+
# Minimum API version required
|
|
12
|
+
MIN_API_VERSION = "0.1.0"
|
|
13
|
+
|
|
14
|
+
# API URL - configurable via HTE_API_URL env var for custom deployments
|
|
15
|
+
# Default points to production deployment
|
|
16
|
+
_DEFAULT_API_URL = "https://hte.lyptus.dev/api/v1/cli"
|
|
17
|
+
API_BASE_URL = os.environ.get("HTE_API_URL", _DEFAULT_API_URL)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def main():
|
|
21
|
+
"""Entry point for hte-cli command."""
|
|
22
|
+
from hte_cli.cli import cli
|
|
23
|
+
|
|
24
|
+
cli()
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"""HTTP API client for HTE web backend.
|
|
2
|
+
|
|
3
|
+
Handles authentication, retry logic, and API calls.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import base64
|
|
7
|
+
import logging
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
import httpx
|
|
12
|
+
|
|
13
|
+
from hte_cli import API_BASE_URL, __version__
|
|
14
|
+
from hte_cli.config import Config
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
# Timeouts
|
|
19
|
+
DEFAULT_TIMEOUT = 30.0 # seconds
|
|
20
|
+
UPLOAD_TIMEOUT = 300.0 # 5 minutes for large eval log uploads
|
|
21
|
+
|
|
22
|
+
# Retry configuration
|
|
23
|
+
MAX_RETRIES = 3
|
|
24
|
+
RETRY_DELAYS = [1, 5, 30] # seconds
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class APIError(Exception):
|
|
28
|
+
"""API request error."""
|
|
29
|
+
|
|
30
|
+
def __init__(self, message: str, status_code: int | None = None):
|
|
31
|
+
super().__init__(message)
|
|
32
|
+
self.status_code = status_code
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class APIClient:
|
|
36
|
+
"""HTTP client for HTE API."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, config: Config):
|
|
39
|
+
self.config = config
|
|
40
|
+
self.base_url = config.api_url or API_BASE_URL
|
|
41
|
+
self._client: httpx.Client | None = None
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def client(self) -> httpx.Client:
|
|
45
|
+
"""Get or create HTTP client."""
|
|
46
|
+
if self._client is None:
|
|
47
|
+
self._client = httpx.Client(
|
|
48
|
+
base_url=self.base_url,
|
|
49
|
+
timeout=DEFAULT_TIMEOUT,
|
|
50
|
+
headers={
|
|
51
|
+
"User-Agent": f"hte-cli/{__version__}",
|
|
52
|
+
"X-CLI-Version": __version__,
|
|
53
|
+
},
|
|
54
|
+
)
|
|
55
|
+
return self._client
|
|
56
|
+
|
|
57
|
+
def _get_auth_headers(self) -> dict[str, str]:
|
|
58
|
+
"""Get authentication headers."""
|
|
59
|
+
if not self.config.api_key:
|
|
60
|
+
raise APIError("Not authenticated. Run: hte-cli auth login")
|
|
61
|
+
return {"Authorization": f"Bearer {self.config.api_key}"}
|
|
62
|
+
|
|
63
|
+
def _handle_response(self, response: httpx.Response) -> Any:
|
|
64
|
+
"""Handle API response, raising appropriate errors."""
|
|
65
|
+
if response.status_code == 401:
|
|
66
|
+
raise APIError("Authentication failed. Run: hte-cli auth login", 401)
|
|
67
|
+
|
|
68
|
+
if response.status_code == 403:
|
|
69
|
+
raise APIError("Access denied", 403)
|
|
70
|
+
|
|
71
|
+
if response.status_code == 404:
|
|
72
|
+
raise APIError("Resource not found", 404)
|
|
73
|
+
|
|
74
|
+
if response.status_code == 413:
|
|
75
|
+
raise APIError("Upload too large (max 200MB)", 413)
|
|
76
|
+
|
|
77
|
+
if response.status_code == 426:
|
|
78
|
+
raise APIError(
|
|
79
|
+
"CLI version too old. Run: pip install --upgrade hte-cli",
|
|
80
|
+
426,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
if response.status_code >= 400:
|
|
84
|
+
try:
|
|
85
|
+
detail = response.json().get("detail", response.text)
|
|
86
|
+
except Exception:
|
|
87
|
+
detail = response.text
|
|
88
|
+
raise APIError(f"API error: {detail}", response.status_code)
|
|
89
|
+
|
|
90
|
+
if response.status_code == 204:
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
return response.json()
|
|
94
|
+
|
|
95
|
+
def get(self, path: str, **kwargs) -> Any:
|
|
96
|
+
"""Make GET request."""
|
|
97
|
+
headers = self._get_auth_headers()
|
|
98
|
+
headers.update(kwargs.pop("headers", {}))
|
|
99
|
+
|
|
100
|
+
response = self.client.get(path, headers=headers, **kwargs)
|
|
101
|
+
return self._handle_response(response)
|
|
102
|
+
|
|
103
|
+
def post(self, path: str, json: dict | None = None, **kwargs) -> Any:
|
|
104
|
+
"""Make POST request with retry for transient failures."""
|
|
105
|
+
headers = self._get_auth_headers()
|
|
106
|
+
headers.update(kwargs.pop("headers", {}))
|
|
107
|
+
|
|
108
|
+
timeout = kwargs.pop("timeout", DEFAULT_TIMEOUT)
|
|
109
|
+
|
|
110
|
+
last_error = None
|
|
111
|
+
for attempt, delay in enumerate(RETRY_DELAYS):
|
|
112
|
+
try:
|
|
113
|
+
response = self.client.post(
|
|
114
|
+
path,
|
|
115
|
+
json=json,
|
|
116
|
+
headers=headers,
|
|
117
|
+
timeout=timeout,
|
|
118
|
+
**kwargs,
|
|
119
|
+
)
|
|
120
|
+
return self._handle_response(response)
|
|
121
|
+
except httpx.TimeoutException as e:
|
|
122
|
+
last_error = APIError(f"Request timed out: {e}")
|
|
123
|
+
logger.warning(f"Attempt {attempt + 1} failed: timeout. Retrying in {delay}s...")
|
|
124
|
+
except httpx.NetworkError as e:
|
|
125
|
+
last_error = APIError(f"Network error: {e}")
|
|
126
|
+
logger.warning(f"Attempt {attempt + 1} failed: network. Retrying in {delay}s...")
|
|
127
|
+
except APIError as e:
|
|
128
|
+
# Don't retry client errors (4xx)
|
|
129
|
+
if e.status_code and 400 <= e.status_code < 500:
|
|
130
|
+
raise
|
|
131
|
+
last_error = e
|
|
132
|
+
logger.warning(f"Attempt {attempt + 1} failed: {e}. Retrying in {delay}s...")
|
|
133
|
+
|
|
134
|
+
import time
|
|
135
|
+
|
|
136
|
+
time.sleep(delay)
|
|
137
|
+
|
|
138
|
+
raise last_error or APIError("Request failed after retries")
|
|
139
|
+
|
|
140
|
+
def get_raw(self, path: str, **kwargs) -> bytes:
|
|
141
|
+
"""Make GET request returning raw bytes (for file downloads)."""
|
|
142
|
+
headers = self._get_auth_headers()
|
|
143
|
+
headers.update(kwargs.pop("headers", {}))
|
|
144
|
+
|
|
145
|
+
response = self.client.get(path, headers=headers, **kwargs)
|
|
146
|
+
|
|
147
|
+
if response.status_code >= 400:
|
|
148
|
+
self._handle_response(response) # Will raise
|
|
149
|
+
|
|
150
|
+
return response.content
|
|
151
|
+
|
|
152
|
+
def close(self) -> None:
|
|
153
|
+
"""Close the HTTP client."""
|
|
154
|
+
if self._client:
|
|
155
|
+
self._client.close()
|
|
156
|
+
self._client = None
|
|
157
|
+
|
|
158
|
+
# =========================================================================
|
|
159
|
+
# API Methods
|
|
160
|
+
# =========================================================================
|
|
161
|
+
|
|
162
|
+
def exchange_code_for_token(self, code: str) -> dict:
|
|
163
|
+
"""Exchange one-time code for API key."""
|
|
164
|
+
# This endpoint doesn't require auth
|
|
165
|
+
response = self.client.post(
|
|
166
|
+
"/token",
|
|
167
|
+
json={"code": code},
|
|
168
|
+
headers={"X-CLI-Version": __version__},
|
|
169
|
+
)
|
|
170
|
+
return self._handle_response(response)
|
|
171
|
+
|
|
172
|
+
def get_assignments(self) -> list[dict]:
|
|
173
|
+
"""Get pending assignments for current user."""
|
|
174
|
+
return self.get("/assignments")
|
|
175
|
+
|
|
176
|
+
def get_assignment_files(self, assignment_id: str) -> bytes:
|
|
177
|
+
"""Download task files as zip."""
|
|
178
|
+
return self.get_raw(f"/assignments/{assignment_id}/files")
|
|
179
|
+
|
|
180
|
+
def get_assignment_compose(self, assignment_id: str) -> str:
|
|
181
|
+
"""Get compose.yaml content."""
|
|
182
|
+
content = self.get_raw(f"/assignments/{assignment_id}/compose")
|
|
183
|
+
return content.decode("utf-8")
|
|
184
|
+
|
|
185
|
+
def start_session(self, assignment_id: str) -> dict:
|
|
186
|
+
"""Start a session for an assignment.
|
|
187
|
+
|
|
188
|
+
Returns session info including session_id.
|
|
189
|
+
If session already exists, returns that session.
|
|
190
|
+
"""
|
|
191
|
+
return self.post(f"/assignments/{assignment_id}/start")
|
|
192
|
+
|
|
193
|
+
def post_event(self, session_id: str, event_type: str, event_data: dict | None = None) -> dict:
|
|
194
|
+
"""Post a session event."""
|
|
195
|
+
return self.post(
|
|
196
|
+
f"/sessions/{session_id}/events",
|
|
197
|
+
json={
|
|
198
|
+
"event_type": event_type,
|
|
199
|
+
"event_data": event_data,
|
|
200
|
+
"client_timestamp": datetime.utcnow().isoformat() + "Z",
|
|
201
|
+
},
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
def upload_result(
|
|
205
|
+
self,
|
|
206
|
+
session_id: str,
|
|
207
|
+
answer: str,
|
|
208
|
+
client_active_seconds: float,
|
|
209
|
+
eval_log_bytes: bytes | None = None,
|
|
210
|
+
score: float | None = None,
|
|
211
|
+
score_binarized: int | None = None,
|
|
212
|
+
) -> dict:
|
|
213
|
+
"""Upload task result with optional eval log."""
|
|
214
|
+
payload = {
|
|
215
|
+
"answer": answer,
|
|
216
|
+
"client_active_seconds": client_active_seconds,
|
|
217
|
+
"score": score,
|
|
218
|
+
"score_binarized": score_binarized,
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if eval_log_bytes:
|
|
222
|
+
payload["eval_log_base64"] = base64.b64encode(eval_log_bytes).decode("ascii")
|
|
223
|
+
|
|
224
|
+
return self.post(
|
|
225
|
+
f"/sessions/{session_id}/result",
|
|
226
|
+
json=payload,
|
|
227
|
+
timeout=UPLOAD_TIMEOUT,
|
|
228
|
+
)
|