rio-cli 0.1.0__tar.gz → 0.2.1__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.
- {rio_cli-0.1.0 → rio_cli-0.2.1}/PKG-INFO +16 -5
- {rio_cli-0.1.0 → rio_cli-0.2.1}/README.md +15 -4
- {rio_cli-0.1.0 → rio_cli-0.2.1}/pyproject.toml +1 -1
- rio_cli-0.2.1/rio_cli/auth.py +72 -0
- {rio_cli-0.1.0 → rio_cli-0.2.1}/rio_cli/config.py +5 -1
- {rio_cli-0.1.0 → rio_cli-0.2.1}/rio_cli/main.py +6 -0
- {rio_cli-0.1.0 → rio_cli-0.2.1}/rio_cli/post.py +12 -5
- {rio_cli-0.1.0 → rio_cli-0.2.1}/.gitignore +0 -0
- {rio_cli-0.1.0 → rio_cli-0.2.1}/LICENSE +0 -0
- {rio_cli-0.1.0 → rio_cli-0.2.1}/rio_cli/__init__.py +0 -0
- {rio_cli-0.1.0 → rio_cli-0.2.1}/rio_cli/git.py +0 -0
- {rio_cli-0.1.0 → rio_cli-0.2.1}/rio_cli/output.py +0 -0
- {rio_cli-0.1.0 → rio_cli-0.2.1}/rio_cli/rio_config.py +0 -0
- {rio_cli-0.1.0 → rio_cli-0.2.1}/rio_cli/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: rio-cli
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Command-line client for Rio, an automated code review tool.
|
|
5
5
|
Project-URL: Homepage, https://github.com/JayTheCoder77/rio
|
|
6
6
|
Project-URL: Repository, https://github.com/JayTheCoder77/rio
|
|
@@ -29,6 +29,18 @@ pip install rio-cli
|
|
|
29
29
|
|
|
30
30
|
This installs the `rio` command.
|
|
31
31
|
|
|
32
|
+
## Authentication
|
|
33
|
+
|
|
34
|
+
Before using `rio review`, authenticate with a Rio API key (create one on your Rio
|
|
35
|
+
dashboard's API keys page):
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
rio auth
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
This prompts for your API key, validates it against your Rio instance, and saves it to
|
|
42
|
+
`~/.config/rio/config.toml` with restricted (`0600`) file permissions.
|
|
43
|
+
|
|
32
44
|
## Usage
|
|
33
45
|
|
|
34
46
|
```sh
|
|
@@ -50,7 +62,9 @@ rio review --staged --include-untracked
|
|
|
50
62
|
|
|
51
63
|
## Configuration
|
|
52
64
|
|
|
53
|
-
|
|
65
|
+
`rio auth` manages `~/.config/rio/config.toml` for you. To point the CLI at a
|
|
66
|
+
self-hosted `ai-engine` instance instead of the default (`http://localhost:8000`), edit
|
|
67
|
+
the `[api] url` value in that file, or set it before running `rio auth`:
|
|
54
68
|
|
|
55
69
|
```toml
|
|
56
70
|
[api]
|
|
@@ -58,9 +72,6 @@ url = "https://your-rio-instance.example.com"
|
|
|
58
72
|
api_key = "your-api-key"
|
|
59
73
|
```
|
|
60
74
|
|
|
61
|
-
If no config file is found, the CLI defaults to `http://localhost:8000` for local
|
|
62
|
-
development against a self-hosted `ai-engine`.
|
|
63
|
-
|
|
64
75
|
## License
|
|
65
76
|
|
|
66
77
|
[ISC](LICENSE) © 2026 JayTheCoder77
|
|
@@ -12,6 +12,18 @@ pip install rio-cli
|
|
|
12
12
|
|
|
13
13
|
This installs the `rio` command.
|
|
14
14
|
|
|
15
|
+
## Authentication
|
|
16
|
+
|
|
17
|
+
Before using `rio review`, authenticate with a Rio API key (create one on your Rio
|
|
18
|
+
dashboard's API keys page):
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
rio auth
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
This prompts for your API key, validates it against your Rio instance, and saves it to
|
|
25
|
+
`~/.config/rio/config.toml` with restricted (`0600`) file permissions.
|
|
26
|
+
|
|
15
27
|
## Usage
|
|
16
28
|
|
|
17
29
|
```sh
|
|
@@ -33,7 +45,9 @@ rio review --staged --include-untracked
|
|
|
33
45
|
|
|
34
46
|
## Configuration
|
|
35
47
|
|
|
36
|
-
|
|
48
|
+
`rio auth` manages `~/.config/rio/config.toml` for you. To point the CLI at a
|
|
49
|
+
self-hosted `ai-engine` instance instead of the default (`http://localhost:8000`), edit
|
|
50
|
+
the `[api] url` value in that file, or set it before running `rio auth`:
|
|
37
51
|
|
|
38
52
|
```toml
|
|
39
53
|
[api]
|
|
@@ -41,9 +55,6 @@ url = "https://your-rio-instance.example.com"
|
|
|
41
55
|
api_key = "your-api-key"
|
|
42
56
|
```
|
|
43
57
|
|
|
44
|
-
If no config file is found, the CLI defaults to `http://localhost:8000` for local
|
|
45
|
-
development against a self-hosted `ai-engine`.
|
|
46
|
-
|
|
47
58
|
## License
|
|
48
59
|
|
|
49
60
|
[ISC](LICENSE) © 2026 JayTheCoder77
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import stat
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
import tomllib
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from rio_cli.config import CONFIG_PATH, get_ai_engine_url
|
|
9
|
+
from rio_cli.utils import _fail
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _load_existing_config() -> dict:
|
|
13
|
+
if not CONFIG_PATH.exists():
|
|
14
|
+
return {}
|
|
15
|
+
try:
|
|
16
|
+
with CONFIG_PATH.open("rb") as f:
|
|
17
|
+
return tomllib.load(f)
|
|
18
|
+
except tomllib.TOMLDecodeError:
|
|
19
|
+
return {}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _write_config(data: dict) -> None:
|
|
23
|
+
CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
24
|
+
|
|
25
|
+
api_section = data.get("api", {})
|
|
26
|
+
lines = ["[api]"]
|
|
27
|
+
if "url" in api_section:
|
|
28
|
+
lines.append(f'url = "{api_section["url"]}"')
|
|
29
|
+
lines.append(f'api_key = "{api_section["api_key"]}"')
|
|
30
|
+
|
|
31
|
+
CONFIG_PATH.write_text("\n".join(lines) + "\n")
|
|
32
|
+
# This file holds a real credential — don't rely on the default umask.
|
|
33
|
+
os.chmod(CONFIG_PATH, stat.S_IRUSR | stat.S_IWUSR) # 0o600
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _validate_api_key(api_key: str, ai_engine_url: str) -> None:
|
|
37
|
+
try:
|
|
38
|
+
response = httpx.get(
|
|
39
|
+
f"{ai_engine_url}/v1/me",
|
|
40
|
+
headers={"Authorization": f"Bearer {api_key}"},
|
|
41
|
+
timeout=15.0,
|
|
42
|
+
)
|
|
43
|
+
except httpx.ConnectError:
|
|
44
|
+
_fail(f"Error: could not connect to ai-engine at {ai_engine_url}.")
|
|
45
|
+
except httpx.TimeoutException:
|
|
46
|
+
_fail("Error: ai-engine request timed out.")
|
|
47
|
+
|
|
48
|
+
if response.status_code == 401:
|
|
49
|
+
_fail("Error: invalid API key. Create one at your Rio dashboard's API keys page.")
|
|
50
|
+
if response.status_code != 200:
|
|
51
|
+
_fail(f"Error: ai-engine returned {response.status_code}: {response.text}")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def auth() -> None:
|
|
55
|
+
"""Authenticate the CLI with a Rio API key, created via the Rio dashboard."""
|
|
56
|
+
existing = _load_existing_config()
|
|
57
|
+
ai_engine_url = existing.get("api", {}).get("url") or get_ai_engine_url()
|
|
58
|
+
|
|
59
|
+
api_key = typer.prompt(
|
|
60
|
+
"Paste your Rio API key (create one at your Rio dashboard's API keys page)",
|
|
61
|
+
hide_input=True,
|
|
62
|
+
).strip()
|
|
63
|
+
|
|
64
|
+
if not api_key:
|
|
65
|
+
_fail("Error: no API key provided.")
|
|
66
|
+
|
|
67
|
+
_validate_api_key(api_key, ai_engine_url)
|
|
68
|
+
|
|
69
|
+
existing["api"] = {**existing.get("api", {}), "api_key": api_key, "url": ai_engine_url}
|
|
70
|
+
_write_config(existing)
|
|
71
|
+
|
|
72
|
+
typer.echo(f"Authenticated. Config saved to {CONFIG_PATH}")
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
import os
|
|
1
2
|
from pathlib import Path
|
|
2
3
|
|
|
3
4
|
import tomllib
|
|
4
5
|
|
|
5
6
|
from rio_cli.utils import _fail
|
|
6
7
|
|
|
7
|
-
DEFAULT_AI_ENGINE_URL =
|
|
8
|
+
DEFAULT_AI_ENGINE_URL = os.environ.get(
|
|
9
|
+
"RIO_API_URL", "https://rio-ai-engine.onrender.com"
|
|
10
|
+
)
|
|
11
|
+
# DEFAULT_AI_ENGINE_URL = "http://localhost:8000"
|
|
8
12
|
CONFIG_PATH = Path.home() / ".config" / "rio" / "config.toml"
|
|
9
13
|
|
|
10
14
|
def get_ai_engine_url() -> str:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import typer
|
|
2
2
|
|
|
3
|
+
from rio_cli.auth import auth as _auth
|
|
3
4
|
from rio_cli.git import (
|
|
4
5
|
_get_combined_diff,
|
|
5
6
|
_get_committed_diff,
|
|
@@ -22,6 +23,11 @@ app = typer.Typer()
|
|
|
22
23
|
def callback():
|
|
23
24
|
pass
|
|
24
25
|
|
|
26
|
+
@app.command()
|
|
27
|
+
def auth():
|
|
28
|
+
"""Authenticate the CLI with a Rio API key."""
|
|
29
|
+
_auth()
|
|
30
|
+
|
|
25
31
|
def _resolve_diff(
|
|
26
32
|
staged: bool,
|
|
27
33
|
uncommitted: bool,
|
|
@@ -7,11 +7,12 @@ from rio_cli.utils import _fail
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def _post_review(diff_text : str) -> list[Finding]:
|
|
10
|
-
headers : dict = {}
|
|
11
10
|
api_key = get_api_key()
|
|
12
|
-
if api_key:
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
if not api_key:
|
|
12
|
+
_fail("Error: not authenticated. Run `rio auth` first.")
|
|
13
|
+
|
|
14
|
+
headers = {"Authorization": f"Bearer {api_key}"}
|
|
15
|
+
|
|
15
16
|
rio_config = load_rio_config()
|
|
16
17
|
ai_engine_url = get_ai_engine_url()
|
|
17
18
|
try:
|
|
@@ -26,7 +27,13 @@ def _post_review(diff_text : str) -> list[Finding]:
|
|
|
26
27
|
|
|
27
28
|
except httpx.TimeoutException:
|
|
28
29
|
_fail("Error: ai-engine request timed out.")
|
|
29
|
-
|
|
30
|
+
|
|
31
|
+
if response.status_code == 401:
|
|
32
|
+
_fail("Error: not authenticated. Run `rio auth` first.")
|
|
33
|
+
if response.status_code == 412:
|
|
34
|
+
_fail(f"Error: {response.json().get('detail', response.text)}")
|
|
35
|
+
if response.status_code == 422:
|
|
36
|
+
_fail(f"Error: {response.json().get('detail', response.text)}")
|
|
30
37
|
if response.status_code != 200:
|
|
31
38
|
_fail(f"Error: ai-engine returned {response.status_code}: {response.text}")
|
|
32
39
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|