janexai 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.
- janexai-0.1.0/PKG-INFO +147 -0
- janexai-0.1.0/README.md +118 -0
- janexai-0.1.0/janex/__init__.py +3 -0
- janexai-0.1.0/janex/main.py +402 -0
- janexai-0.1.0/janexai.egg-info/PKG-INFO +147 -0
- janexai-0.1.0/janexai.egg-info/SOURCES.txt +10 -0
- janexai-0.1.0/janexai.egg-info/dependency_links.txt +1 -0
- janexai-0.1.0/janexai.egg-info/entry_points.txt +2 -0
- janexai-0.1.0/janexai.egg-info/requires.txt +3 -0
- janexai-0.1.0/janexai.egg-info/top_level.txt +1 -0
- janexai-0.1.0/pyproject.toml +48 -0
- janexai-0.1.0/setup.cfg +4 -0
janexai-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: janexai
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: AI-powered code security and quality analysis CLI
|
|
5
|
+
Author-email: JanexAI Team <hello@janex.ai>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://janex.ai
|
|
8
|
+
Project-URL: Documentation, https://docs.janex.ai
|
|
9
|
+
Project-URL: Repository, https://github.com/janexai/janex-cli
|
|
10
|
+
Project-URL: Issues, https://github.com/janexai/janex-cli/issues
|
|
11
|
+
Keywords: security,code-analysis,ai,llm,cli
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Security
|
|
23
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
Requires-Dist: typer>=0.9.0
|
|
27
|
+
Requires-Dist: rich>=13.0.0
|
|
28
|
+
Requires-Dist: requests>=2.28.0
|
|
29
|
+
|
|
30
|
+
# JanexAI CLI
|
|
31
|
+
|
|
32
|
+
AI-powered code security and quality analysis from your terminal.
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install janexai
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Quick Start
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Scan a public repository
|
|
44
|
+
janex scan https://github.com/user/repo
|
|
45
|
+
|
|
46
|
+
# Or use owner/repo format
|
|
47
|
+
janex scan user/repo
|
|
48
|
+
|
|
49
|
+
# Save report to file
|
|
50
|
+
janex scan user/repo -o report.json
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Authentication
|
|
54
|
+
|
|
55
|
+
For private repositories or higher rate limits, authenticate with your API token:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
janex login --token YOUR_API_TOKEN
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Get your token at [janex.ai/settings](https://janex.ai/settings)
|
|
62
|
+
|
|
63
|
+
## Commands
|
|
64
|
+
|
|
65
|
+
### `janex scan`
|
|
66
|
+
|
|
67
|
+
Scan a repository for security and quality issues.
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
janex scan <repo> [OPTIONS]
|
|
71
|
+
|
|
72
|
+
Arguments:
|
|
73
|
+
repo GitHub repo URL or owner/repo format
|
|
74
|
+
|
|
75
|
+
Options:
|
|
76
|
+
-o, --output Save report to JSON file
|
|
77
|
+
--no-claude Skip Claude AI analysis (faster)
|
|
78
|
+
-v, --verbose Verbose output
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### `janex login`
|
|
82
|
+
|
|
83
|
+
Authenticate with your API token.
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
janex login --token YOUR_TOKEN
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### `janex config`
|
|
90
|
+
|
|
91
|
+
Configure CLI settings.
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# Show current config
|
|
95
|
+
janex config --show
|
|
96
|
+
|
|
97
|
+
# Set custom API URL (for self-hosted)
|
|
98
|
+
janex config --api-url https://your-server.com
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### `janex version`
|
|
102
|
+
|
|
103
|
+
Show CLI version.
|
|
104
|
+
|
|
105
|
+
## Environment Variables
|
|
106
|
+
|
|
107
|
+
- `JANEX_API_TOKEN` - API token (alternative to `janex login`)
|
|
108
|
+
- `JANEX_API_URL` - Custom API URL (for self-hosted instances)
|
|
109
|
+
|
|
110
|
+
## Example Output
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
╭──────────────── JanexAI ─────────────────╮
|
|
114
|
+
│ Scanning repository: vercel/next.js │
|
|
115
|
+
╰──────────────────────────────────────────╯
|
|
116
|
+
|
|
117
|
+
✓ Repository ingested (2,847 files)
|
|
118
|
+
✓ Analysis complete!
|
|
119
|
+
|
|
120
|
+
Scores
|
|
121
|
+
┏━━━━━━━━━━┳━━━━━━━━┓
|
|
122
|
+
┃ Category ┃ Score ┃
|
|
123
|
+
┡━━━━━━━━━━╇━━━━━━━━┩
|
|
124
|
+
│ Security │ 85/100 │
|
|
125
|
+
│ Quality │ 78/100 │
|
|
126
|
+
└──────────┴────────┘
|
|
127
|
+
|
|
128
|
+
Security Findings
|
|
129
|
+
┏━━━━━━━━━━┳━━━━━━━┓
|
|
130
|
+
┃ Severity ┃ Count ┃
|
|
131
|
+
┡━━━━━━━━━━╇━━━━━━━┩
|
|
132
|
+
│ Medium │ 3 │
|
|
133
|
+
│ Low │ 12 │
|
|
134
|
+
└──────────┴───────┘
|
|
135
|
+
|
|
136
|
+
✓ No critical security issues found
|
|
137
|
+
|
|
138
|
+
╭─────────── Architecture Summary ───────────╮
|
|
139
|
+
│ Next.js app with App Router architecture. │
|
|
140
|
+
│ Uses React Server Components with proper │
|
|
141
|
+
│ data fetching patterns... │
|
|
142
|
+
╰────────────────────────────────────────────╯
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
MIT
|
janexai-0.1.0/README.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# JanexAI CLI
|
|
2
|
+
|
|
3
|
+
AI-powered code security and quality analysis from your terminal.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install janexai
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Scan a public repository
|
|
15
|
+
janex scan https://github.com/user/repo
|
|
16
|
+
|
|
17
|
+
# Or use owner/repo format
|
|
18
|
+
janex scan user/repo
|
|
19
|
+
|
|
20
|
+
# Save report to file
|
|
21
|
+
janex scan user/repo -o report.json
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Authentication
|
|
25
|
+
|
|
26
|
+
For private repositories or higher rate limits, authenticate with your API token:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
janex login --token YOUR_API_TOKEN
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Get your token at [janex.ai/settings](https://janex.ai/settings)
|
|
33
|
+
|
|
34
|
+
## Commands
|
|
35
|
+
|
|
36
|
+
### `janex scan`
|
|
37
|
+
|
|
38
|
+
Scan a repository for security and quality issues.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
janex scan <repo> [OPTIONS]
|
|
42
|
+
|
|
43
|
+
Arguments:
|
|
44
|
+
repo GitHub repo URL or owner/repo format
|
|
45
|
+
|
|
46
|
+
Options:
|
|
47
|
+
-o, --output Save report to JSON file
|
|
48
|
+
--no-claude Skip Claude AI analysis (faster)
|
|
49
|
+
-v, --verbose Verbose output
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### `janex login`
|
|
53
|
+
|
|
54
|
+
Authenticate with your API token.
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
janex login --token YOUR_TOKEN
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### `janex config`
|
|
61
|
+
|
|
62
|
+
Configure CLI settings.
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# Show current config
|
|
66
|
+
janex config --show
|
|
67
|
+
|
|
68
|
+
# Set custom API URL (for self-hosted)
|
|
69
|
+
janex config --api-url https://your-server.com
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### `janex version`
|
|
73
|
+
|
|
74
|
+
Show CLI version.
|
|
75
|
+
|
|
76
|
+
## Environment Variables
|
|
77
|
+
|
|
78
|
+
- `JANEX_API_TOKEN` - API token (alternative to `janex login`)
|
|
79
|
+
- `JANEX_API_URL` - Custom API URL (for self-hosted instances)
|
|
80
|
+
|
|
81
|
+
## Example Output
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
╭──────────────── JanexAI ─────────────────╮
|
|
85
|
+
│ Scanning repository: vercel/next.js │
|
|
86
|
+
╰──────────────────────────────────────────╯
|
|
87
|
+
|
|
88
|
+
✓ Repository ingested (2,847 files)
|
|
89
|
+
✓ Analysis complete!
|
|
90
|
+
|
|
91
|
+
Scores
|
|
92
|
+
┏━━━━━━━━━━┳━━━━━━━━┓
|
|
93
|
+
┃ Category ┃ Score ┃
|
|
94
|
+
┡━━━━━━━━━━╇━━━━━━━━┩
|
|
95
|
+
│ Security │ 85/100 │
|
|
96
|
+
│ Quality │ 78/100 │
|
|
97
|
+
└──────────┴────────┘
|
|
98
|
+
|
|
99
|
+
Security Findings
|
|
100
|
+
┏━━━━━━━━━━┳━━━━━━━┓
|
|
101
|
+
┃ Severity ┃ Count ┃
|
|
102
|
+
┡━━━━━━━━━━╇━━━━━━━┩
|
|
103
|
+
│ Medium │ 3 │
|
|
104
|
+
│ Low │ 12 │
|
|
105
|
+
└──────────┴───────┘
|
|
106
|
+
|
|
107
|
+
✓ No critical security issues found
|
|
108
|
+
|
|
109
|
+
╭─────────── Architecture Summary ───────────╮
|
|
110
|
+
│ Next.js app with App Router architecture. │
|
|
111
|
+
│ Uses React Server Components with proper │
|
|
112
|
+
│ data fetching patterns... │
|
|
113
|
+
╰────────────────────────────────────────────╯
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## License
|
|
117
|
+
|
|
118
|
+
MIT
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
JanexAI CLI - Scan repositories for security and quality issues.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
janex scan https://github.com/user/repo
|
|
7
|
+
janex scan owner/repo
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import re
|
|
12
|
+
import sys
|
|
13
|
+
import json
|
|
14
|
+
import time
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
import typer
|
|
18
|
+
import requests
|
|
19
|
+
from rich.console import Console
|
|
20
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
21
|
+
from rich.panel import Panel
|
|
22
|
+
from rich.table import Table
|
|
23
|
+
from rich import print as rprint
|
|
24
|
+
|
|
25
|
+
from . import __version__
|
|
26
|
+
|
|
27
|
+
# API Configuration
|
|
28
|
+
DEFAULT_API_URL = "https://trust-layer-ai.onrender.com"
|
|
29
|
+
CONFIG_DIR = Path.home() / ".janex"
|
|
30
|
+
CONFIG_FILE = CONFIG_DIR / "config.json"
|
|
31
|
+
|
|
32
|
+
app = typer.Typer(
|
|
33
|
+
name="janex",
|
|
34
|
+
help="JanexAI - AI-powered code security and quality analysis",
|
|
35
|
+
add_completion=False,
|
|
36
|
+
)
|
|
37
|
+
console = Console()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_config() -> dict:
|
|
41
|
+
"""Load config from ~/.janex/config.json"""
|
|
42
|
+
if CONFIG_FILE.exists():
|
|
43
|
+
try:
|
|
44
|
+
return json.loads(CONFIG_FILE.read_text())
|
|
45
|
+
except Exception:
|
|
46
|
+
return {}
|
|
47
|
+
return {}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def save_config(config: dict):
|
|
51
|
+
"""Save config to ~/.janex/config.json with secure permissions."""
|
|
52
|
+
CONFIG_DIR.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
53
|
+
CONFIG_FILE.write_text(json.dumps(config, indent=2))
|
|
54
|
+
# Restrict file permissions to owner only (Unix)
|
|
55
|
+
try:
|
|
56
|
+
CONFIG_FILE.chmod(0o600)
|
|
57
|
+
except Exception:
|
|
58
|
+
pass # Windows doesn't support chmod the same way
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_api_url() -> str:
|
|
62
|
+
"""Get API URL from config or environment."""
|
|
63
|
+
return os.getenv("JANEX_API_URL") or get_config().get("api_url") or DEFAULT_API_URL
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def get_api_token() -> str | None:
|
|
67
|
+
"""Get API token from config or environment."""
|
|
68
|
+
return os.getenv("JANEX_API_TOKEN") or get_config().get("api_token")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def parse_repo_url(repo_input: str) -> tuple[str, str]:
|
|
72
|
+
"""Parse GitHub URL or owner/repo format into (owner, repo)."""
|
|
73
|
+
# Handle full GitHub URLs
|
|
74
|
+
github_patterns = [
|
|
75
|
+
r'https?://github\.com/([^/]+)/([^/]+?)(?:\.git)?/?$',
|
|
76
|
+
r'git@github\.com:([^/]+)/([^/]+?)(?:\.git)?$',
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
for pattern in github_patterns:
|
|
80
|
+
match = re.match(pattern, repo_input)
|
|
81
|
+
if match:
|
|
82
|
+
return match.group(1), match.group(2)
|
|
83
|
+
|
|
84
|
+
# Handle owner/repo format
|
|
85
|
+
if '/' in repo_input and not repo_input.startswith('http'):
|
|
86
|
+
parts = repo_input.split('/')
|
|
87
|
+
if len(parts) == 2:
|
|
88
|
+
return parts[0], parts[1]
|
|
89
|
+
|
|
90
|
+
raise typer.BadParameter(
|
|
91
|
+
f"Invalid repository format: {repo_input}\n"
|
|
92
|
+
"Use: https://github.com/owner/repo or owner/repo"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def api_request(
|
|
97
|
+
method: str,
|
|
98
|
+
endpoint: str,
|
|
99
|
+
data: dict = None,
|
|
100
|
+
timeout: int = 300,
|
|
101
|
+
) -> requests.Response:
|
|
102
|
+
"""Make an authenticated API request."""
|
|
103
|
+
api_url = get_api_url()
|
|
104
|
+
token = get_api_token()
|
|
105
|
+
|
|
106
|
+
# Security: Enforce HTTPS for non-localhost URLs
|
|
107
|
+
if not api_url.startswith("https://") and "localhost" not in api_url and "127.0.0.1" not in api_url:
|
|
108
|
+
console.print("[red]Error:[/red] API URL must use HTTPS for security.")
|
|
109
|
+
console.print(f"Current URL: {api_url}")
|
|
110
|
+
raise typer.Exit(1)
|
|
111
|
+
|
|
112
|
+
headers = {"Content-Type": "application/json"}
|
|
113
|
+
if token:
|
|
114
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
115
|
+
|
|
116
|
+
url = f"{api_url}/api{endpoint}"
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
if method == "GET":
|
|
120
|
+
response = requests.get(url, headers=headers, timeout=timeout)
|
|
121
|
+
elif method == "POST":
|
|
122
|
+
response = requests.post(url, headers=headers, json=data, timeout=timeout)
|
|
123
|
+
else:
|
|
124
|
+
raise ValueError(f"Unsupported method: {method}")
|
|
125
|
+
|
|
126
|
+
return response
|
|
127
|
+
except requests.exceptions.SSLError as e:
|
|
128
|
+
console.print("[red]SSL Error:[/red] Could not verify server certificate.")
|
|
129
|
+
console.print("This could indicate a man-in-the-middle attack.")
|
|
130
|
+
raise typer.Exit(1)
|
|
131
|
+
except requests.exceptions.ConnectionError:
|
|
132
|
+
console.print(f"[red]Error:[/red] Could not connect to {api_url}")
|
|
133
|
+
console.print("Check your internet connection or API URL.")
|
|
134
|
+
raise typer.Exit(1)
|
|
135
|
+
except requests.exceptions.Timeout:
|
|
136
|
+
console.print("[red]Error:[/red] Request timed out")
|
|
137
|
+
raise typer.Exit(1)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@app.command()
|
|
141
|
+
def login(
|
|
142
|
+
token: str = typer.Option(None, "--token", "-t", help="API token (prefer interactive prompt for security)"),
|
|
143
|
+
):
|
|
144
|
+
"""
|
|
145
|
+
Login with your JanexAI API token.
|
|
146
|
+
|
|
147
|
+
Get your token at: https://janex.ai/settings
|
|
148
|
+
|
|
149
|
+
For better security, run without --token flag to enter interactively,
|
|
150
|
+
or use JANEX_API_TOKEN environment variable.
|
|
151
|
+
"""
|
|
152
|
+
if token:
|
|
153
|
+
# Warn about command-line token visibility
|
|
154
|
+
console.print("[yellow]Warning:[/yellow] Token passed via command line may be visible in shell history.")
|
|
155
|
+
console.print("Consider using interactive login or JANEX_API_TOKEN env variable instead.\n")
|
|
156
|
+
else:
|
|
157
|
+
token = typer.prompt("Enter your API token", hide_input=True)
|
|
158
|
+
|
|
159
|
+
if not token or len(token) < 10:
|
|
160
|
+
console.print("[red]Error:[/red] Invalid token format")
|
|
161
|
+
raise typer.Exit(1)
|
|
162
|
+
|
|
163
|
+
# Save config with secure permissions
|
|
164
|
+
config = get_config()
|
|
165
|
+
config["api_token"] = token
|
|
166
|
+
save_config(config)
|
|
167
|
+
|
|
168
|
+
# Test the token
|
|
169
|
+
response = api_request("GET", "/health/")
|
|
170
|
+
if response.status_code == 200:
|
|
171
|
+
console.print("[green]✓[/green] Successfully logged in!")
|
|
172
|
+
console.print(f"Config saved to: {CONFIG_FILE}")
|
|
173
|
+
else:
|
|
174
|
+
console.print("[yellow]Token saved, but could not verify.[/yellow]")
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@app.command()
|
|
178
|
+
def logout():
|
|
179
|
+
"""Logout and remove stored credentials."""
|
|
180
|
+
config = get_config()
|
|
181
|
+
if "api_token" in config:
|
|
182
|
+
del config["api_token"]
|
|
183
|
+
save_config(config)
|
|
184
|
+
console.print("[green]✓[/green] Logged out")
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@app.command()
|
|
188
|
+
def config(
|
|
189
|
+
api_url: str = typer.Option(None, "--api-url", help="Set custom API URL"),
|
|
190
|
+
show: bool = typer.Option(False, "--show", help="Show current config"),
|
|
191
|
+
):
|
|
192
|
+
"""Configure JanexAI CLI settings."""
|
|
193
|
+
cfg = get_config()
|
|
194
|
+
|
|
195
|
+
if show:
|
|
196
|
+
console.print(Panel(
|
|
197
|
+
f"API URL: {get_api_url()}\n"
|
|
198
|
+
f"Token: {'********' if get_api_token() else '(not set)'}",
|
|
199
|
+
title="Configuration",
|
|
200
|
+
))
|
|
201
|
+
return
|
|
202
|
+
|
|
203
|
+
if api_url:
|
|
204
|
+
cfg["api_url"] = api_url
|
|
205
|
+
save_config(cfg)
|
|
206
|
+
console.print(f"[green]✓[/green] API URL set to: {api_url}")
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@app.command()
|
|
210
|
+
def scan(
|
|
211
|
+
repo: str = typer.Argument(..., help="GitHub repo URL or owner/repo"),
|
|
212
|
+
output: str = typer.Option(None, "--output", "-o", help="Output file (JSON)"),
|
|
213
|
+
no_claude: bool = typer.Option(False, "--no-claude", help="Skip Claude analysis"),
|
|
214
|
+
watch: bool = typer.Option(False, "--watch", "-w", help="Watch and wait for results"),
|
|
215
|
+
verbose: bool = typer.Option(False, "--verbose", "-v", help="Verbose output"),
|
|
216
|
+
):
|
|
217
|
+
"""
|
|
218
|
+
Scan a GitHub repository for security and quality issues.
|
|
219
|
+
|
|
220
|
+
Examples:
|
|
221
|
+
janex scan https://github.com/user/repo
|
|
222
|
+
janex scan user/repo
|
|
223
|
+
janex scan user/repo -o report.json
|
|
224
|
+
"""
|
|
225
|
+
try:
|
|
226
|
+
owner, repo_name = parse_repo_url(repo)
|
|
227
|
+
except typer.BadParameter as e:
|
|
228
|
+
console.print(f"[red]Error:[/red] {e}")
|
|
229
|
+
raise typer.Exit(1)
|
|
230
|
+
|
|
231
|
+
full_name = f"{owner}/{repo_name}"
|
|
232
|
+
|
|
233
|
+
console.print(Panel(
|
|
234
|
+
f"[bold]Scanning repository:[/bold] {full_name}",
|
|
235
|
+
title="JanexAI",
|
|
236
|
+
border_style="blue"
|
|
237
|
+
))
|
|
238
|
+
|
|
239
|
+
try:
|
|
240
|
+
with Progress(
|
|
241
|
+
SpinnerColumn(),
|
|
242
|
+
TextColumn("[progress.description]{task.description}"),
|
|
243
|
+
console=console,
|
|
244
|
+
) as progress:
|
|
245
|
+
|
|
246
|
+
# Step 1: Ingest repository
|
|
247
|
+
task = progress.add_task("Ingesting repository...", total=None)
|
|
248
|
+
|
|
249
|
+
response = api_request("POST", "/repositories/ingest/", {
|
|
250
|
+
"owner": owner,
|
|
251
|
+
"repo": repo_name,
|
|
252
|
+
}, timeout=120)
|
|
253
|
+
|
|
254
|
+
if response.status_code == 401:
|
|
255
|
+
console.print("\n[red]Error:[/red] Authentication required.")
|
|
256
|
+
console.print("Run [bold]janex login[/bold] to authenticate.")
|
|
257
|
+
raise typer.Exit(1)
|
|
258
|
+
|
|
259
|
+
if response.status_code not in (200, 201):
|
|
260
|
+
error = response.json().get("error", response.text)
|
|
261
|
+
console.print(f"\n[red]Error:[/red] {error}")
|
|
262
|
+
raise typer.Exit(1)
|
|
263
|
+
|
|
264
|
+
ingest_data = response.json()
|
|
265
|
+
repo_id = ingest_data.get("repository_id")
|
|
266
|
+
|
|
267
|
+
if not repo_id:
|
|
268
|
+
console.print("[red]Error:[/red] No repository ID returned")
|
|
269
|
+
raise typer.Exit(1)
|
|
270
|
+
|
|
271
|
+
progress.update(task, description=f"[green]✓[/green] Repository ingested ({ingest_data.get('files_count', 0)} files)")
|
|
272
|
+
|
|
273
|
+
if verbose:
|
|
274
|
+
console.print(f" Commit: {ingest_data.get('commit_sha', 'unknown')[:8]}")
|
|
275
|
+
|
|
276
|
+
# Step 2: Run analysis
|
|
277
|
+
progress.update(task, description="Running security & quality analysis...")
|
|
278
|
+
|
|
279
|
+
analyze_response = api_request("POST", f"/repositories/{repo_id}/analyze/", {
|
|
280
|
+
"use_claude": not no_claude,
|
|
281
|
+
}, timeout=600) # 10 min timeout for analysis
|
|
282
|
+
|
|
283
|
+
if analyze_response.status_code != 200:
|
|
284
|
+
error = analyze_response.json().get("error", analyze_response.text)
|
|
285
|
+
console.print(f"\n[red]Analysis Error:[/red] {error}")
|
|
286
|
+
raise typer.Exit(1)
|
|
287
|
+
|
|
288
|
+
report = analyze_response.json()
|
|
289
|
+
|
|
290
|
+
progress.update(task, description="[green]✓[/green] Analysis complete!")
|
|
291
|
+
|
|
292
|
+
# Output results
|
|
293
|
+
if output:
|
|
294
|
+
output_path = Path(output).resolve()
|
|
295
|
+
# Basic safety check - don't write to system directories
|
|
296
|
+
unsafe_prefixes = ['/etc', '/usr', '/bin', '/sbin', '/var', '/System', 'C:\\Windows']
|
|
297
|
+
if any(str(output_path).startswith(prefix) for prefix in unsafe_prefixes):
|
|
298
|
+
console.print(f"[red]Error:[/red] Cannot write to system directory: {output_path}")
|
|
299
|
+
raise typer.Exit(1)
|
|
300
|
+
|
|
301
|
+
with open(output_path, 'w') as f:
|
|
302
|
+
json.dump(report, f, indent=2, default=str)
|
|
303
|
+
console.print(f"\n[green]Report saved to:[/green] {output_path}")
|
|
304
|
+
|
|
305
|
+
# Display summary
|
|
306
|
+
display_summary(report)
|
|
307
|
+
|
|
308
|
+
except requests.exceptions.RequestException as e:
|
|
309
|
+
console.print(f"[red]Request Error:[/red] {e}")
|
|
310
|
+
raise typer.Exit(1)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def display_summary(report: dict):
|
|
314
|
+
"""Display a summary of the scan results."""
|
|
315
|
+
console.print()
|
|
316
|
+
|
|
317
|
+
# Overall scores
|
|
318
|
+
security_score = report.get("security_score", 0)
|
|
319
|
+
quality_score = report.get("quality_score", 0)
|
|
320
|
+
|
|
321
|
+
# Score colors
|
|
322
|
+
def score_color(score):
|
|
323
|
+
if score >= 80:
|
|
324
|
+
return "green"
|
|
325
|
+
elif score >= 60:
|
|
326
|
+
return "yellow"
|
|
327
|
+
return "red"
|
|
328
|
+
|
|
329
|
+
scores_table = Table(title="Scores", show_header=True)
|
|
330
|
+
scores_table.add_column("Category", style="bold")
|
|
331
|
+
scores_table.add_column("Score", justify="right")
|
|
332
|
+
|
|
333
|
+
scores_table.add_row(
|
|
334
|
+
"Security",
|
|
335
|
+
f"[{score_color(security_score)}]{security_score}/100[/{score_color(security_score)}]"
|
|
336
|
+
)
|
|
337
|
+
scores_table.add_row(
|
|
338
|
+
"Quality",
|
|
339
|
+
f"[{score_color(quality_score)}]{quality_score}/100[/{score_color(quality_score)}]"
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
console.print(scores_table)
|
|
343
|
+
console.print()
|
|
344
|
+
|
|
345
|
+
# Security findings
|
|
346
|
+
security = report.get("security_summary", {})
|
|
347
|
+
high_count = security.get("high_severity_count", 0)
|
|
348
|
+
medium_count = security.get("medium_severity_count", 0)
|
|
349
|
+
low_count = security.get("low_severity_count", 0)
|
|
350
|
+
|
|
351
|
+
if high_count or medium_count or low_count:
|
|
352
|
+
security_table = Table(title="Security Findings", show_header=True)
|
|
353
|
+
security_table.add_column("Severity", style="bold")
|
|
354
|
+
security_table.add_column("Count", justify="right")
|
|
355
|
+
|
|
356
|
+
if high_count > 0:
|
|
357
|
+
security_table.add_row("[red]High[/red]", str(high_count))
|
|
358
|
+
if medium_count > 0:
|
|
359
|
+
security_table.add_row("[yellow]Medium[/yellow]", str(medium_count))
|
|
360
|
+
if low_count > 0:
|
|
361
|
+
security_table.add_row("[green]Low[/green]", str(low_count))
|
|
362
|
+
|
|
363
|
+
console.print(security_table)
|
|
364
|
+
else:
|
|
365
|
+
console.print("[green]✓ No security issues found[/green]")
|
|
366
|
+
|
|
367
|
+
console.print()
|
|
368
|
+
|
|
369
|
+
# Quality findings
|
|
370
|
+
quality = report.get("quality_summary", {})
|
|
371
|
+
quality_count = quality.get("total_findings", 0)
|
|
372
|
+
|
|
373
|
+
if quality_count:
|
|
374
|
+
console.print(f"[yellow]Quality issues:[/yellow] {quality_count}")
|
|
375
|
+
else:
|
|
376
|
+
console.print("[green]✓ No quality issues found[/green]")
|
|
377
|
+
|
|
378
|
+
console.print()
|
|
379
|
+
|
|
380
|
+
# Architecture summary
|
|
381
|
+
arch_summary = report.get("arch_summary")
|
|
382
|
+
if arch_summary:
|
|
383
|
+
console.print(Panel(
|
|
384
|
+
arch_summary[:600] + "..." if len(arch_summary) > 600 else arch_summary,
|
|
385
|
+
title="Architecture Summary",
|
|
386
|
+
border_style="blue"
|
|
387
|
+
))
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
@app.command()
|
|
391
|
+
def version():
|
|
392
|
+
"""Show version information."""
|
|
393
|
+
console.print(f"JanexAI CLI v{__version__}")
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def main():
|
|
397
|
+
"""Entry point for the CLI."""
|
|
398
|
+
app()
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
if __name__ == "__main__":
|
|
402
|
+
main()
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: janexai
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: AI-powered code security and quality analysis CLI
|
|
5
|
+
Author-email: JanexAI Team <hello@janex.ai>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://janex.ai
|
|
8
|
+
Project-URL: Documentation, https://docs.janex.ai
|
|
9
|
+
Project-URL: Repository, https://github.com/janexai/janex-cli
|
|
10
|
+
Project-URL: Issues, https://github.com/janexai/janex-cli/issues
|
|
11
|
+
Keywords: security,code-analysis,ai,llm,cli
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Security
|
|
23
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
Requires-Dist: typer>=0.9.0
|
|
27
|
+
Requires-Dist: rich>=13.0.0
|
|
28
|
+
Requires-Dist: requests>=2.28.0
|
|
29
|
+
|
|
30
|
+
# JanexAI CLI
|
|
31
|
+
|
|
32
|
+
AI-powered code security and quality analysis from your terminal.
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install janexai
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Quick Start
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# Scan a public repository
|
|
44
|
+
janex scan https://github.com/user/repo
|
|
45
|
+
|
|
46
|
+
# Or use owner/repo format
|
|
47
|
+
janex scan user/repo
|
|
48
|
+
|
|
49
|
+
# Save report to file
|
|
50
|
+
janex scan user/repo -o report.json
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Authentication
|
|
54
|
+
|
|
55
|
+
For private repositories or higher rate limits, authenticate with your API token:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
janex login --token YOUR_API_TOKEN
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Get your token at [janex.ai/settings](https://janex.ai/settings)
|
|
62
|
+
|
|
63
|
+
## Commands
|
|
64
|
+
|
|
65
|
+
### `janex scan`
|
|
66
|
+
|
|
67
|
+
Scan a repository for security and quality issues.
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
janex scan <repo> [OPTIONS]
|
|
71
|
+
|
|
72
|
+
Arguments:
|
|
73
|
+
repo GitHub repo URL or owner/repo format
|
|
74
|
+
|
|
75
|
+
Options:
|
|
76
|
+
-o, --output Save report to JSON file
|
|
77
|
+
--no-claude Skip Claude AI analysis (faster)
|
|
78
|
+
-v, --verbose Verbose output
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### `janex login`
|
|
82
|
+
|
|
83
|
+
Authenticate with your API token.
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
janex login --token YOUR_TOKEN
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### `janex config`
|
|
90
|
+
|
|
91
|
+
Configure CLI settings.
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# Show current config
|
|
95
|
+
janex config --show
|
|
96
|
+
|
|
97
|
+
# Set custom API URL (for self-hosted)
|
|
98
|
+
janex config --api-url https://your-server.com
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### `janex version`
|
|
102
|
+
|
|
103
|
+
Show CLI version.
|
|
104
|
+
|
|
105
|
+
## Environment Variables
|
|
106
|
+
|
|
107
|
+
- `JANEX_API_TOKEN` - API token (alternative to `janex login`)
|
|
108
|
+
- `JANEX_API_URL` - Custom API URL (for self-hosted instances)
|
|
109
|
+
|
|
110
|
+
## Example Output
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
╭──────────────── JanexAI ─────────────────╮
|
|
114
|
+
│ Scanning repository: vercel/next.js │
|
|
115
|
+
╰──────────────────────────────────────────╯
|
|
116
|
+
|
|
117
|
+
✓ Repository ingested (2,847 files)
|
|
118
|
+
✓ Analysis complete!
|
|
119
|
+
|
|
120
|
+
Scores
|
|
121
|
+
┏━━━━━━━━━━┳━━━━━━━━┓
|
|
122
|
+
┃ Category ┃ Score ┃
|
|
123
|
+
┡━━━━━━━━━━╇━━━━━━━━┩
|
|
124
|
+
│ Security │ 85/100 │
|
|
125
|
+
│ Quality │ 78/100 │
|
|
126
|
+
└──────────┴────────┘
|
|
127
|
+
|
|
128
|
+
Security Findings
|
|
129
|
+
┏━━━━━━━━━━┳━━━━━━━┓
|
|
130
|
+
┃ Severity ┃ Count ┃
|
|
131
|
+
┡━━━━━━━━━━╇━━━━━━━┩
|
|
132
|
+
│ Medium │ 3 │
|
|
133
|
+
│ Low │ 12 │
|
|
134
|
+
└──────────┴───────┘
|
|
135
|
+
|
|
136
|
+
✓ No critical security issues found
|
|
137
|
+
|
|
138
|
+
╭─────────── Architecture Summary ───────────╮
|
|
139
|
+
│ Next.js app with App Router architecture. │
|
|
140
|
+
│ Uses React Server Components with proper │
|
|
141
|
+
│ data fetching patterns... │
|
|
142
|
+
╰────────────────────────────────────────────╯
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
MIT
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
janex/__init__.py
|
|
4
|
+
janex/main.py
|
|
5
|
+
janexai.egg-info/PKG-INFO
|
|
6
|
+
janexai.egg-info/SOURCES.txt
|
|
7
|
+
janexai.egg-info/dependency_links.txt
|
|
8
|
+
janexai.egg-info/entry_points.txt
|
|
9
|
+
janexai.egg-info/requires.txt
|
|
10
|
+
janexai.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
janex
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "janexai"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "AI-powered code security and quality analysis CLI"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "JanexAI Team", email = "hello@janex.ai"}
|
|
14
|
+
]
|
|
15
|
+
keywords = ["security", "code-analysis", "ai", "llm", "cli"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Environment :: Console",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3.9",
|
|
24
|
+
"Programming Language :: Python :: 3.10",
|
|
25
|
+
"Programming Language :: Python :: 3.11",
|
|
26
|
+
"Programming Language :: Python :: 3.12",
|
|
27
|
+
"Topic :: Security",
|
|
28
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
dependencies = [
|
|
32
|
+
"typer>=0.9.0",
|
|
33
|
+
"rich>=13.0.0",
|
|
34
|
+
"requests>=2.28.0",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
janex = "janex.main:main"
|
|
39
|
+
|
|
40
|
+
[project.urls]
|
|
41
|
+
Homepage = "https://janex.ai"
|
|
42
|
+
Documentation = "https://docs.janex.ai"
|
|
43
|
+
Repository = "https://github.com/janexai/janex-cli"
|
|
44
|
+
Issues = "https://github.com/janexai/janex-cli/issues"
|
|
45
|
+
|
|
46
|
+
[tool.setuptools.packages.find]
|
|
47
|
+
where = ["."]
|
|
48
|
+
include = ["janex*"]
|
janexai-0.1.0/setup.cfg
ADDED