nova-bridgeye 0.1.0
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.
- package/.env.example +17 -0
- package/.vscode/settings.json +5 -0
- package/README.md +9 -0
- package/bin/nova +3 -0
- package/core/__init__.py +0 -0
- package/core/prompts.py +158 -0
- package/core/state.py +37 -0
- package/nova_cli/__init__.py +1 -0
- package/nova_cli/__main__.py +4 -0
- package/nova_cli/cli/__init__.py +0 -0
- package/nova_cli/cli/commands.py +49 -0
- package/nova_cli/cli/main.py +83 -0
- package/nova_cli/cli/registry.py +14 -0
- package/nova_cli/cli/shell.py +657 -0
- package/nova_cli/config.py +36 -0
- package/nova_cli/local/file_manager/__init__.py +36 -0
- package/nova_cli/local/file_manager/commands.py +173 -0
- package/nova_cli/local/file_manager/edit_ops.py +98 -0
- package/nova_cli/local/file_manager/git_ops.py +135 -0
- package/nova_cli/local/file_manager/io_ops.py +297 -0
- package/nova_cli/local/file_manager/path_ops.py +48 -0
- package/nova_cli/local/file_manager.py +4 -0
- package/nova_cli/local/healer/__init__.py +0 -0
- package/nova_cli/local/healer/runner.py +313 -0
- package/nova_cli/local/ui.py +196 -0
- package/nova_cli/local/utils.py +201 -0
- package/nova_cli/nova_core/__init__.py +0 -0
- package/nova_cli/nova_core/ai/__init__.py +0 -0
- package/nova_cli/nova_core/ai/api_client.py +298 -0
- package/nova_cli/nova_core/ai/utils.py +12 -0
- package/nova_cli/nova_core/auth/__init__.py +0 -0
- package/nova_cli/nova_core/auth/client.py +103 -0
- package/nova_cli/nova_core/auth/storage.py +146 -0
- package/nova_legacy.py +15 -0
- package/package.json +19 -0
- package/project_context.txt +3528 -0
- package/pyproject.toml +26 -0
- package/requirements.txt +6 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# nova_cli/nova_core/ai/utils.py
|
|
2
|
+
# CLI-only list for model selector UI.
|
|
3
|
+
# The API remains the source of truth for what is actually allowed.
|
|
4
|
+
|
|
5
|
+
MODELS = {
|
|
6
|
+
"groq": [
|
|
7
|
+
"openai/gpt-oss-120b",
|
|
8
|
+
"openai/gpt-oss-20b",
|
|
9
|
+
"meta-llama/llama-4-scout-17b-16e-instruct",
|
|
10
|
+
"moonshotai/kimi-k2-instruct-0905",
|
|
11
|
+
],
|
|
12
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# NOVA_CLI\nova_cli\nova_core\auth\client.py
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import time
|
|
5
|
+
import webbrowser
|
|
6
|
+
import requests
|
|
7
|
+
from typing import Optional, Dict, Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class NovaAuthClient:
|
|
11
|
+
def __init__(self, base_url: Optional[str] = None):
|
|
12
|
+
# Priority:
|
|
13
|
+
# 1) passed base_url
|
|
14
|
+
# 2) env NOVA_AUTH_BASE_URL
|
|
15
|
+
# 3) default production
|
|
16
|
+
resolved = base_url or os.getenv("NOVA_AUTH_BASE_URL") or "https://nova.bridgeye.com"
|
|
17
|
+
self.base_url = resolved.rstrip("/")
|
|
18
|
+
|
|
19
|
+
# Basic client identity (helps audit logs + debugging)
|
|
20
|
+
self.user_agent = os.getenv("NOVA_USER_AGENT", "NovaCLI/1.0")
|
|
21
|
+
|
|
22
|
+
def _headers(self) -> Dict[str, str]:
|
|
23
|
+
return {"User-Agent": self.user_agent}
|
|
24
|
+
|
|
25
|
+
def create_session(self) -> str:
|
|
26
|
+
# Render can cold-start, keep this lenient
|
|
27
|
+
r = requests.post(
|
|
28
|
+
f"{self.base_url}/auth/session",
|
|
29
|
+
timeout=30,
|
|
30
|
+
headers=self._headers(),
|
|
31
|
+
)
|
|
32
|
+
r.raise_for_status()
|
|
33
|
+
data = r.json()
|
|
34
|
+
return data["session_id"]
|
|
35
|
+
|
|
36
|
+
def open_browser(self, session_id: str) -> None:
|
|
37
|
+
url = f"{self.base_url}/login?session_id={session_id}"
|
|
38
|
+
webbrowser.open(url)
|
|
39
|
+
|
|
40
|
+
def poll_session(self, session_id: str, timeout: int = 300) -> Optional[str]:
|
|
41
|
+
start = time.time()
|
|
42
|
+
|
|
43
|
+
while time.time() - start < timeout:
|
|
44
|
+
try:
|
|
45
|
+
r = requests.get(
|
|
46
|
+
f"{self.base_url}/auth/session/{session_id}",
|
|
47
|
+
timeout=10,
|
|
48
|
+
headers=self._headers(),
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
if r.status_code == 200:
|
|
52
|
+
data = r.json()
|
|
53
|
+
if data.get("status") == "approved":
|
|
54
|
+
return data.get("auth_code")
|
|
55
|
+
|
|
56
|
+
except Exception:
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
time.sleep(2)
|
|
60
|
+
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
def exchange_auth_code(self, auth_code: str) -> Dict[str, Any]:
|
|
64
|
+
try:
|
|
65
|
+
r = requests.post(
|
|
66
|
+
f"{self.base_url}/auth/token",
|
|
67
|
+
json={"auth_code": auth_code},
|
|
68
|
+
timeout=20,
|
|
69
|
+
headers=self._headers(),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
if r.status_code != 200:
|
|
73
|
+
return {
|
|
74
|
+
"error": "token_exchange_failed",
|
|
75
|
+
"status": r.status_code,
|
|
76
|
+
"body": r.text,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return r.json()
|
|
80
|
+
|
|
81
|
+
except Exception as e:
|
|
82
|
+
return {"error": "request_failed", "body": str(e)}
|
|
83
|
+
|
|
84
|
+
def refresh_access(self, refresh_token: str) -> Dict[str, Any]:
|
|
85
|
+
try:
|
|
86
|
+
r = requests.post(
|
|
87
|
+
f"{self.base_url}/auth/refresh",
|
|
88
|
+
json={"refresh_token": refresh_token},
|
|
89
|
+
timeout=20,
|
|
90
|
+
headers=self._headers(),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
if r.status_code != 200:
|
|
94
|
+
return {
|
|
95
|
+
"error": "refresh_failed",
|
|
96
|
+
"status": r.status_code,
|
|
97
|
+
"body": r.text,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return r.json()
|
|
101
|
+
|
|
102
|
+
except Exception as e:
|
|
103
|
+
return {"error": "request_failed", "body": str(e)}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# NOVA_CLI\nova_cli\nova_core\auth\storage.py
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import json
|
|
5
|
+
import time
|
|
6
|
+
from typing import Optional, Dict, Any
|
|
7
|
+
|
|
8
|
+
AUTH_DIR = os.path.expanduser("~/.nova")
|
|
9
|
+
AUTH_FILE = os.path.join(AUTH_DIR, "auth.json")
|
|
10
|
+
|
|
11
|
+
DEFAULT_EXPIRES_IN = 3600
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _now() -> float:
|
|
15
|
+
return time.time()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _read_json(path: str) -> Optional[Dict[str, Any]]:
|
|
19
|
+
if not os.path.exists(path):
|
|
20
|
+
return None
|
|
21
|
+
try:
|
|
22
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
23
|
+
return json.load(f)
|
|
24
|
+
except Exception:
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def save_auth(data: dict) -> None:
|
|
29
|
+
os.makedirs(AUTH_DIR, exist_ok=True)
|
|
30
|
+
|
|
31
|
+
# Normalize issued_at
|
|
32
|
+
if "issued_at" not in data or data["issued_at"] in (None, ""):
|
|
33
|
+
data["issued_at"] = _now()
|
|
34
|
+
else:
|
|
35
|
+
try:
|
|
36
|
+
data["issued_at"] = float(data["issued_at"])
|
|
37
|
+
except Exception:
|
|
38
|
+
data["issued_at"] = _now()
|
|
39
|
+
|
|
40
|
+
# Normalize expires_in
|
|
41
|
+
if "expires_in" in data and data["expires_in"] not in (None, ""):
|
|
42
|
+
try:
|
|
43
|
+
data["expires_in"] = int(data["expires_in"])
|
|
44
|
+
except Exception:
|
|
45
|
+
data["expires_in"] = DEFAULT_EXPIRES_IN
|
|
46
|
+
else:
|
|
47
|
+
# if missing, keep whatever is there; access expiry will be treated conservatively
|
|
48
|
+
data.setdefault("expires_in", DEFAULT_EXPIRES_IN)
|
|
49
|
+
|
|
50
|
+
with open(AUTH_FILE, "w", encoding="utf-8") as f:
|
|
51
|
+
json.dump(data, f, indent=2)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def load_auth() -> Optional[Dict[str, Any]]:
|
|
55
|
+
return _read_json(AUTH_FILE)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def logout() -> None:
|
|
59
|
+
if os.path.exists(AUTH_FILE):
|
|
60
|
+
os.remove(AUTH_FILE)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def has_refresh_token() -> bool:
|
|
64
|
+
data = load_auth()
|
|
65
|
+
return bool(data and data.get("refresh_token"))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _access_expired(data: Dict[str, Any]) -> bool:
|
|
69
|
+
access = data.get("access_token")
|
|
70
|
+
if not access:
|
|
71
|
+
return True
|
|
72
|
+
|
|
73
|
+
expires_in = data.get("expires_in")
|
|
74
|
+
issued_at = data.get("issued_at")
|
|
75
|
+
|
|
76
|
+
# If metadata missing, treat as expired so client will refresh safely.
|
|
77
|
+
if expires_in in (None, "", 0) or issued_at in (None, ""):
|
|
78
|
+
return True
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
exp_ts = float(issued_at) + float(expires_in)
|
|
82
|
+
except Exception:
|
|
83
|
+
return True
|
|
84
|
+
|
|
85
|
+
return _now() >= exp_ts
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def is_logged_in() -> bool:
|
|
89
|
+
"""
|
|
90
|
+
Logged-in means we have either:
|
|
91
|
+
- a non-expired access token, OR
|
|
92
|
+
- a refresh token (so we can get a new access token)
|
|
93
|
+
"""
|
|
94
|
+
data = load_auth()
|
|
95
|
+
if not data:
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
if data.get("refresh_token"):
|
|
99
|
+
return True
|
|
100
|
+
|
|
101
|
+
# no refresh token -> must have valid access
|
|
102
|
+
return not _access_expired(data)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def get_access_token() -> Optional[str]:
|
|
106
|
+
data = load_auth()
|
|
107
|
+
if not data:
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
if _access_expired(data):
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
return data.get("access_token")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def get_refresh_token() -> Optional[str]:
|
|
117
|
+
data = load_auth()
|
|
118
|
+
if not data:
|
|
119
|
+
return None
|
|
120
|
+
return data.get("refresh_token")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def update_tokens(
|
|
124
|
+
access_token: str,
|
|
125
|
+
refresh_token: str,
|
|
126
|
+
expires_in: int = DEFAULT_EXPIRES_IN,
|
|
127
|
+
issued_at: Optional[float] = None,
|
|
128
|
+
) -> None:
|
|
129
|
+
data = load_auth() or {}
|
|
130
|
+
data["access_token"] = access_token
|
|
131
|
+
data["refresh_token"] = refresh_token
|
|
132
|
+
data["expires_in"] = int(expires_in) if expires_in else DEFAULT_EXPIRES_IN
|
|
133
|
+
data["issued_at"] = float(issued_at) if issued_at else _now()
|
|
134
|
+
save_auth(data)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def update_access_token(
|
|
138
|
+
access_token: str,
|
|
139
|
+
expires_in: int = DEFAULT_EXPIRES_IN,
|
|
140
|
+
issued_at: Optional[float] = None,
|
|
141
|
+
) -> None:
|
|
142
|
+
data = load_auth() or {}
|
|
143
|
+
data["access_token"] = access_token
|
|
144
|
+
data["expires_in"] = int(expires_in) if expires_in else DEFAULT_EXPIRES_IN
|
|
145
|
+
data["issued_at"] = float(issued_at) if issued_at else _now()
|
|
146
|
+
save_auth(data)
|
package/nova_legacy.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from nova.CLI.shell import NovaShell
|
|
4
|
+
|
|
5
|
+
# --- LOGGING SETUP ---
|
|
6
|
+
logging.basicConfig(
|
|
7
|
+
filename='nova.log',
|
|
8
|
+
level=logging.INFO,
|
|
9
|
+
format='%(asctime)s - %(levelname)s - %(message)s'
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
if __name__ == "__main__":
|
|
14
|
+
app = NovaShell()
|
|
15
|
+
app.run()
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nova-bridgeye",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "NOVA CLI (Developer Teammate in your Terminal)",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"nova": "./bin/nova"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"postinstall": "pip3 install nova-bridgeye || pip install nova-bridgeye"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/bridgeyeai/NOVA-CLI.git"
|
|
15
|
+
},
|
|
16
|
+
"keywords": ["nova", "bridgeye", "cli", "ai"],
|
|
17
|
+
"author": "Bridgeye",
|
|
18
|
+
"license": "MIT"
|
|
19
|
+
}
|