janito 0.15.0__py3-none-any.whl → 1.0.1__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 +138 -0
- janito/cli_chat_shell/__init__.py +1 -0
- janito/cli_chat_shell/chat_loop.py +148 -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 +38 -0
- janito/web/__init__.py +0 -0
- janito/web/__main__.py +17 -0
- janito/web/app.py +132 -0
- janito-1.0.1.dist-info/METADATA +144 -0
- janito-1.0.1.dist-info/RECORD +51 -0
- {janito-0.15.0.dist-info → janito-1.0.1.dist-info}/WHEEL +2 -1
- janito-1.0.1.dist-info/entry_points.txt +2 -0
- {janito-0.15.0.dist-info → janito-1.0.1.dist-info}/licenses/LICENSE +2 -2
- janito-1.0.1.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
@@ -1,94 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Handler for the str_replace command in str_replace_editor.
|
3
|
-
"""
|
4
|
-
import os
|
5
|
-
import pathlib
|
6
|
-
from typing import Dict, Any, Tuple
|
7
|
-
from janito.config import get_config
|
8
|
-
from janito.tools.rich_console import print_info, print_success, print_error
|
9
|
-
from janito.tools.usage_tracker import get_tracker, count_lines_in_string
|
10
|
-
from ..utils import normalize_path, _file_history
|
11
|
-
|
12
|
-
def handle_str_replace(args: Dict[str, Any]) -> Tuple[str, bool]:
|
13
|
-
"""
|
14
|
-
Replace a specific string in a file with a new string.
|
15
|
-
|
16
|
-
Args:
|
17
|
-
args: Dictionary containing:
|
18
|
-
- path: Path to the file to modify
|
19
|
-
- old_str: The text to replace (must match EXACTLY)
|
20
|
-
- new_str: The new text to insert
|
21
|
-
|
22
|
-
Returns:
|
23
|
-
A tuple containing (message, is_error)
|
24
|
-
"""
|
25
|
-
path = args.get("path")
|
26
|
-
old_str = args.get("old_str")
|
27
|
-
new_str = args.get("new_str", "") # new_str can be empty to effectively delete text
|
28
|
-
|
29
|
-
# Count lines in old and new strings
|
30
|
-
old_lines_count = len(old_str.splitlines()) if old_str else 0
|
31
|
-
new_lines_count = len(new_str.splitlines()) if new_str else 0
|
32
|
-
line_delta = new_lines_count - old_lines_count
|
33
|
-
delta_sign = "+" if line_delta > 0 else "" if line_delta == 0 else "-"
|
34
|
-
|
35
|
-
print_info(f"Replacing text in file: {path} ({old_lines_count} -> {new_lines_count} lines, {delta_sign}{abs(line_delta)})", "Replacing text in file")
|
36
|
-
|
37
|
-
if not path:
|
38
|
-
print_error("Missing required parameter: path", "Error")
|
39
|
-
return ("Missing required parameter: path", True)
|
40
|
-
if old_str is None:
|
41
|
-
print_error("Missing required parameter: old_str", "Error")
|
42
|
-
return ("Missing required parameter: old_str", True)
|
43
|
-
|
44
|
-
path = normalize_path(path)
|
45
|
-
file_path = pathlib.Path(path)
|
46
|
-
|
47
|
-
if not file_path.exists():
|
48
|
-
print_error(f"File {path} does not exist", "Error")
|
49
|
-
return (f"File {path} does not exist", True)
|
50
|
-
|
51
|
-
try:
|
52
|
-
# Read the file content
|
53
|
-
with open(file_path, 'r', encoding='utf-8') as f:
|
54
|
-
content = f.read()
|
55
|
-
|
56
|
-
# Save the current content for undo
|
57
|
-
if path not in _file_history:
|
58
|
-
_file_history[path] = []
|
59
|
-
_file_history[path].append(content)
|
60
|
-
|
61
|
-
# Check if old_str exists in the content (must match EXACTLY)
|
62
|
-
if old_str not in content:
|
63
|
-
# Only print error if not in trust mode
|
64
|
-
if not get_config().trust_mode:
|
65
|
-
print_error("No exact match", "?")
|
66
|
-
return ("Error: No exact match found for replacement. Please check your text and ensure whitespaces match exactly.", True)
|
67
|
-
|
68
|
-
# Count occurrences to check for multiple matches
|
69
|
-
match_count = content.count(old_str)
|
70
|
-
if match_count > 1:
|
71
|
-
print_error(f"Found {match_count} matches for replacement text. The old_str parameter is not unique in the file. Please include more context to make it unique.", "Error")
|
72
|
-
return (f"Error: Found {match_count} matches for replacement text. The old_str parameter is not unique in the file. Please include more context to make it unique.", True)
|
73
|
-
|
74
|
-
# Replace the string
|
75
|
-
new_content = content.replace(old_str, new_str)
|
76
|
-
|
77
|
-
# Track the number of lines replaced and the line delta
|
78
|
-
lines_changed, line_delta = count_lines_in_string(old_str, new_str)
|
79
|
-
get_tracker().increment('lines_replaced', lines_changed)
|
80
|
-
get_tracker().increment('lines_delta', line_delta)
|
81
|
-
|
82
|
-
# Write the new content
|
83
|
-
with open(file_path, 'w', encoding='utf-8') as f:
|
84
|
-
f.write(new_content)
|
85
|
-
|
86
|
-
# Show relative path if it's not an absolute path in the original input
|
87
|
-
display_path = args.get("path") if os.path.isabs(args.get("path")) else os.path.relpath(file_path, get_config().workspace_dir)
|
88
|
-
print_success(f"", "Success")
|
89
|
-
return (f"Successfully replaced string in file {display_path}", False)
|
90
|
-
except Exception as e:
|
91
|
-
# Show relative path if it's not an absolute path in the original input
|
92
|
-
display_path = args.get("path") if os.path.isabs(args.get("path")) else os.path.relpath(file_path, get_config().workspace_dir)
|
93
|
-
print_error(f"Error replacing string in file {display_path}: {str(e)}", "Error")
|
94
|
-
return (f"Error replacing string in file {display_path}: {str(e)}", True)
|
@@ -1,64 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Handler for the undo_edit command in str_replace_editor.
|
3
|
-
"""
|
4
|
-
import os
|
5
|
-
import pathlib
|
6
|
-
from typing import Dict, Any, Tuple
|
7
|
-
from janito.config import get_config
|
8
|
-
from janito.tools.rich_console import print_info, print_success, print_error, print_warning
|
9
|
-
from ..utils import normalize_path, _file_history
|
10
|
-
|
11
|
-
def handle_undo_edit(args: Dict[str, Any]) -> Tuple[str, bool]:
|
12
|
-
"""
|
13
|
-
Undo the last edit made to a file using in-memory history.
|
14
|
-
|
15
|
-
Args:
|
16
|
-
args: Dictionary containing:
|
17
|
-
- path: Path to the file whose last edit should be undone
|
18
|
-
|
19
|
-
Returns:
|
20
|
-
A tuple containing (message, is_error)
|
21
|
-
"""
|
22
|
-
path = args.get("path")
|
23
|
-
|
24
|
-
print_info(f"Undoing last edit to file: {path}", "Undo Operation")
|
25
|
-
|
26
|
-
if not path:
|
27
|
-
print_error("Missing required parameter: path", "Error")
|
28
|
-
return ("Missing required parameter: path", True)
|
29
|
-
|
30
|
-
# Store the original path for display purposes
|
31
|
-
original_path = path
|
32
|
-
|
33
|
-
# Normalize the path (converts to absolute path)
|
34
|
-
path = normalize_path(path)
|
35
|
-
file_path = pathlib.Path(path)
|
36
|
-
|
37
|
-
# Check if file exists
|
38
|
-
if not file_path.exists():
|
39
|
-
print_error(f"File {path} does not exist", "Error")
|
40
|
-
return (f"File {path} does not exist", True)
|
41
|
-
|
42
|
-
# Check in-memory history
|
43
|
-
if path not in _file_history or not _file_history[path]:
|
44
|
-
print_warning(f"Warning: No edit history for file {path}")
|
45
|
-
return (f"No edit history for file {path}", True)
|
46
|
-
|
47
|
-
try:
|
48
|
-
# Get the last content
|
49
|
-
last_content = _file_history[path].pop()
|
50
|
-
|
51
|
-
# Write the last content back to the file
|
52
|
-
with open(path, 'w', encoding='utf-8') as f:
|
53
|
-
f.write(last_content)
|
54
|
-
|
55
|
-
# Show relative path if it's not an absolute path in the original input
|
56
|
-
display_path = original_path if os.path.isabs(original_path) else os.path.relpath(file_path, get_config().workspace_dir)
|
57
|
-
success_msg = f"Successfully reverted the last edit made to the file {display_path}"
|
58
|
-
print_success(success_msg, "Success")
|
59
|
-
return (success_msg, False)
|
60
|
-
except Exception as e:
|
61
|
-
display_path = original_path if os.path.isabs(original_path) else os.path.relpath(file_path, get_config().workspace_dir)
|
62
|
-
error_msg = f"Error undoing edit to file {display_path}: {str(e)}"
|
63
|
-
print_error(error_msg, "Error")
|
64
|
-
return (error_msg, True)
|
@@ -1,165 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Handler for the view command in str_replace_editor.
|
3
|
-
"""
|
4
|
-
import os
|
5
|
-
import pathlib
|
6
|
-
from typing import Dict, Any, Tuple
|
7
|
-
from janito.config import get_config
|
8
|
-
from janito.tools.rich_console import print_info, print_error, console
|
9
|
-
from janito.tools.usage_tracker import get_tracker
|
10
|
-
from ..utils import normalize_path
|
11
|
-
|
12
|
-
def handle_view(args: Dict[str, Any]) -> Tuple[str, bool]:
|
13
|
-
"""
|
14
|
-
View the contents of a file or list directory contents.
|
15
|
-
|
16
|
-
Args:
|
17
|
-
args: Dictionary containing:
|
18
|
-
- path: Path to the file or directory to view
|
19
|
-
- view_range (optional): Array of two integers specifying start and end line numbers
|
20
|
-
|
21
|
-
Returns:
|
22
|
-
A tuple containing (content_or_message, is_error)
|
23
|
-
"""
|
24
|
-
path = args.get("path")
|
25
|
-
view_range = args.get("view_range")
|
26
|
-
|
27
|
-
# First normalize the path to check if it's a file or directory
|
28
|
-
normalized_path = normalize_path(path)
|
29
|
-
file_path = pathlib.Path(normalized_path)
|
30
|
-
|
31
|
-
if file_path.exists():
|
32
|
-
if file_path.is_dir():
|
33
|
-
print_info(f"Viewing directory: {path}: ", "Directory View")
|
34
|
-
else:
|
35
|
-
if view_range:
|
36
|
-
# Print with proper title for File View
|
37
|
-
print_info(f"Viewing file: {path}, from line {view_range[0]} to {view_range[1]}: ", "File View")
|
38
|
-
else:
|
39
|
-
# Print with proper title for File View
|
40
|
-
print_info(f"Viewing file: {path}, all lines: ", "File View")
|
41
|
-
else:
|
42
|
-
# If path doesn't exist yet, assume it's a file (will be validated later)
|
43
|
-
if view_range:
|
44
|
-
# Print with proper title for File View
|
45
|
-
print_info(f"Viewing file: {path}, from line {view_range[0]} to {view_range[1]}: ", "File View")
|
46
|
-
else:
|
47
|
-
# Print with proper title for File View
|
48
|
-
print_info(f"Viewing file: {path}, all lines: ", "File View")
|
49
|
-
|
50
|
-
if not path:
|
51
|
-
print_error("Missing required parameter: path", "Error")
|
52
|
-
return ("Missing required parameter: path", True)
|
53
|
-
|
54
|
-
path = normalize_path(path)
|
55
|
-
file_path = pathlib.Path(path)
|
56
|
-
|
57
|
-
if not file_path.exists():
|
58
|
-
print_error(f"❓ (not found)", "Error")
|
59
|
-
return (f"❓ (not found)", True)
|
60
|
-
|
61
|
-
# If the path is a directory, list non-hidden files and directories up to 2 levels deep
|
62
|
-
if file_path.is_dir():
|
63
|
-
try:
|
64
|
-
result = []
|
65
|
-
# Process the first level
|
66
|
-
for item in sorted(file_path.iterdir()):
|
67
|
-
if item.name.startswith('.'):
|
68
|
-
continue # Skip hidden files/directories
|
69
|
-
|
70
|
-
if item.is_dir():
|
71
|
-
result.append(f"{item.name}/")
|
72
|
-
# Process the second level
|
73
|
-
try:
|
74
|
-
for subitem in sorted(item.iterdir()):
|
75
|
-
if subitem.name.startswith('.'):
|
76
|
-
continue # Skip hidden files/directories
|
77
|
-
|
78
|
-
if subitem.is_dir():
|
79
|
-
result.append(f"{item.name}/{subitem.name}/")
|
80
|
-
else:
|
81
|
-
result.append(f"{item.name}/{subitem.name}")
|
82
|
-
except PermissionError:
|
83
|
-
# Skip directories we can't access
|
84
|
-
pass
|
85
|
-
else:
|
86
|
-
result.append(item.name)
|
87
|
-
|
88
|
-
if not result:
|
89
|
-
return (f"Directory {path} is empty or contains only hidden files", False)
|
90
|
-
|
91
|
-
# Track directory view
|
92
|
-
get_tracker().increment('file_views')
|
93
|
-
|
94
|
-
# Directory listings should not be truncated
|
95
|
-
file_dir_count = len(result)
|
96
|
-
output = "\n".join(result)
|
97
|
-
|
98
|
-
# Only print count if not in trust mode
|
99
|
-
if not get_config().trust_mode:
|
100
|
-
console.print(f"(", style="default", end="")
|
101
|
-
console.print(f"{file_dir_count}", style="cyan", end="")
|
102
|
-
console.print(" files and directories returned)")
|
103
|
-
return (output, False)
|
104
|
-
except Exception as e:
|
105
|
-
return (f"Error listing directory {path}: {str(e)}", True)
|
106
|
-
|
107
|
-
# If the path is a file, view its contents with cat -n style output
|
108
|
-
try:
|
109
|
-
with open(file_path, 'r', encoding='utf-8') as f:
|
110
|
-
content = f.readlines()
|
111
|
-
|
112
|
-
# If view_range is specified, return only the specified lines
|
113
|
-
if view_range:
|
114
|
-
start_line = max(1, view_range[0]) - 1 # Convert to 0-indexed
|
115
|
-
end_line = view_range[1] if view_range[1] != -1 else len(content)
|
116
|
-
end_line = min(end_line, len(content))
|
117
|
-
|
118
|
-
# Adjust content to only include the specified lines
|
119
|
-
content = content[start_line:end_line]
|
120
|
-
|
121
|
-
# Track partial file view
|
122
|
-
get_tracker().increment('partial_file_views')
|
123
|
-
else:
|
124
|
-
# Track full file view
|
125
|
-
get_tracker().increment('file_views')
|
126
|
-
|
127
|
-
# Add line numbers to each line (cat -n style)
|
128
|
-
numbered_content = []
|
129
|
-
start_idx = 1 if view_range is None else view_range[0]
|
130
|
-
for i, line in enumerate(content):
|
131
|
-
line_number = start_idx + i
|
132
|
-
# Ensure line ends with newline
|
133
|
-
if not line.endswith('\n'):
|
134
|
-
line += '\n'
|
135
|
-
# Format line number in cyan color using Rich's styling
|
136
|
-
# Use a simpler approach with f-strings and Rich's console
|
137
|
-
|
138
|
-
# Create a string with the line number that will be styled as cyan
|
139
|
-
line_num_str = f"{line_number:6d}\t{line}"
|
140
|
-
numbered_content.append(line_num_str)
|
141
|
-
|
142
|
-
# Check if we need to show a warning about large file
|
143
|
-
MAX_LINES = get_config().max_view_lines
|
144
|
-
if len(numbered_content) > MAX_LINES:
|
145
|
-
# Only print warning if not in trust mode
|
146
|
-
if not get_config().trust_mode:
|
147
|
-
console.print("(", style="default", end="")
|
148
|
-
console.print(f"{len(numbered_content)}", style="cyan", end="")
|
149
|
-
console.print(f" lines returned - warning: file exceeds recommended size of {MAX_LINES} lines)")
|
150
|
-
|
151
|
-
# Return the full content without truncation
|
152
|
-
content_to_print = "".join(numbered_content)
|
153
|
-
return (content_to_print, False)
|
154
|
-
|
155
|
-
content_to_print = "".join(numbered_content)
|
156
|
-
|
157
|
-
# Only print line count if not in trust mode
|
158
|
-
if not get_config().trust_mode:
|
159
|
-
console.print("(", style="default", end="")
|
160
|
-
console.print(f"{len(numbered_content)}", style="cyan", end="")
|
161
|
-
console.print(" lines returned)")
|
162
|
-
# Return the content as a string without any Rich objects
|
163
|
-
return (content_to_print, False)
|
164
|
-
except Exception as e:
|
165
|
-
return (f"Error viewing file {path}: {str(e)}", True)
|
@@ -1,33 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Utility functions for the str_replace_editor package.
|
3
|
-
"""
|
4
|
-
import os
|
5
|
-
from janito.config import get_config
|
6
|
-
|
7
|
-
def normalize_path(path: str) -> str:
|
8
|
-
"""
|
9
|
-
Normalizes a path relative to the workspace directory.
|
10
|
-
|
11
|
-
For internal operations, converts relative paths to absolute paths
|
12
|
-
based on the workspace directory.
|
13
|
-
|
14
|
-
Args:
|
15
|
-
path: The original path
|
16
|
-
|
17
|
-
Returns:
|
18
|
-
The normalized absolute path
|
19
|
-
"""
|
20
|
-
# If path is absolute, return it as is
|
21
|
-
if os.path.isabs(path):
|
22
|
-
return path
|
23
|
-
|
24
|
-
# Handle paths starting with ./ by removing the ./ prefix
|
25
|
-
if path.startswith('./'):
|
26
|
-
path = path[2:]
|
27
|
-
|
28
|
-
# Convert relative paths to absolute paths for internal operations
|
29
|
-
workspace_dir = get_config().workspace_dir
|
30
|
-
return os.path.normpath(os.path.join(workspace_dir, path))
|
31
|
-
|
32
|
-
# Store file history for undo operations (in-memory backup)
|
33
|
-
_file_history = {}
|
janito/tools/think.py
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Tool for thinking about something without obtaining new information or changing the database.
|
3
|
-
"""
|
4
|
-
from typing import Tuple
|
5
|
-
import logging
|
6
|
-
from janito.tools.usage_tracker import track_usage
|
7
|
-
from janito.tools.rich_console import print_info
|
8
|
-
|
9
|
-
# Set up logging
|
10
|
-
logger = logging.getLogger(__name__)
|
11
|
-
|
12
|
-
@track_usage('thoughts')
|
13
|
-
def think(
|
14
|
-
thought: str,
|
15
|
-
) -> Tuple[str, bool]:
|
16
|
-
"""
|
17
|
-
Use the tool to think about something. It will not obtain new information or change the database,
|
18
|
-
but just append the thought to the log. Use it when complex reasoning or some cache memory is needed.
|
19
|
-
|
20
|
-
Args:
|
21
|
-
thought: A thought to think about.
|
22
|
-
|
23
|
-
Returns:
|
24
|
-
A tuple containing (message, is_error)
|
25
|
-
"""
|
26
|
-
try:
|
27
|
-
# Log the thought
|
28
|
-
logger.info(f"Thought: {thought}")
|
29
|
-
|
30
|
-
# Print a confirmation message
|
31
|
-
print_info(f"Thought recorded: {thought[:50]}{'...' if len(thought) > 50 else ''}", "Thinking")
|
32
|
-
|
33
|
-
return (f"Thought recorded: {thought}", False)
|
34
|
-
except Exception as e:
|
35
|
-
error_msg = f"Error recording thought: {str(e)}"
|
36
|
-
logger.error(error_msg)
|
37
|
-
return (error_msg, True)
|
janito/tools/usage_tracker.py
DELETED
@@ -1,137 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Tool usage tracking module for Janito.
|
3
|
-
|
4
|
-
This module provides functionality to track tool usage statistics
|
5
|
-
such as files modified, created, deleted, and lines replaced.
|
6
|
-
"""
|
7
|
-
|
8
|
-
from functools import wraps
|
9
|
-
from typing import Dict, Any, Callable
|
10
|
-
import threading
|
11
|
-
|
12
|
-
# Global tracker instance
|
13
|
-
_tracker = None
|
14
|
-
_tracker_lock = threading.Lock()
|
15
|
-
|
16
|
-
class ToolUsageTracker:
|
17
|
-
"""Tracks usage statistics for Janito tools."""
|
18
|
-
|
19
|
-
def __init__(self):
|
20
|
-
self.reset()
|
21
|
-
|
22
|
-
def reset(self):
|
23
|
-
"""Reset all counters to zero."""
|
24
|
-
self.files_modified = 0
|
25
|
-
self.files_created = 0
|
26
|
-
self.files_deleted = 0
|
27
|
-
self.files_moved = 0
|
28
|
-
self.lines_replaced = 0
|
29
|
-
self.lines_delta = 0 # Track the net change in number of lines
|
30
|
-
self.web_requests = 0
|
31
|
-
self.bash_commands = 0
|
32
|
-
self.user_prompts = 0
|
33
|
-
self.search_operations = 0
|
34
|
-
self.file_views = 0
|
35
|
-
self.partial_file_views = 0
|
36
|
-
self.thoughts = 0 # Track the number of thoughts recorded
|
37
|
-
|
38
|
-
def increment(self, counter_name: str, value: int = 1):
|
39
|
-
"""Increment a specific counter by the given value."""
|
40
|
-
if hasattr(self, counter_name):
|
41
|
-
setattr(self, counter_name, getattr(self, counter_name) + value)
|
42
|
-
|
43
|
-
def get_stats(self) -> Dict[str, int]:
|
44
|
-
"""Get all non-zero statistics as a dictionary."""
|
45
|
-
stats = {}
|
46
|
-
for attr_name in dir(self):
|
47
|
-
if not attr_name.startswith('_') and not callable(getattr(self, attr_name)):
|
48
|
-
value = getattr(self, attr_name)
|
49
|
-
if value > 0:
|
50
|
-
# Convert attribute_name to "Attribute Name" format
|
51
|
-
display_name = ' '.join(word.capitalize() for word in attr_name.split('_'))
|
52
|
-
stats[display_name] = value
|
53
|
-
return stats
|
54
|
-
|
55
|
-
|
56
|
-
def get_tracker() -> ToolUsageTracker:
|
57
|
-
"""Get the global tracker instance."""
|
58
|
-
global _tracker
|
59
|
-
with _tracker_lock:
|
60
|
-
if _tracker is None:
|
61
|
-
_tracker = ToolUsageTracker()
|
62
|
-
return _tracker
|
63
|
-
|
64
|
-
|
65
|
-
def reset_tracker():
|
66
|
-
"""Reset the global tracker."""
|
67
|
-
get_tracker().reset()
|
68
|
-
|
69
|
-
|
70
|
-
def track_usage(counter_name: str, increment_value: int = 1):
|
71
|
-
"""
|
72
|
-
Decorator to track tool usage.
|
73
|
-
|
74
|
-
Args:
|
75
|
-
counter_name: The name of the counter to increment
|
76
|
-
increment_value: Value to increment the counter by (default: 1)
|
77
|
-
"""
|
78
|
-
def decorator(func):
|
79
|
-
@wraps(func)
|
80
|
-
def wrapper(*args, **kwargs):
|
81
|
-
result = func(*args, **kwargs)
|
82
|
-
# Only track successful operations
|
83
|
-
if isinstance(result, tuple) and len(result) >= 2:
|
84
|
-
message, is_error = result[0], result[1]
|
85
|
-
if not is_error:
|
86
|
-
get_tracker().increment(counter_name, increment_value)
|
87
|
-
return result
|
88
|
-
return wrapper
|
89
|
-
return decorator
|
90
|
-
|
91
|
-
|
92
|
-
def count_lines_in_string(old_str: str, new_str: str) -> tuple[int, int]:
|
93
|
-
"""
|
94
|
-
Count the number of lines that differ between old_str and new_str.
|
95
|
-
|
96
|
-
Args:
|
97
|
-
old_str: Original string
|
98
|
-
new_str: New string
|
99
|
-
|
100
|
-
Returns:
|
101
|
-
Tuple of (number of lines that differ, line delta)
|
102
|
-
"""
|
103
|
-
old_lines = old_str.splitlines()
|
104
|
-
new_lines = new_str.splitlines()
|
105
|
-
|
106
|
-
# Calculate the line delta (positive for added lines, negative for removed lines)
|
107
|
-
line_delta = len(new_lines) - len(old_lines)
|
108
|
-
|
109
|
-
# Simple approach: count the total number of lines changed
|
110
|
-
# For tracking purposes, we'll use the max to ensure we don't undercount
|
111
|
-
return max(len(old_lines), len(new_lines)), line_delta
|
112
|
-
|
113
|
-
|
114
|
-
def print_usage_stats():
|
115
|
-
"""Print the current usage statistics if any values are non-zero."""
|
116
|
-
stats = get_tracker().get_stats()
|
117
|
-
if stats:
|
118
|
-
from rich.console import Console
|
119
|
-
|
120
|
-
console = Console()
|
121
|
-
|
122
|
-
# Create a single-line summary of tool usage
|
123
|
-
summary_parts = []
|
124
|
-
for name, value in stats.items():
|
125
|
-
# Format lines delta with a sign
|
126
|
-
if name == "Lines Delta":
|
127
|
-
sign = "+" if value > 0 else "" if value == 0 else "-"
|
128
|
-
formatted_value = f"{sign}{abs(value)}"
|
129
|
-
summary_parts.append(f"{name}: {formatted_value}")
|
130
|
-
else:
|
131
|
-
summary_parts.append(f"{name}: {value}")
|
132
|
-
|
133
|
-
summary = " | ".join(summary_parts)
|
134
|
-
|
135
|
-
# Display with a rule similar to token usage
|
136
|
-
console.rule("[blue]Tool Usage[/blue]")
|
137
|
-
console.print(f"[blue]{summary}[/blue]", justify="center")
|