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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: rio-cli
3
- Version: 0.1.0
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
- Rio reads `~/.config/rio/config.toml` for the AI engine URL and API key:
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
- Rio reads `~/.config/rio/config.toml` for the AI engine URL and API key:
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "rio-cli"
3
- version = "0.1.0"
3
+ version = "0.2.1"
4
4
  description = "Command-line client for Rio, an automated code review tool."
5
5
  readme = "README.md"
6
6
  license = "ISC"
@@ -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 = "http://localhost:8000"
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
- headers["Authorization"] = f"Bearer {api_key}"
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