janito 0.15.0__py3-none-any.whl → 1.0.0__py3-none-any.whl
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.
- janito/__init__.py +1 -5
- janito/__main__.py +3 -5
- janito/agent/__init__.py +1 -0
- janito/agent/agent.py +96 -0
- janito/agent/config.py +113 -0
- janito/agent/config_defaults.py +10 -0
- janito/agent/conversation.py +107 -0
- janito/agent/queued_tool_handler.py +16 -0
- janito/agent/runtime_config.py +30 -0
- janito/agent/tool_handler.py +124 -0
- janito/agent/tools/__init__.py +11 -0
- janito/agent/tools/ask_user.py +63 -0
- janito/agent/tools/bash_exec.py +58 -0
- janito/agent/tools/create_directory.py +19 -0
- janito/agent/tools/create_file.py +43 -0
- janito/agent/tools/fetch_url.py +48 -0
- janito/agent/tools/file_str_replace.py +48 -0
- janito/agent/tools/find_files.py +37 -0
- janito/agent/tools/gitignore_utils.py +40 -0
- janito/agent/tools/move_file.py +37 -0
- janito/agent/tools/remove_file.py +19 -0
- janito/agent/tools/rich_live.py +37 -0
- janito/agent/tools/rich_utils.py +31 -0
- janito/agent/tools/search_text.py +41 -0
- janito/agent/tools/view_file.py +34 -0
- janito/cli/__init__.py +0 -6
- janito/cli/_print_config.py +68 -0
- janito/cli/_utils.py +8 -0
- janito/cli/arg_parser.py +26 -0
- janito/cli/config_commands.py +131 -0
- janito/cli/logging_setup.py +27 -0
- janito/cli/main.py +39 -0
- janito/cli/runner.py +135 -0
- janito/cli_chat_shell/__init__.py +1 -0
- janito/cli_chat_shell/chat_loop.py +147 -0
- janito/cli_chat_shell/commands.py +202 -0
- janito/cli_chat_shell/config_shell.py +75 -0
- janito/cli_chat_shell/load_prompt.py +15 -0
- janito/cli_chat_shell/session_manager.py +60 -0
- janito/cli_chat_shell/ui.py +136 -0
- janito/render_prompt.py +12 -0
- janito/templates/system_instructions.j2 +36 -0
- janito/web/__init__.py +0 -0
- janito/web/__main__.py +17 -0
- janito/web/app.py +132 -0
- janito-1.0.0.dist-info/METADATA +144 -0
- janito-1.0.0.dist-info/RECORD +51 -0
- {janito-0.15.0.dist-info → janito-1.0.0.dist-info}/WHEEL +2 -1
- janito-1.0.0.dist-info/entry_points.txt +2 -0
- {janito-0.15.0.dist-info → janito-1.0.0.dist-info}/licenses/LICENSE +2 -2
- janito-1.0.0.dist-info/top_level.txt +1 -0
- janito/callbacks.py +0 -34
- janito/cli/agent/__init__.py +0 -7
- janito/cli/agent/conversation.py +0 -149
- janito/cli/agent/initialization.py +0 -168
- janito/cli/agent/query.py +0 -112
- janito/cli/agent.py +0 -12
- janito/cli/app.py +0 -178
- janito/cli/commands/__init__.py +0 -12
- janito/cli/commands/config.py +0 -30
- janito/cli/commands/history.py +0 -119
- janito/cli/commands/profile.py +0 -93
- janito/cli/commands/validation.py +0 -24
- janito/cli/commands/workspace.py +0 -31
- janito/cli/commands.py +0 -12
- janito/cli/output.py +0 -29
- janito/cli/utils.py +0 -22
- janito/config/README.md +0 -104
- janito/config/__init__.py +0 -16
- janito/config/cli/__init__.py +0 -28
- janito/config/cli/commands.py +0 -397
- janito/config/cli/validators.py +0 -77
- janito/config/core/__init__.py +0 -23
- janito/config/core/file_operations.py +0 -90
- janito/config/core/properties.py +0 -316
- janito/config/core/singleton.py +0 -282
- janito/config/profiles/__init__.py +0 -8
- janito/config/profiles/definitions.py +0 -38
- janito/config/profiles/manager.py +0 -80
- janito/data/instructions_template.txt +0 -34
- janito/token_report.py +0 -154
- janito/tools/__init__.py +0 -44
- janito/tools/bash/bash.py +0 -157
- janito/tools/bash/unix_persistent_bash.py +0 -215
- janito/tools/bash/win_persistent_bash.py +0 -341
- janito/tools/decorators.py +0 -90
- janito/tools/delete_file.py +0 -65
- janito/tools/fetch_webpage/__init__.py +0 -23
- janito/tools/fetch_webpage/core.py +0 -182
- janito/tools/find_files.py +0 -220
- janito/tools/move_file.py +0 -72
- janito/tools/prompt_user.py +0 -57
- janito/tools/replace_file.py +0 -63
- janito/tools/rich_console.py +0 -176
- janito/tools/search_text.py +0 -226
- janito/tools/str_replace_editor/__init__.py +0 -6
- janito/tools/str_replace_editor/editor.py +0 -55
- janito/tools/str_replace_editor/handlers/__init__.py +0 -16
- janito/tools/str_replace_editor/handlers/create.py +0 -60
- janito/tools/str_replace_editor/handlers/insert.py +0 -100
- janito/tools/str_replace_editor/handlers/str_replace.py +0 -94
- janito/tools/str_replace_editor/handlers/undo.py +0 -64
- janito/tools/str_replace_editor/handlers/view.py +0 -165
- janito/tools/str_replace_editor/utils.py +0 -33
- janito/tools/think.py +0 -37
- janito/tools/usage_tracker.py +0 -137
- janito-0.15.0.dist-info/METADATA +0 -481
- janito-0.15.0.dist-info/RECORD +0 -64
- janito-0.15.0.dist-info/entry_points.txt +0 -2
@@ -0,0 +1,43 @@
|
|
1
|
+
import os
|
2
|
+
from janito.agent.tool_handler import ToolHandler
|
3
|
+
from janito.agent.tools.rich_utils import print_info, print_success, print_error, format_path
|
4
|
+
|
5
|
+
@ToolHandler.register_tool
|
6
|
+
def create_file(path: str, content: str, overwrite: bool = False) -> str:
|
7
|
+
"""
|
8
|
+
Create a file with the specified content.
|
9
|
+
|
10
|
+
path: The path of the file to create
|
11
|
+
content: The content to write into the file
|
12
|
+
overwrite: Whether to overwrite the file if it exists (default: False)
|
13
|
+
"""
|
14
|
+
old_lines = None
|
15
|
+
if os.path.exists(path):
|
16
|
+
if os.path.isdir(path):
|
17
|
+
print_error("❌ Error: is a directory")
|
18
|
+
return f"❌ Cannot create file: '{path}' is an existing directory."
|
19
|
+
if overwrite:
|
20
|
+
try:
|
21
|
+
with open(path, "r", encoding="utf-8") as f:
|
22
|
+
old_lines = sum(1 for _ in f)
|
23
|
+
except Exception:
|
24
|
+
old_lines = 'unknown'
|
25
|
+
else:
|
26
|
+
print_error(f"❗ Error: file '{path}' exists and overwrite is False")
|
27
|
+
return f"❗ Cannot create file: '{path}' already exists and overwrite is False."
|
28
|
+
|
29
|
+
new_lines = content.count('\n') + 1 if content else 0
|
30
|
+
|
31
|
+
if old_lines is not None:
|
32
|
+
print_info(f"♻️ Replacing file: '{format_path(path)}' (line count: {old_lines} -> {new_lines}) ... ")
|
33
|
+
else:
|
34
|
+
print_info(f"📝 Creating file: '{format_path(path)}' (lines: {new_lines}) ... ")
|
35
|
+
|
36
|
+
try:
|
37
|
+
with open(path, "w", encoding="utf-8") as f:
|
38
|
+
f.write(content)
|
39
|
+
print_success("✅ Success")
|
40
|
+
return f"✅ Successfully created the file at '{path}'."
|
41
|
+
except Exception as e:
|
42
|
+
print_error(f"❌ Error: {e}")
|
43
|
+
return f"❌ Failed to create the file at '{path}': {e}"
|
@@ -0,0 +1,48 @@
|
|
1
|
+
import requests
|
2
|
+
from bs4 import BeautifulSoup
|
3
|
+
from janito.agent.tool_handler import ToolHandler
|
4
|
+
from janito.agent.tools.rich_utils import print_info, print_success, print_error
|
5
|
+
|
6
|
+
@ToolHandler.register_tool
|
7
|
+
def fetch_url(url: str, search_strings: list[str] = None, on_progress: callable = None) -> str:
|
8
|
+
"""
|
9
|
+
Fetch the content of a web page and extract its text.
|
10
|
+
|
11
|
+
url: The URL to fetch.
|
12
|
+
search_strings: Optional list of strings to filter the extracted text around those strings.
|
13
|
+
on_progress: Optional callback function for streaming progress updates.
|
14
|
+
"""
|
15
|
+
if on_progress:
|
16
|
+
on_progress({'event': 'start', 'url': url})
|
17
|
+
print_info(f"\U0001F310 Fetching URL: {url} ... ")
|
18
|
+
try:
|
19
|
+
response = requests.get(url, timeout=10)
|
20
|
+
response.raise_for_status()
|
21
|
+
if on_progress:
|
22
|
+
on_progress({'event': 'fetched', 'status_code': response.status_code})
|
23
|
+
soup = BeautifulSoup(response.text, 'html.parser')
|
24
|
+
text = soup.get_text(separator=' ', strip=True)
|
25
|
+
|
26
|
+
if search_strings:
|
27
|
+
filtered = []
|
28
|
+
for s in search_strings:
|
29
|
+
idx = text.find(s)
|
30
|
+
if idx != -1:
|
31
|
+
start = max(0, idx - 200)
|
32
|
+
end = min(len(text), idx + len(s) + 200)
|
33
|
+
snippet = text[start:end]
|
34
|
+
filtered.append(snippet)
|
35
|
+
if filtered:
|
36
|
+
text = '\n...\n'.join(filtered)
|
37
|
+
else:
|
38
|
+
text = "No matches found for the provided search strings."
|
39
|
+
|
40
|
+
print_success("\u2705 Success")
|
41
|
+
if on_progress:
|
42
|
+
on_progress({'event': 'done'})
|
43
|
+
return text
|
44
|
+
except Exception as e:
|
45
|
+
print_error(f"\u274c Error: {e}")
|
46
|
+
if on_progress:
|
47
|
+
on_progress({'event': 'error', 'error': str(e)})
|
48
|
+
return f"\u274c Failed to fetch URL '{url}': {e}"
|
@@ -0,0 +1,48 @@
|
|
1
|
+
import os
|
2
|
+
from janito.agent.tool_handler import ToolHandler
|
3
|
+
from janito.agent.tools.rich_utils import print_info, print_success, print_error, format_path
|
4
|
+
|
5
|
+
@ToolHandler.register_tool
|
6
|
+
def file_str_replace(path: str, old_string: str, new_string: str) -> str:
|
7
|
+
"""
|
8
|
+
Replace a unique occurrence of a string in a file.
|
9
|
+
|
10
|
+
path: Path to the file
|
11
|
+
old_string: The exact string to replace
|
12
|
+
- must be unique within all the file lines
|
13
|
+
new_string: The replacement string
|
14
|
+
|
15
|
+
|
16
|
+
|
17
|
+
Returns a message indicating success on an error
|
18
|
+
"""
|
19
|
+
if not os.path.isfile(path):
|
20
|
+
print_error(f"❌ Error: '{path}' is not a valid file.")
|
21
|
+
return f"❌ Error: '{path}' is not a valid file."
|
22
|
+
|
23
|
+
try:
|
24
|
+
with open(path, 'r', encoding='utf-8') as f:
|
25
|
+
content = f.read()
|
26
|
+
except Exception as e:
|
27
|
+
print_error(f"❌ Error reading file: {e}")
|
28
|
+
return f"❌ Failed to read file '{path}': {e}"
|
29
|
+
|
30
|
+
num_matches = content.count(old_string)
|
31
|
+
|
32
|
+
if num_matches == 0:
|
33
|
+
print_info(f"ℹ️ No occurrences of the target string found in '{format_path(path)}'.")
|
34
|
+
return f"ℹ️ No occurrences of the target string found in '{path}'."
|
35
|
+
elif num_matches > 1:
|
36
|
+
print_error(f"❌ Error: More than one occurrence ({num_matches}) of the target string found in '{format_path(path)}'. Aborting replacement.")
|
37
|
+
return f"❌ Error: More than one occurrence ({num_matches}) of the target string found in '{path}'. Aborting replacement."
|
38
|
+
|
39
|
+
new_content = content.replace(old_string, new_string, 1)
|
40
|
+
|
41
|
+
try:
|
42
|
+
with open(path, 'w', encoding='utf-8') as f:
|
43
|
+
f.write(new_content)
|
44
|
+
print_success(f"✅ Replaced the unique occurrence in '{format_path(path)}'.")
|
45
|
+
return f"✅ Successfully replaced the unique occurrence in '{path}'."
|
46
|
+
except Exception as e:
|
47
|
+
print_error(f"❌ Error writing file: {e}")
|
48
|
+
return f"❌ Failed to write updated content to '{path}': {e}"
|
@@ -0,0 +1,37 @@
|
|
1
|
+
import os
|
2
|
+
import fnmatch
|
3
|
+
from janito.agent.tool_handler import ToolHandler
|
4
|
+
from janito.agent.tools.rich_utils import print_info, print_success, print_error, format_path, format_number
|
5
|
+
from janito.agent.tools.gitignore_utils import load_gitignore_patterns, filter_ignored
|
6
|
+
|
7
|
+
@ToolHandler.register_tool
|
8
|
+
def find_files(directory: str, pattern: str = "*") -> str:
|
9
|
+
"""
|
10
|
+
Recursively find files matching a pattern within a directory, skipping ignored files/dirs.
|
11
|
+
|
12
|
+
directory: The root directory to start searching from.
|
13
|
+
pattern: Glob pattern to match filenames (default: '*').
|
14
|
+
"""
|
15
|
+
print_info(f"🔍 Searching for files in '{format_path(directory)}' matching pattern '{pattern}' ... ")
|
16
|
+
|
17
|
+
# Check if pattern is an exact relative path to a file
|
18
|
+
full_path = os.path.join(directory, pattern)
|
19
|
+
if os.path.isfile(full_path):
|
20
|
+
print_success("✅ Found 1 file (exact match)")
|
21
|
+
return full_path
|
22
|
+
|
23
|
+
matches = []
|
24
|
+
ignore_patterns = load_gitignore_patterns()
|
25
|
+
try:
|
26
|
+
for root, dirs, files in os.walk(directory):
|
27
|
+
dirs, files = filter_ignored(root, dirs, files, ignore_patterns)
|
28
|
+
for filename in fnmatch.filter(files, pattern):
|
29
|
+
matches.append(os.path.join(root, filename))
|
30
|
+
print_success(f"✅ Found {format_number(len(matches))} files")
|
31
|
+
if matches:
|
32
|
+
return "\n".join(matches)
|
33
|
+
else:
|
34
|
+
return "No matching files found."
|
35
|
+
except Exception as e:
|
36
|
+
print_error(f"❌ Error: {e}")
|
37
|
+
return f"❌ Failed to search files in '{directory}': {e}"
|
@@ -0,0 +1,40 @@
|
|
1
|
+
import os
|
2
|
+
import pathspec
|
3
|
+
|
4
|
+
_spec = None
|
5
|
+
|
6
|
+
|
7
|
+
def load_gitignore_patterns(gitignore_path='.gitignore'):
|
8
|
+
global _spec
|
9
|
+
if not os.path.exists(gitignore_path):
|
10
|
+
_spec = pathspec.PathSpec.from_lines('gitwildmatch', [])
|
11
|
+
return _spec
|
12
|
+
with open(gitignore_path, 'r') as f:
|
13
|
+
lines = f.readlines()
|
14
|
+
_spec = pathspec.PathSpec.from_lines('gitwildmatch', lines)
|
15
|
+
return _spec
|
16
|
+
|
17
|
+
|
18
|
+
def is_ignored(path):
|
19
|
+
global _spec
|
20
|
+
if _spec is None:
|
21
|
+
_spec = load_gitignore_patterns()
|
22
|
+
# Normalize path to be relative and use forward slashes
|
23
|
+
rel_path = os.path.relpath(path).replace(os.sep, '/')
|
24
|
+
return _spec.match_file(rel_path)
|
25
|
+
|
26
|
+
|
27
|
+
def filter_ignored(root, dirs, files, spec=None):
|
28
|
+
if spec is None:
|
29
|
+
global _spec
|
30
|
+
if _spec is None:
|
31
|
+
_spec = load_gitignore_patterns()
|
32
|
+
spec = _spec
|
33
|
+
|
34
|
+
def not_ignored(p):
|
35
|
+
rel_path = os.path.relpath(os.path.join(root, p)).replace(os.sep, '/')
|
36
|
+
return not spec.match_file(rel_path)
|
37
|
+
|
38
|
+
dirs[:] = [d for d in dirs if not_ignored(d)]
|
39
|
+
files = [f for f in files if not_ignored(f)]
|
40
|
+
return dirs, files
|
@@ -0,0 +1,37 @@
|
|
1
|
+
import shutil
|
2
|
+
import os
|
3
|
+
from janito.agent.tool_handler import ToolHandler
|
4
|
+
from janito.agent.tools.rich_utils import print_info, print_success, print_error, format_path
|
5
|
+
|
6
|
+
|
7
|
+
@ToolHandler.register_tool
|
8
|
+
def move_file(source_path: str, destination_path: str, overwrite: bool = False) -> str:
|
9
|
+
"""
|
10
|
+
Move a file or directory from source_path to destination_path.
|
11
|
+
|
12
|
+
source_path: The path of the file or directory to move
|
13
|
+
destination_path: The target path
|
14
|
+
overwrite: Whether to overwrite the destination if it exists (default: False)
|
15
|
+
"""
|
16
|
+
print_info(f"🚚 Moving '{format_path(source_path)}' to '{format_path(destination_path)}' ... ")
|
17
|
+
try:
|
18
|
+
if not os.path.exists(source_path):
|
19
|
+
print_error("❌ Error: source does not exist")
|
20
|
+
return f"❌ Source path '{source_path}' does not exist."
|
21
|
+
|
22
|
+
if os.path.exists(destination_path):
|
23
|
+
if not overwrite:
|
24
|
+
print_error("❌ Error: destination exists and overwrite is False")
|
25
|
+
return f"❌ Destination path '{destination_path}' already exists. Use overwrite=True to replace it."
|
26
|
+
# Remove destination if overwrite is True
|
27
|
+
if os.path.isdir(destination_path):
|
28
|
+
shutil.rmtree(destination_path)
|
29
|
+
else:
|
30
|
+
os.remove(destination_path)
|
31
|
+
|
32
|
+
shutil.move(source_path, destination_path)
|
33
|
+
print_success("✅ Success")
|
34
|
+
return f"✅ Successfully moved '{source_path}' to '{destination_path}'."
|
35
|
+
except Exception as e:
|
36
|
+
print_error(f"❌ Error: {e}")
|
37
|
+
return f"❌ Failed to move '{source_path}' to '{destination_path}': {e}"
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import os
|
2
|
+
from janito.agent.tool_handler import ToolHandler
|
3
|
+
from janito.agent.tools.rich_utils import print_info, print_success, print_error, format_path
|
4
|
+
|
5
|
+
@ToolHandler.register_tool
|
6
|
+
def remove_file(path: str) -> str:
|
7
|
+
"""
|
8
|
+
Remove a specified file.
|
9
|
+
|
10
|
+
path: The path of the file to remove
|
11
|
+
"""
|
12
|
+
print_info(f"🗑️ Removing file: '{format_path(path)}' ... ")
|
13
|
+
try:
|
14
|
+
os.remove(path)
|
15
|
+
print_success("✅ Success")
|
16
|
+
return f"✅ Successfully deleted the file at '{path}'."
|
17
|
+
except Exception as e:
|
18
|
+
print_error(f"❌ Error: {e}")
|
19
|
+
return f"❌ Failed to delete the file at '{path}': {e}"
|
@@ -0,0 +1,37 @@
|
|
1
|
+
from contextlib import contextmanager
|
2
|
+
from rich.live import Live
|
3
|
+
from rich.panel import Panel
|
4
|
+
from rich.console import Console
|
5
|
+
|
6
|
+
console = Console()
|
7
|
+
|
8
|
+
_global_live = None
|
9
|
+
|
10
|
+
@contextmanager
|
11
|
+
def global_live_panel(title="Working..."):
|
12
|
+
global _global_live
|
13
|
+
if _global_live is None:
|
14
|
+
_global_live = Live(Panel("", title=title), console=console, refresh_per_second=4)
|
15
|
+
_global_live.start()
|
16
|
+
try:
|
17
|
+
yield _global_live
|
18
|
+
finally:
|
19
|
+
pass # Do not stop here; stopping is handled explicitly
|
20
|
+
|
21
|
+
def stop_global_live_panel():
|
22
|
+
global _global_live
|
23
|
+
if _global_live is not None:
|
24
|
+
_global_live.stop()
|
25
|
+
_global_live = None
|
26
|
+
|
27
|
+
@contextmanager
|
28
|
+
def live_panel(title="Working..."):
|
29
|
+
global _global_live
|
30
|
+
if _global_live is not None:
|
31
|
+
# Update the global panel content instead of creating a nested panel
|
32
|
+
_global_live.update(Panel("", title=title))
|
33
|
+
yield _global_live
|
34
|
+
else:
|
35
|
+
# Fallback: create a temporary panel if no global panel is running
|
36
|
+
with Live(Panel("", title=title), console=console, refresh_per_second=4) as live:
|
37
|
+
yield live
|
@@ -0,0 +1,31 @@
|
|
1
|
+
from rich.console import Console
|
2
|
+
from rich.text import Text
|
3
|
+
|
4
|
+
console = Console()
|
5
|
+
|
6
|
+
def print_info(message: str):
|
7
|
+
console.print(message, style="cyan")
|
8
|
+
|
9
|
+
def print_success(message: str):
|
10
|
+
console.print(message, style="bold green")
|
11
|
+
|
12
|
+
def print_error(message: str):
|
13
|
+
console.print(message, style="bold red")
|
14
|
+
|
15
|
+
def print_warning(message: str):
|
16
|
+
console.print(message, style="yellow")
|
17
|
+
|
18
|
+
def print_magenta(message: str):
|
19
|
+
console.print(message, style="magenta")
|
20
|
+
|
21
|
+
def print_bash_stdout(message: str):
|
22
|
+
console.print(message, style="bold white on blue")
|
23
|
+
|
24
|
+
def print_bash_stderr(message: str):
|
25
|
+
console.print(message, style="bold white on red")
|
26
|
+
|
27
|
+
def format_path(path: str) -> Text:
|
28
|
+
return Text(path, style="cyan")
|
29
|
+
|
30
|
+
def format_number(number) -> Text:
|
31
|
+
return Text(str(number), style="magenta")
|
@@ -0,0 +1,41 @@
|
|
1
|
+
import os
|
2
|
+
import re
|
3
|
+
import fnmatch
|
4
|
+
from janito.agent.tool_handler import ToolHandler
|
5
|
+
from janito.agent.tools.rich_utils import print_info, print_success, print_error, format_path, format_number
|
6
|
+
from janito.agent.tools.gitignore_utils import load_gitignore_patterns, filter_ignored
|
7
|
+
|
8
|
+
@ToolHandler.register_tool
|
9
|
+
def search_text(directory: str, file_pattern: str, text_pattern: str, case_sensitive: bool = False):
|
10
|
+
"""
|
11
|
+
directory: Root directory to search.
|
12
|
+
file_pattern: Glob pattern for filenames (e.g., '*.py').
|
13
|
+
text_pattern: Regex pattern to search within files.
|
14
|
+
case_sensitive: Whether the search is case sensitive.
|
15
|
+
|
16
|
+
Returns a string with matches, each in 'filepath:line_number:matched_line' format, separated by newlines.
|
17
|
+
"""
|
18
|
+
print_info(f"🔎 Searching for pattern '{text_pattern}' in files under '{format_path(directory)}' matching '{file_pattern}' ...")
|
19
|
+
flags = 0 if case_sensitive else re.IGNORECASE
|
20
|
+
regex = re.compile(text_pattern, flags)
|
21
|
+
results = []
|
22
|
+
ignore_patterns = load_gitignore_patterns()
|
23
|
+
|
24
|
+
try:
|
25
|
+
for root, dirs, files in os.walk(directory):
|
26
|
+
dirs, files = filter_ignored(root, dirs, files, ignore_patterns)
|
27
|
+
for filename in fnmatch.filter(files, file_pattern):
|
28
|
+
filepath = os.path.join(root, filename)
|
29
|
+
try:
|
30
|
+
with open(filepath, 'r', encoding='utf-8', errors='replace') as f:
|
31
|
+
for lineno, line in enumerate(f, start=1):
|
32
|
+
if regex.search(line):
|
33
|
+
results.append(f"{filepath}:{lineno}:{line.rstrip()}")
|
34
|
+
except Exception as e:
|
35
|
+
print_error(f"❌ Error reading file '{filepath}': {e}")
|
36
|
+
continue # Ignore unreadable files
|
37
|
+
print_success(f"✅ Found {format_number(len(results))} matches")
|
38
|
+
except Exception as e:
|
39
|
+
print_error(f"❌ Error during search: {e}")
|
40
|
+
|
41
|
+
return "\n".join(results)
|
@@ -0,0 +1,34 @@
|
|
1
|
+
import os
|
2
|
+
from janito.agent.tool_handler import ToolHandler
|
3
|
+
from janito.agent.tools.rich_utils import print_info, print_success, print_error, format_path, format_number
|
4
|
+
|
5
|
+
@ToolHandler.register_tool
|
6
|
+
def view_file(path: str, start_line: int = 1, end_line: int = None) -> str:
|
7
|
+
"""
|
8
|
+
View the contents of a file or list the contents of a directory.
|
9
|
+
|
10
|
+
path: The path of the file or directory to view
|
11
|
+
start_line: The starting line number (1-based, default: 1)
|
12
|
+
end_line: The ending line number (inclusive). If None, view until end of file.
|
13
|
+
"""
|
14
|
+
print_info(f"📂 View '{format_path(path)}' lines {format_number(start_line)} to {format_number(end_line) if end_line else 'end of file'}")
|
15
|
+
if os.path.isdir(path):
|
16
|
+
files = os.listdir(path)
|
17
|
+
print_success(f"✅ {format_number(len(files))} items")
|
18
|
+
return "\n".join(files)
|
19
|
+
else:
|
20
|
+
with open(path, "r", encoding="utf-8") as f:
|
21
|
+
lines = f.readlines()
|
22
|
+
|
23
|
+
total_lines = len(lines)
|
24
|
+
if end_line is None or end_line > total_lines:
|
25
|
+
end_line = total_lines
|
26
|
+
|
27
|
+
# Adjust for 0-based index
|
28
|
+
start_idx = max(start_line - 1, 0)
|
29
|
+
end_idx = end_line
|
30
|
+
|
31
|
+
selected_lines = lines[start_idx:end_idx]
|
32
|
+
content = '\n'.join(f"{i + start_line}: {line.rstrip()}" for i, line in enumerate(selected_lines))
|
33
|
+
print_success(f"✅ Returned lines {format_number(start_line)} to {format_number(end_line)} of {format_number(total_lines)}")
|
34
|
+
return content
|
janito/cli/__init__.py
CHANGED
@@ -0,0 +1,68 @@
|
|
1
|
+
import os
|
2
|
+
from rich import print
|
3
|
+
from ._utils import home_shorten
|
4
|
+
|
5
|
+
def print_config_items(items, color_label=None):
|
6
|
+
if not items:
|
7
|
+
return
|
8
|
+
if color_label:
|
9
|
+
print(color_label)
|
10
|
+
home = os.path.expanduser("~")
|
11
|
+
for key, value in items.items():
|
12
|
+
if key == "system_prompt" and isinstance(value, str):
|
13
|
+
if value.startswith(home):
|
14
|
+
print(f"{key} = {home_shorten(value)}")
|
15
|
+
else:
|
16
|
+
print(f"{key} = {value}")
|
17
|
+
else:
|
18
|
+
print(f"{key} = {value}")
|
19
|
+
print()
|
20
|
+
|
21
|
+
def print_full_config(local_config, global_config, unified_config, config_defaults, console=None):
|
22
|
+
"""
|
23
|
+
Print local, global, and default config values in a unified way.
|
24
|
+
Handles masking API keys and showing the template file for system_prompt if not set.
|
25
|
+
"""
|
26
|
+
local_items = {}
|
27
|
+
global_items = {}
|
28
|
+
local_keys = set(local_config.all().keys())
|
29
|
+
global_keys = set(global_config.all().keys())
|
30
|
+
all_keys = set(config_defaults.keys()) | global_keys | local_keys
|
31
|
+
out = print if console is None else console.print
|
32
|
+
if not (local_keys or global_keys):
|
33
|
+
out("No configuration found.")
|
34
|
+
else:
|
35
|
+
for key in sorted(local_keys):
|
36
|
+
if key == "api_key":
|
37
|
+
value = local_config.get("api_key")
|
38
|
+
value = value[:4] + '...' + value[-4:] if value and len(value) > 8 else ('***' if value else None)
|
39
|
+
else:
|
40
|
+
value = unified_config.get(key)
|
41
|
+
local_items[key] = value
|
42
|
+
for key in sorted(global_keys - local_keys):
|
43
|
+
if key == "api_key":
|
44
|
+
value = global_config.get("api_key")
|
45
|
+
value = value[:4] + '...' + value[-4:] if value and len(value) > 8 else ('***' if value else None)
|
46
|
+
else:
|
47
|
+
value = unified_config.get(key)
|
48
|
+
global_items[key] = value
|
49
|
+
# Mask API key
|
50
|
+
for cfg in (local_items, global_items):
|
51
|
+
if 'api_key' in cfg and cfg['api_key']:
|
52
|
+
val = cfg['api_key']
|
53
|
+
cfg['api_key'] = val[:4] + '...' + val[-4:] if len(val) > 8 else '***'
|
54
|
+
print_config_items(local_items, color_label="[cyan]🏠 Local Configuration[/cyan]")
|
55
|
+
print_config_items(global_items, color_label="[yellow]🌐 Global Configuration[/yellow]")
|
56
|
+
# Show defaults for unset keys
|
57
|
+
shown_keys = set(local_items.keys()) | set(global_items.keys())
|
58
|
+
default_items = {k: v for k, v in config_defaults.items() if k not in shown_keys and k != 'api_key'}
|
59
|
+
if default_items:
|
60
|
+
out("[green]🟢 Defaults (not set in config files)[/green]")
|
61
|
+
from pathlib import Path
|
62
|
+
template_path = Path(__file__).parent.parent / "templates" / "system_instructions.j2"
|
63
|
+
for key, value in default_items.items():
|
64
|
+
if key == "system_prompt" and value is None:
|
65
|
+
out(f"{key} = file: {home_shorten(str(template_path))}")
|
66
|
+
else:
|
67
|
+
out(f"{key} = {value}")
|
68
|
+
out("")
|
janito/cli/_utils.py
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
import os
|
2
|
+
|
3
|
+
def home_shorten(path: str) -> str:
|
4
|
+
"""If path starts with the user's home directory, replace it with ~."""
|
5
|
+
home = os.path.expanduser("~")
|
6
|
+
if path and isinstance(path, str) and path.startswith(home):
|
7
|
+
return path.replace(home, "~", 1)
|
8
|
+
return path
|
janito/cli/arg_parser.py
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
import argparse
|
2
|
+
|
3
|
+
|
4
|
+
def create_parser():
|
5
|
+
parser = argparse.ArgumentParser(description="OpenRouter API call using OpenAI Python SDK")
|
6
|
+
parser.add_argument("prompt", type=str, nargs="?", help="Prompt to send to the model")
|
7
|
+
parser.add_argument("--max-tokens", type=int, default=None, help="Maximum tokens for model response (overrides config, default: 200000)")
|
8
|
+
parser.add_argument("-s", "--system-prompt", type=str, default=None, help="Optional system prompt")
|
9
|
+
parser.add_argument("-r", "--role", type=str, default=None, help="Role description for the system prompt")
|
10
|
+
parser.add_argument("-t", "--temperature", type=float, default=None, help="Sampling temperature (e.g., 0.0 - 2.0)")
|
11
|
+
parser.add_argument("--verbose-http", action="store_true", help="Enable verbose HTTP logging")
|
12
|
+
parser.add_argument("--verbose-http-raw", action="store_true", help="Enable raw HTTP wire-level logging")
|
13
|
+
parser.add_argument("--verbose-response", action="store_true", help="Pretty print the full response object")
|
14
|
+
parser.add_argument("--show-system", action="store_true", help="Show model, parameters, system prompt, and tool definitions, then exit")
|
15
|
+
parser.add_argument("--verbose-tools", action="store_true", help="Print tool call parameters and results")
|
16
|
+
parser.add_argument("--set-local-config", type=str, default=None, help='Set a local config key-value pair, format "key=val"')
|
17
|
+
parser.add_argument("--set-global-config", type=str, default=None, help='Set a global config key-value pair, format "key=val"')
|
18
|
+
parser.add_argument("--show-config", action="store_true", help="Show effective configuration and exit")
|
19
|
+
parser.add_argument("--set-api-key", type=str, default=None, help="Set and save the API key globally")
|
20
|
+
parser.add_argument("--version", action="store_true", help="Show program's version number and exit")
|
21
|
+
parser.add_argument("--help-config", action="store_true", help="Show all configuration options and exit")
|
22
|
+
parser.add_argument("--continue-session", action="store_true", help="Continue from the last saved conversation")
|
23
|
+
parser.add_argument("--web", action="store_true", help="Launch the Janito web server instead of CLI")
|
24
|
+
parser.add_argument("--config-reset-local", action="store_true", help="Remove the local config file (~/.janito/config.json)")
|
25
|
+
parser.add_argument("--config-reset-global", action="store_true", help="Remove the global config file (~/.janito/config.json)")
|
26
|
+
return parser
|