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.
Files changed (38) hide show
  1. package/.env.example +17 -0
  2. package/.vscode/settings.json +5 -0
  3. package/README.md +9 -0
  4. package/bin/nova +3 -0
  5. package/core/__init__.py +0 -0
  6. package/core/prompts.py +158 -0
  7. package/core/state.py +37 -0
  8. package/nova_cli/__init__.py +1 -0
  9. package/nova_cli/__main__.py +4 -0
  10. package/nova_cli/cli/__init__.py +0 -0
  11. package/nova_cli/cli/commands.py +49 -0
  12. package/nova_cli/cli/main.py +83 -0
  13. package/nova_cli/cli/registry.py +14 -0
  14. package/nova_cli/cli/shell.py +657 -0
  15. package/nova_cli/config.py +36 -0
  16. package/nova_cli/local/file_manager/__init__.py +36 -0
  17. package/nova_cli/local/file_manager/commands.py +173 -0
  18. package/nova_cli/local/file_manager/edit_ops.py +98 -0
  19. package/nova_cli/local/file_manager/git_ops.py +135 -0
  20. package/nova_cli/local/file_manager/io_ops.py +297 -0
  21. package/nova_cli/local/file_manager/path_ops.py +48 -0
  22. package/nova_cli/local/file_manager.py +4 -0
  23. package/nova_cli/local/healer/__init__.py +0 -0
  24. package/nova_cli/local/healer/runner.py +313 -0
  25. package/nova_cli/local/ui.py +196 -0
  26. package/nova_cli/local/utils.py +201 -0
  27. package/nova_cli/nova_core/__init__.py +0 -0
  28. package/nova_cli/nova_core/ai/__init__.py +0 -0
  29. package/nova_cli/nova_core/ai/api_client.py +298 -0
  30. package/nova_cli/nova_core/ai/utils.py +12 -0
  31. package/nova_cli/nova_core/auth/__init__.py +0 -0
  32. package/nova_cli/nova_core/auth/client.py +103 -0
  33. package/nova_cli/nova_core/auth/storage.py +146 -0
  34. package/nova_legacy.py +15 -0
  35. package/package.json +19 -0
  36. package/project_context.txt +3528 -0
  37. package/pyproject.toml +26 -0
  38. 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
+ }