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
package/.env.example
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# NOVA CLI (API-only)
|
|
2
|
+
|
|
3
|
+
# Where your NOVA_API is running
|
|
4
|
+
NOVA_API_BASE_URL=https://api.nova.bridgeye.com
|
|
5
|
+
|
|
6
|
+
# Where nova-web auth is running (Render)
|
|
7
|
+
NOVA_AUTH_BASE_URL=https://nova.bridgeye.com
|
|
8
|
+
|
|
9
|
+
# Optional: debug auth failures locally
|
|
10
|
+
NOVA_DEBUG_AUTH=0
|
|
11
|
+
|
|
12
|
+
# Optional: defaults for model selector
|
|
13
|
+
NOVA_DEFAULT_PROVIDER=groq
|
|
14
|
+
NOVA_DEFAULT_MODEL=openai/gpt-oss-120b
|
|
15
|
+
|
|
16
|
+
# Optional: client label (helps logs/audit)
|
|
17
|
+
NOVA_USER_AGENT=NovaCLI/1.0
|
package/README.md
ADDED
package/bin/nova
ADDED
package/core/__init__.py
ADDED
|
File without changes
|
package/core/prompts.py
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
from nova_cli import config
|
|
5
|
+
from string import Template
|
|
6
|
+
from typing import List, Optional, Dict
|
|
7
|
+
from nova_cli.local.utils import generate_ast_map
|
|
8
|
+
|
|
9
|
+
# --- CONFIGURATION ---
|
|
10
|
+
EXCLUDED_DIRS = {
|
|
11
|
+
".git", "__pycache__", "venv", "env", "node_modules",
|
|
12
|
+
".idea", ".vscode", "dist", "build", ".next", ".ds_store",
|
|
13
|
+
".mypy_cache", ".nova"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
_TREE_CACHE = None
|
|
17
|
+
MAX_CONTEXT_CHARS = 150000 # Threshold to trigger skeletal view
|
|
18
|
+
|
|
19
|
+
def clear_file_tree_cache():
|
|
20
|
+
global _TREE_CACHE
|
|
21
|
+
_TREE_CACHE = None
|
|
22
|
+
|
|
23
|
+
# --- TEMPLATES ---
|
|
24
|
+
SYSTEM_TEMPLATE = Template("""
|
|
25
|
+
/// SYSTEM BOOT SEQUENCE ///
|
|
26
|
+
IDENTITY: N.O.V.A. (Neural Orchestration & Virtual Assistant)
|
|
27
|
+
USER: $user_name (Authorization: Level 10)
|
|
28
|
+
TIME: $current_time
|
|
29
|
+
LOC: $location
|
|
30
|
+
CWD: $cwd
|
|
31
|
+
|
|
32
|
+
/// REPOSITORY MAP (AST) ///
|
|
33
|
+
The following is a high-level map of the codebase structure:
|
|
34
|
+
$dir_structure
|
|
35
|
+
|
|
36
|
+
/// PERSISTENT CONTEXT ///
|
|
37
|
+
$loaded_files_block
|
|
38
|
+
|
|
39
|
+
/// ACTIVE CONTEXT ///
|
|
40
|
+
$context_modules
|
|
41
|
+
|
|
42
|
+
/// PRIME DIRECTIVES ///
|
|
43
|
+
1. [PERSONA & BEHAVIOR]
|
|
44
|
+
- You are a high-performance Code Engine.
|
|
45
|
+
- **INPUT CLASSIFICATION**:
|
|
46
|
+
- **CASUAL/VAGUE**: Respond conversationally.
|
|
47
|
+
- **TECHNICAL**: Execute immediate file operations.
|
|
48
|
+
- **STRICT**: No "Certainly sir". Get straight to the point.
|
|
49
|
+
|
|
50
|
+
2. [SCOPE ADHERENCE]
|
|
51
|
+
- **DEFAULT LOCATION**: Create files in the **CURRENT DIRECTORY** ($cwd).
|
|
52
|
+
- **NO HALLUCINATION**: You see only the files listed in PERSISTENT CONTEXT.
|
|
53
|
+
|
|
54
|
+
3. [WRITE PROTOCOLS - SURGICAL MODE]
|
|
55
|
+
- **Do NOT rewrite the whole file** when editing.
|
|
56
|
+
- Use SEARCH/REPLACE blocks.
|
|
57
|
+
- **COMMAND SYNTAX**:
|
|
58
|
+
- New Directory: `[MKDIR: path/to/dir]`
|
|
59
|
+
- New File: `[CREATE: path/to/file.ext]` followed immediately by a **CODE BLOCK**.
|
|
60
|
+
- Edit File: `[EDIT: path/to/file.ext]` followed by SEARCH/REPLACE blocks.
|
|
61
|
+
- Delete File/Dir: `[DELETE: path/to/target]`
|
|
62
|
+
|
|
63
|
+
**SEARCH/REPLACE BLOCK FORMAT**:
|
|
64
|
+
<<<<<<< SEARCH
|
|
65
|
+
(original code lines - must match EXACTLY)
|
|
66
|
+
=======
|
|
67
|
+
(new code lines)
|
|
68
|
+
>>>>>>> REPLACE
|
|
69
|
+
|
|
70
|
+
/// INCOMING TRANSMISSION ///
|
|
71
|
+
$user_input
|
|
72
|
+
""")
|
|
73
|
+
|
|
74
|
+
def get_repo_map_cached(startpath: str) -> str:
|
|
75
|
+
"""
|
|
76
|
+
Retrieves the AST-based repo map from utils.py, caching the result
|
|
77
|
+
until clear_file_tree_cache() is called (usually after a file op).
|
|
78
|
+
"""
|
|
79
|
+
global _TREE_CACHE
|
|
80
|
+
if _TREE_CACHE is not None: return _TREE_CACHE
|
|
81
|
+
|
|
82
|
+
# Call the new smart mapper in utils
|
|
83
|
+
result = generate_ast_map(startpath)
|
|
84
|
+
if not result:
|
|
85
|
+
result = "[Empty or Non-Python Directory]"
|
|
86
|
+
|
|
87
|
+
_TREE_CACHE = result
|
|
88
|
+
return result
|
|
89
|
+
|
|
90
|
+
def generate_skeleton(content: str) -> str:
|
|
91
|
+
"""
|
|
92
|
+
Minifies file content to save tokens.
|
|
93
|
+
Keeps imports, class definitions, and function signatures.
|
|
94
|
+
Hides bodies.
|
|
95
|
+
"""
|
|
96
|
+
lines = content.splitlines()
|
|
97
|
+
skeleton = []
|
|
98
|
+
for line in lines:
|
|
99
|
+
stripped = line.strip()
|
|
100
|
+
# Keep imports, classes, defs, and decorators
|
|
101
|
+
if (stripped.startswith("import ") or
|
|
102
|
+
stripped.startswith("from ") or
|
|
103
|
+
stripped.startswith("class ") or
|
|
104
|
+
stripped.startswith("def ") or
|
|
105
|
+
stripped.startswith("@")):
|
|
106
|
+
skeleton.append(line)
|
|
107
|
+
# Keep non-indented assignments (globals)
|
|
108
|
+
elif line and not line[0].isspace() and "=" in line:
|
|
109
|
+
skeleton.append(line)
|
|
110
|
+
|
|
111
|
+
return "\n".join(skeleton) + "\n... [BODY HIDDEN] ..."
|
|
112
|
+
|
|
113
|
+
def get_loaded_files_block(loaded_files: Dict[str, str], active_file: Optional[str] = None) -> str:
|
|
114
|
+
"""
|
|
115
|
+
Injects content.
|
|
116
|
+
Feature: SMART CONTEXT.
|
|
117
|
+
If total size > MAX_CONTEXT_CHARS, minifies files that are NOT the active file.
|
|
118
|
+
"""
|
|
119
|
+
if not loaded_files:
|
|
120
|
+
return "[NO FILES LOADED]"
|
|
121
|
+
|
|
122
|
+
# Calculate total size
|
|
123
|
+
total_size = sum(len(c) for c in loaded_files.values())
|
|
124
|
+
use_minification = total_size > MAX_CONTEXT_CHARS
|
|
125
|
+
|
|
126
|
+
block = []
|
|
127
|
+
for fname, content in loaded_files.items():
|
|
128
|
+
# Determine if this file needs to be fully visible
|
|
129
|
+
is_active = active_file and (fname == active_file or fname == os.path.basename(active_file))
|
|
130
|
+
|
|
131
|
+
if use_minification and not is_active:
|
|
132
|
+
display_content = generate_skeleton(content)
|
|
133
|
+
header = f"--- START FILE (OUTLINE): {fname} ---"
|
|
134
|
+
else:
|
|
135
|
+
# Full content for active file or small context
|
|
136
|
+
display_content = content
|
|
137
|
+
header = f"--- START FILE: {fname} ---"
|
|
138
|
+
|
|
139
|
+
block.append(f"{header}\n{display_content}\n--- END FILE: {fname} ---")
|
|
140
|
+
|
|
141
|
+
return "\n\n".join(block)
|
|
142
|
+
|
|
143
|
+
def get_system_prompt(
|
|
144
|
+
current_task: Optional[str] = None,
|
|
145
|
+
loaded_files: Dict[str, str] = {},
|
|
146
|
+
active_file: Optional[str] = None
|
|
147
|
+
) -> str:
|
|
148
|
+
"""Constructs the dynamic system prompt."""
|
|
149
|
+
return SYSTEM_TEMPLATE.substitute(
|
|
150
|
+
user_name=config.USER_NAME,
|
|
151
|
+
current_time=datetime.datetime.now().strftime("%Y-%m-%d %H:%M"),
|
|
152
|
+
location=config.LOCATION,
|
|
153
|
+
cwd=os.getcwd(),
|
|
154
|
+
dir_structure=get_repo_map_cached(os.getcwd()),
|
|
155
|
+
loaded_files_block=get_loaded_files_block(loaded_files, active_file),
|
|
156
|
+
context_modules=f"[TASK]: {current_task}" if current_task else "[STANDBY]",
|
|
157
|
+
user_input=""
|
|
158
|
+
).strip()
|
package/core/state.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# NOVA_CLI\core\state.py
|
|
2
|
+
|
|
3
|
+
class SessionState:
|
|
4
|
+
def __init__(self) -> None:
|
|
5
|
+
"""
|
|
6
|
+
Initializes the SessionState with default values.
|
|
7
|
+
|
|
8
|
+
Attributes:
|
|
9
|
+
active_file (str | None): The file currently being focused on/edited (absolute path).
|
|
10
|
+
last_ai_response (str): The last response from the AI.
|
|
11
|
+
last_generated_code (str | None): The last valid code block generated.
|
|
12
|
+
loaded_files (dict[str, str]): Canonical mapping {basename: content} for persistent context.
|
|
13
|
+
loaded_paths (dict[str, str]): Mapping {basename: absolute_path} for writing/applying edits.
|
|
14
|
+
total_tokens_used (int): The total number of tokens used.
|
|
15
|
+
"""
|
|
16
|
+
self.active_file: str | None = None
|
|
17
|
+
self.last_ai_response: str = ""
|
|
18
|
+
self.last_generated_code: str | None = None
|
|
19
|
+
|
|
20
|
+
# Canonical: basename -> content
|
|
21
|
+
self.loaded_files: dict[str, str] = {}
|
|
22
|
+
|
|
23
|
+
# Canonical: basename -> abs path
|
|
24
|
+
self.loaded_paths: dict[str, str] = {}
|
|
25
|
+
|
|
26
|
+
self.total_tokens_used: int = 0
|
|
27
|
+
|
|
28
|
+
def reset(self) -> None:
|
|
29
|
+
"""
|
|
30
|
+
Resets the session state to initial defaults.
|
|
31
|
+
"""
|
|
32
|
+
self.active_file = None
|
|
33
|
+
self.last_ai_response = ""
|
|
34
|
+
self.last_generated_code = None
|
|
35
|
+
self.loaded_files = {}
|
|
36
|
+
self.loaded_paths = {}
|
|
37
|
+
self.total_tokens_used = 0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
File without changes
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# NOVA_CLI/nova_cli/cli/commands.py
|
|
2
|
+
|
|
3
|
+
def register_core_commands(registry, shell):
|
|
4
|
+
|
|
5
|
+
registry.register("login", shell.cmd_login)
|
|
6
|
+
registry.register("nova login", shell.cmd_login)
|
|
7
|
+
# model / git
|
|
8
|
+
registry.register(":model", shell.cmd_model)
|
|
9
|
+
registry.register(":gitoptions", shell.cmd_gitoptions)
|
|
10
|
+
|
|
11
|
+
# reset / unload
|
|
12
|
+
registry.register(":reset", shell.cmd_reset)
|
|
13
|
+
registry.register("reset", shell.cmd_reset)
|
|
14
|
+
|
|
15
|
+
registry.register(":unload", shell.cmd_unload)
|
|
16
|
+
registry.register("unload", shell.cmd_unload)
|
|
17
|
+
|
|
18
|
+
# map / ls
|
|
19
|
+
registry.register(":map", shell.cmd_map)
|
|
20
|
+
registry.register("ls", shell.cmd_map)
|
|
21
|
+
|
|
22
|
+
# wizard / apply / paste
|
|
23
|
+
registry.register(":wizard", shell.cmd_wizard)
|
|
24
|
+
registry.register(":apply", shell.cmd_apply)
|
|
25
|
+
registry.register(":paste", shell.cmd_paste)
|
|
26
|
+
|
|
27
|
+
# load / navigation
|
|
28
|
+
registry.register(":load", shell.cmd_load)
|
|
29
|
+
registry.register("cd", shell.cmd_cd)
|
|
30
|
+
registry.register("pwd", shell.cmd_pwd)
|
|
31
|
+
|
|
32
|
+
# execution / cleanup
|
|
33
|
+
registry.register("run", shell.cmd_run)
|
|
34
|
+
registry.register("clean", shell.cmd_clean)
|
|
35
|
+
|
|
36
|
+
# build command
|
|
37
|
+
registry.register("Build It", shell.cmd_build_it)
|
|
38
|
+
registry.register("build it", shell.cmd_build_it)
|
|
39
|
+
registry.register("Build", shell.cmd_build_it)
|
|
40
|
+
registry.register("build", shell.cmd_build_it)
|
|
41
|
+
|
|
42
|
+
# exit / help
|
|
43
|
+
registry.register("exit", shell.cmd_exit)
|
|
44
|
+
registry.register("quit", shell.cmd_exit)
|
|
45
|
+
|
|
46
|
+
registry.register("help", shell.cmd_help)
|
|
47
|
+
registry.register(":help", shell.cmd_help)
|
|
48
|
+
|
|
49
|
+
#registry.register("logout", shell.cmd_logout) # optional, future-proof
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
from nova_cli.cli.shell import NovaShell
|
|
4
|
+
from nova_cli.nova_core.ai.api_client import BridgeyeAPIClient
|
|
5
|
+
from nova_cli import config as cli_config
|
|
6
|
+
from nova_cli.nova_core.auth.storage import is_logged_in, has_refresh_token
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _doctor() -> int:
|
|
10
|
+
api = BridgeyeAPIClient()
|
|
11
|
+
|
|
12
|
+
print("NOVA CLI — Doctor")
|
|
13
|
+
print(f"- NOVA_API_BASE_URL: {cli_config.NOVA_API_BASE_URL}")
|
|
14
|
+
print(f"- NOVA_AUTH_BASE_URL: {cli_config.NOVA_AUTH_BASE_URL}")
|
|
15
|
+
print(f"- Default provider: {cli_config.DEFAULT_PROVIDER}")
|
|
16
|
+
print(f"- Default model: {cli_config.DEFAULT_MODEL}")
|
|
17
|
+
|
|
18
|
+
api_ok = api.health()
|
|
19
|
+
print(f"- API reachable: {'yes' if api_ok else 'no'}")
|
|
20
|
+
|
|
21
|
+
logged_in = is_logged_in()
|
|
22
|
+
print(f"- Logged in: {'yes' if logged_in else 'no'}")
|
|
23
|
+
|
|
24
|
+
# Helpful extra signal: refresh token presence
|
|
25
|
+
rt = has_refresh_token()
|
|
26
|
+
print(f"- Has refresh token: {'yes' if rt else 'no'}")
|
|
27
|
+
|
|
28
|
+
if not api_ok:
|
|
29
|
+
print("\nFix:")
|
|
30
|
+
print(" 1) Start NOVA_API")
|
|
31
|
+
print(" 2) Or set NOVA_API_BASE_URL to the right host/port")
|
|
32
|
+
return 1
|
|
33
|
+
|
|
34
|
+
if not logged_in:
|
|
35
|
+
print("\nFix:")
|
|
36
|
+
print(" Run: nova login")
|
|
37
|
+
return 1
|
|
38
|
+
|
|
39
|
+
return 0
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def main():
|
|
43
|
+
shell = NovaShell()
|
|
44
|
+
|
|
45
|
+
# No args => interactive
|
|
46
|
+
if len(sys.argv) == 1:
|
|
47
|
+
shell.run()
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
cmd = sys.argv[1]
|
|
51
|
+
args = " ".join(sys.argv[2:]) if len(sys.argv) > 2 else ""
|
|
52
|
+
|
|
53
|
+
if cmd in ("help", "--help", "-h"):
|
|
54
|
+
shell.cmd_help("")
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
if cmd == "doctor":
|
|
58
|
+
raise SystemExit(_doctor())
|
|
59
|
+
|
|
60
|
+
if cmd == "run":
|
|
61
|
+
shell.cmd_run(args)
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
if cmd == "clean":
|
|
65
|
+
shell.cmd_clean(args)
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
if cmd == "login":
|
|
69
|
+
shell.cmd_login(args)
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
# Optional legacy alias
|
|
73
|
+
if cmd == "heal":
|
|
74
|
+
shell.cmd_run(args)
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
# Anything else => treat as chat prompt (one-shot)
|
|
78
|
+
prompt = " ".join(sys.argv[1:])
|
|
79
|
+
shell.handle_ai_request(prompt)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
if __name__ == "__main__":
|
|
83
|
+
main()
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# NOVA_CLI\nova_cli\cli\registry.py
|
|
2
|
+
|
|
3
|
+
class CommandRegistry:
|
|
4
|
+
def __init__(self):
|
|
5
|
+
self._commands = {}
|
|
6
|
+
|
|
7
|
+
def register(self, name, handler):
|
|
8
|
+
self._commands[name] = handler
|
|
9
|
+
|
|
10
|
+
def get(self, name):
|
|
11
|
+
return self._commands.get(name)
|
|
12
|
+
|
|
13
|
+
def all(self):
|
|
14
|
+
return self._commands
|