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
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
@@ -0,0 +1,5 @@
1
+ {
2
+ "python-envs.defaultEnvManager": "ms-python.python:conda",
3
+ "python-envs.defaultPackageManager": "ms-python.python:conda",
4
+ "python-envs.pythonProjects": []
5
+ }
package/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # NOVA CLI
2
+
3
+ Official CLI for Bridgeye Nova.
4
+
5
+ ## Installation
6
+
7
+ ### Via pip
8
+ ```bash
9
+ pip install bridgeye-nova
package/bin/nova ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bash
2
+ # This script calls the python 'nova' command installed via pip
3
+ nova "$@"
File without changes
@@ -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"
@@ -0,0 +1,4 @@
1
+ from nova_cli.cli.main import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
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