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.
Files changed (109) hide show
  1. janito/__init__.py +1 -5
  2. janito/__main__.py +3 -5
  3. janito/agent/__init__.py +1 -0
  4. janito/agent/agent.py +96 -0
  5. janito/agent/config.py +113 -0
  6. janito/agent/config_defaults.py +10 -0
  7. janito/agent/conversation.py +107 -0
  8. janito/agent/queued_tool_handler.py +16 -0
  9. janito/agent/runtime_config.py +30 -0
  10. janito/agent/tool_handler.py +124 -0
  11. janito/agent/tools/__init__.py +11 -0
  12. janito/agent/tools/ask_user.py +63 -0
  13. janito/agent/tools/bash_exec.py +58 -0
  14. janito/agent/tools/create_directory.py +19 -0
  15. janito/agent/tools/create_file.py +43 -0
  16. janito/agent/tools/fetch_url.py +48 -0
  17. janito/agent/tools/file_str_replace.py +48 -0
  18. janito/agent/tools/find_files.py +37 -0
  19. janito/agent/tools/gitignore_utils.py +40 -0
  20. janito/agent/tools/move_file.py +37 -0
  21. janito/agent/tools/remove_file.py +19 -0
  22. janito/agent/tools/rich_live.py +37 -0
  23. janito/agent/tools/rich_utils.py +31 -0
  24. janito/agent/tools/search_text.py +41 -0
  25. janito/agent/tools/view_file.py +34 -0
  26. janito/cli/__init__.py +0 -6
  27. janito/cli/_print_config.py +68 -0
  28. janito/cli/_utils.py +8 -0
  29. janito/cli/arg_parser.py +26 -0
  30. janito/cli/config_commands.py +131 -0
  31. janito/cli/logging_setup.py +27 -0
  32. janito/cli/main.py +39 -0
  33. janito/cli/runner.py +135 -0
  34. janito/cli_chat_shell/__init__.py +1 -0
  35. janito/cli_chat_shell/chat_loop.py +147 -0
  36. janito/cli_chat_shell/commands.py +202 -0
  37. janito/cli_chat_shell/config_shell.py +75 -0
  38. janito/cli_chat_shell/load_prompt.py +15 -0
  39. janito/cli_chat_shell/session_manager.py +60 -0
  40. janito/cli_chat_shell/ui.py +136 -0
  41. janito/render_prompt.py +12 -0
  42. janito/templates/system_instructions.j2 +36 -0
  43. janito/web/__init__.py +0 -0
  44. janito/web/__main__.py +17 -0
  45. janito/web/app.py +132 -0
  46. janito-1.0.0.dist-info/METADATA +144 -0
  47. janito-1.0.0.dist-info/RECORD +51 -0
  48. {janito-0.15.0.dist-info → janito-1.0.0.dist-info}/WHEEL +2 -1
  49. janito-1.0.0.dist-info/entry_points.txt +2 -0
  50. {janito-0.15.0.dist-info → janito-1.0.0.dist-info}/licenses/LICENSE +2 -2
  51. janito-1.0.0.dist-info/top_level.txt +1 -0
  52. janito/callbacks.py +0 -34
  53. janito/cli/agent/__init__.py +0 -7
  54. janito/cli/agent/conversation.py +0 -149
  55. janito/cli/agent/initialization.py +0 -168
  56. janito/cli/agent/query.py +0 -112
  57. janito/cli/agent.py +0 -12
  58. janito/cli/app.py +0 -178
  59. janito/cli/commands/__init__.py +0 -12
  60. janito/cli/commands/config.py +0 -30
  61. janito/cli/commands/history.py +0 -119
  62. janito/cli/commands/profile.py +0 -93
  63. janito/cli/commands/validation.py +0 -24
  64. janito/cli/commands/workspace.py +0 -31
  65. janito/cli/commands.py +0 -12
  66. janito/cli/output.py +0 -29
  67. janito/cli/utils.py +0 -22
  68. janito/config/README.md +0 -104
  69. janito/config/__init__.py +0 -16
  70. janito/config/cli/__init__.py +0 -28
  71. janito/config/cli/commands.py +0 -397
  72. janito/config/cli/validators.py +0 -77
  73. janito/config/core/__init__.py +0 -23
  74. janito/config/core/file_operations.py +0 -90
  75. janito/config/core/properties.py +0 -316
  76. janito/config/core/singleton.py +0 -282
  77. janito/config/profiles/__init__.py +0 -8
  78. janito/config/profiles/definitions.py +0 -38
  79. janito/config/profiles/manager.py +0 -80
  80. janito/data/instructions_template.txt +0 -34
  81. janito/token_report.py +0 -154
  82. janito/tools/__init__.py +0 -44
  83. janito/tools/bash/bash.py +0 -157
  84. janito/tools/bash/unix_persistent_bash.py +0 -215
  85. janito/tools/bash/win_persistent_bash.py +0 -341
  86. janito/tools/decorators.py +0 -90
  87. janito/tools/delete_file.py +0 -65
  88. janito/tools/fetch_webpage/__init__.py +0 -23
  89. janito/tools/fetch_webpage/core.py +0 -182
  90. janito/tools/find_files.py +0 -220
  91. janito/tools/move_file.py +0 -72
  92. janito/tools/prompt_user.py +0 -57
  93. janito/tools/replace_file.py +0 -63
  94. janito/tools/rich_console.py +0 -176
  95. janito/tools/search_text.py +0 -226
  96. janito/tools/str_replace_editor/__init__.py +0 -6
  97. janito/tools/str_replace_editor/editor.py +0 -55
  98. janito/tools/str_replace_editor/handlers/__init__.py +0 -16
  99. janito/tools/str_replace_editor/handlers/create.py +0 -60
  100. janito/tools/str_replace_editor/handlers/insert.py +0 -100
  101. janito/tools/str_replace_editor/handlers/str_replace.py +0 -94
  102. janito/tools/str_replace_editor/handlers/undo.py +0 -64
  103. janito/tools/str_replace_editor/handlers/view.py +0 -165
  104. janito/tools/str_replace_editor/utils.py +0 -33
  105. janito/tools/think.py +0 -37
  106. janito/tools/usage_tracker.py +0 -137
  107. janito-0.15.0.dist-info/METADATA +0 -481
  108. janito-0.15.0.dist-info/RECORD +0 -64
  109. 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
@@ -1,6 +0,0 @@
1
- """
2
- CLI module for Janito.
3
- """
4
- from janito.cli.app import app
5
-
6
- __all__ = ["app"]
@@ -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
@@ -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