janito 1.6.0__py3-none-any.whl → 1.8.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 -1
- janito/agent/config.py +3 -3
- janito/agent/config_defaults.py +3 -2
- janito/agent/conversation.py +73 -27
- janito/agent/conversation_api.py +104 -4
- janito/agent/conversation_exceptions.py +6 -0
- janito/agent/conversation_tool_calls.py +17 -3
- janito/agent/event.py +24 -0
- janito/agent/event_dispatcher.py +24 -0
- janito/agent/event_handler_protocol.py +5 -0
- janito/agent/event_system.py +15 -0
- janito/agent/message_handler.py +4 -1
- janito/agent/message_handler_protocol.py +5 -0
- janito/agent/openai_client.py +5 -6
- janito/agent/openai_schema_generator.py +23 -4
- janito/agent/platform_discovery.py +90 -0
- janito/agent/profile_manager.py +34 -110
- janito/agent/queued_message_handler.py +22 -3
- janito/agent/rich_message_handler.py +3 -1
- janito/agent/templates/profiles/system_prompt_template_base.txt.j2 +14 -0
- janito/agent/templates/profiles/system_prompt_template_base_pt.txt.j2 +13 -0
- janito/agent/test_handler_protocols.py +47 -0
- janito/agent/tests/__init__.py +1 -0
- janito/agent/tool_base.py +1 -1
- janito/agent/tool_executor.py +109 -0
- janito/agent/tool_registry.py +3 -75
- janito/agent/tool_use_tracker.py +46 -0
- janito/agent/tools/__init__.py +11 -8
- janito/agent/tools/ask_user.py +26 -12
- janito/agent/tools/create_directory.py +50 -18
- janito/agent/tools/create_file.py +60 -29
- janito/agent/tools/dir_walk_utils.py +16 -0
- janito/agent/tools/fetch_url.py +10 -11
- janito/agent/tools/find_files.py +49 -40
- janito/agent/tools/get_lines.py +60 -25
- janito/agent/tools/memory.py +48 -0
- janito/agent/tools/move_file.py +72 -23
- janito/agent/tools/outline_file/__init__.py +85 -0
- janito/agent/tools/outline_file/formatting.py +20 -0
- janito/agent/tools/outline_file/markdown_outline.py +14 -0
- janito/agent/tools/outline_file/python_outline.py +71 -0
- janito/agent/tools/present_choices.py +62 -0
- janito/agent/tools/present_choices_test.py +18 -0
- janito/agent/tools/remove_directory.py +31 -26
- janito/agent/tools/remove_file.py +31 -13
- janito/agent/tools/replace_text_in_file.py +135 -36
- janito/agent/tools/run_bash_command.py +113 -97
- janito/agent/tools/run_powershell_command.py +169 -0
- janito/agent/tools/run_python_command.py +53 -29
- janito/agent/tools/search_outline.py +17 -0
- janito/agent/tools/search_text.py +208 -0
- janito/agent/tools/tools_utils.py +47 -4
- janito/agent/tools/utils.py +14 -15
- janito/agent/tools/validate_file_syntax.py +163 -0
- janito/cli/_print_config.py +1 -1
- janito/cli/arg_parser.py +36 -4
- janito/cli/config_commands.py +1 -1
- janito/cli/logging_setup.py +7 -2
- janito/cli/main.py +97 -3
- janito/cli/runner/__init__.py +0 -2
- janito/cli/runner/_termweb_log_utils.py +17 -0
- janito/cli/runner/cli_main.py +121 -89
- janito/cli/runner/config.py +6 -4
- janito/cli/termweb_starter.py +73 -0
- janito/cli_chat_shell/chat_loop.py +52 -13
- janito/cli_chat_shell/chat_state.py +1 -1
- janito/cli_chat_shell/chat_ui.py +2 -3
- janito/cli_chat_shell/commands/__init__.py +17 -6
- janito/cli_chat_shell/commands/{history_reset.py → history_start.py} +13 -5
- janito/cli_chat_shell/commands/lang.py +16 -0
- janito/cli_chat_shell/commands/prompt.py +42 -0
- janito/cli_chat_shell/commands/session_control.py +36 -1
- janito/cli_chat_shell/commands/sum.py +49 -0
- janito/cli_chat_shell/commands/termweb_log.py +86 -0
- janito/cli_chat_shell/commands/utility.py +5 -2
- janito/cli_chat_shell/commands/verbose.py +29 -0
- janito/cli_chat_shell/load_prompt.py +47 -8
- janito/cli_chat_shell/session_manager.py +9 -1
- janito/cli_chat_shell/shell_command_completer.py +20 -0
- janito/cli_chat_shell/ui.py +110 -93
- janito/i18n/__init__.py +35 -0
- janito/i18n/messages.py +23 -0
- janito/i18n/pt.py +46 -0
- janito/rich_utils.py +43 -43
- janito/termweb/app.py +95 -0
- janito/termweb/static/editor.html +238 -0
- janito/termweb/static/editor.html.bak +238 -0
- janito/termweb/static/explorer.html.bak +59 -0
- janito/termweb/static/favicon.ico +0 -0
- janito/termweb/static/favicon.ico.bak +0 -0
- janito/termweb/static/index.html +55 -0
- janito/termweb/static/index.html.bak +55 -0
- janito/termweb/static/index.html.bak.bak +175 -0
- janito/termweb/static/landing.html.bak +36 -0
- janito/termweb/static/termicon.svg +1 -0
- janito/termweb/static/termweb.css +235 -0
- janito/termweb/static/termweb.css.bak +286 -0
- janito/termweb/static/termweb.js +187 -0
- janito/termweb/static/termweb.js.bak +187 -0
- janito/termweb/static/termweb.js.bak.bak +157 -0
- janito/termweb/static/termweb_quickopen.js +135 -0
- janito/termweb/static/termweb_quickopen.js.bak +125 -0
- janito/web/app.py +10 -13
- {janito-1.6.0.dist-info → janito-1.8.0.dist-info}/METADATA +73 -32
- janito-1.8.0.dist-info/RECORD +127 -0
- {janito-1.6.0.dist-info → janito-1.8.0.dist-info}/WHEEL +1 -1
- janito/agent/tool_registry_core.py +0 -2
- janito/agent/tools/get_file_outline.py +0 -117
- janito/agent/tools/py_compile_file.py +0 -40
- janito/agent/tools/replace_file.py +0 -51
- janito/agent/tools/search_files.py +0 -71
- janito/cli/runner/scan.py +0 -44
- janito/cli_chat_shell/commands/system.py +0 -73
- janito-1.6.0.dist-info/RECORD +0 -81
- {janito-1.6.0.dist-info → janito-1.8.0.dist-info}/entry_points.txt +0 -0
- {janito-1.6.0.dist-info → janito-1.8.0.dist-info}/licenses/LICENSE +0 -0
- {janito-1.6.0.dist-info → janito-1.8.0.dist-info}/top_level.txt +0 -0
janito/agent/tools/move_file.py
CHANGED
@@ -3,23 +3,24 @@ import shutil
|
|
3
3
|
from janito.agent.tool_registry import register_tool
|
4
4
|
from janito.agent.tools.utils import expand_path, display_path
|
5
5
|
from janito.agent.tool_base import ToolBase
|
6
|
+
from janito.i18n import tr
|
6
7
|
|
7
8
|
|
8
9
|
@register_tool(name="move_file")
|
9
10
|
class MoveFileTool(ToolBase):
|
10
11
|
"""
|
11
|
-
Move a file from src_path to dest_path.
|
12
|
+
Move a file or directory from src_path to dest_path.
|
12
13
|
|
13
14
|
Args:
|
14
|
-
src_path (str): Source file path.
|
15
|
-
dest_path (str): Destination file path.
|
15
|
+
src_path (str): Source file or directory path.
|
16
|
+
dest_path (str): Destination file or directory path.
|
16
17
|
overwrite (bool, optional): Whether to overwrite if the destination exists. Defaults to False.
|
17
|
-
backup (bool, optional): If True, create a backup (.bak) of the destination before moving if it exists. Recommend using backup=True only in the first call to avoid redundant backups. Defaults to False.
|
18
|
+
backup (bool, optional): If True, create a backup (.bak for files, .bak.zip for directories) of the destination before moving if it exists. Recommend using backup=True only in the first call to avoid redundant backups. Defaults to False.
|
18
19
|
Returns:
|
19
20
|
str: Status message indicating the result.
|
20
21
|
"""
|
21
22
|
|
22
|
-
def
|
23
|
+
def run(
|
23
24
|
self,
|
24
25
|
src_path: str,
|
25
26
|
dest_path: str,
|
@@ -30,30 +31,78 @@ class MoveFileTool(ToolBase):
|
|
30
31
|
original_dest = dest_path
|
31
32
|
src = expand_path(src_path)
|
32
33
|
dest = expand_path(dest_path)
|
33
|
-
disp_src = display_path(original_src
|
34
|
-
disp_dest = display_path(original_dest
|
35
|
-
|
34
|
+
disp_src = display_path(original_src)
|
35
|
+
disp_dest = display_path(original_dest)
|
36
|
+
backup_path = None
|
36
37
|
if not os.path.exists(src):
|
37
|
-
self.report_error(
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
38
|
+
self.report_error(
|
39
|
+
tr("❌ Source '{disp_src}' does not exist.", disp_src=disp_src)
|
40
|
+
)
|
41
|
+
return tr("❌ Source '{disp_src}' does not exist.", disp_src=disp_src)
|
42
|
+
is_src_file = os.path.isfile(src)
|
43
|
+
is_src_dir = os.path.isdir(src)
|
44
|
+
if not (is_src_file or is_src_dir):
|
45
|
+
self.report_error(
|
46
|
+
tr(
|
47
|
+
"❌ Source path '{disp_src}' is neither a file nor a directory.",
|
48
|
+
disp_src=disp_src,
|
49
|
+
)
|
50
|
+
)
|
51
|
+
return tr(
|
52
|
+
"❌ Source path '{disp_src}' is neither a file nor a directory.",
|
53
|
+
disp_src=disp_src,
|
54
|
+
)
|
42
55
|
if os.path.exists(dest):
|
43
56
|
if not overwrite:
|
44
57
|
self.report_error(
|
45
|
-
|
58
|
+
tr(
|
59
|
+
"❗ Destination '{disp_dest}' exists and overwrite is False.",
|
60
|
+
disp_dest=disp_dest,
|
61
|
+
)
|
62
|
+
)
|
63
|
+
return tr(
|
64
|
+
"❗ Destination '{disp_dest}' already exists and overwrite is False.",
|
65
|
+
disp_dest=disp_dest,
|
46
66
|
)
|
47
|
-
return f"\u2757 Destination '{disp_dest}' already exists and overwrite is False."
|
48
|
-
if os.path.isdir(dest):
|
49
|
-
self.report_error(f"\u274c Destination '{disp_dest}' is a directory.")
|
50
|
-
return f"\u274c Destination '{disp_dest}' is a directory."
|
51
67
|
if backup:
|
52
|
-
|
68
|
+
if os.path.isfile(dest):
|
69
|
+
backup_path = dest + ".bak"
|
70
|
+
shutil.copy2(dest, backup_path)
|
71
|
+
elif os.path.isdir(dest):
|
72
|
+
backup_path = dest.rstrip("/\\") + ".bak.zip"
|
73
|
+
shutil.make_archive(dest.rstrip("/\\") + ".bak", "zip", dest)
|
74
|
+
try:
|
75
|
+
if os.path.isfile(dest):
|
76
|
+
os.remove(dest)
|
77
|
+
elif os.path.isdir(dest):
|
78
|
+
shutil.rmtree(dest)
|
79
|
+
except Exception as e:
|
80
|
+
self.report_error(
|
81
|
+
tr("❌ Error removing destination before move: {error}", error=e)
|
82
|
+
)
|
83
|
+
return tr("❌ Error removing destination before move: {error}", error=e)
|
53
84
|
try:
|
54
85
|
shutil.move(src, dest)
|
55
|
-
self.report_success(
|
56
|
-
|
86
|
+
self.report_success(
|
87
|
+
tr(
|
88
|
+
"✅ Moved from '{disp_src}' to '{disp_dest}'",
|
89
|
+
disp_src=disp_src,
|
90
|
+
disp_dest=disp_dest,
|
91
|
+
)
|
92
|
+
)
|
93
|
+
msg = tr(
|
94
|
+
"✅ Successfully moved from '{disp_src}' to '{disp_dest}'.",
|
95
|
+
disp_src=disp_src,
|
96
|
+
disp_dest=disp_dest,
|
97
|
+
)
|
98
|
+
if backup_path:
|
99
|
+
msg += tr(
|
100
|
+
" (backup at {backup_disp})",
|
101
|
+
backup_disp=display_path(
|
102
|
+
original_dest + (".bak" if is_src_file else ".bak.zip")
|
103
|
+
),
|
104
|
+
)
|
105
|
+
return msg
|
57
106
|
except Exception as e:
|
58
|
-
self.report_error(
|
59
|
-
return
|
107
|
+
self.report_error(tr("❌ Error moving: {error}", error=e))
|
108
|
+
return tr("❌ Error moving: {error}", error=e)
|
@@ -0,0 +1,85 @@
|
|
1
|
+
from janito.agent.tool_registry import register_tool
|
2
|
+
from .python_outline import parse_python_outline
|
3
|
+
from .markdown_outline import parse_markdown_outline
|
4
|
+
from .formatting import format_outline_table, format_markdown_outline_table
|
5
|
+
import os
|
6
|
+
from janito.agent.tool_base import ToolBase
|
7
|
+
from janito.agent.tools.tools_utils import display_path
|
8
|
+
from janito.i18n import tr
|
9
|
+
|
10
|
+
|
11
|
+
@register_tool(name="outline_file")
|
12
|
+
class GetFileOutlineTool(ToolBase):
|
13
|
+
"""
|
14
|
+
Get an outline of a file's structure. Supports Python and Markdown files.
|
15
|
+
|
16
|
+
Args:
|
17
|
+
file_path (str): Path to the file to outline.
|
18
|
+
"""
|
19
|
+
|
20
|
+
def run(self, file_path: str) -> str:
|
21
|
+
try:
|
22
|
+
self.report_info(
|
23
|
+
tr(
|
24
|
+
"📄 Outlining file: '{disp_path}' ...",
|
25
|
+
disp_path=display_path(file_path),
|
26
|
+
)
|
27
|
+
)
|
28
|
+
ext = os.path.splitext(file_path)[1].lower()
|
29
|
+
with open(file_path, "r", encoding="utf-8", errors="replace") as f:
|
30
|
+
lines = f.readlines()
|
31
|
+
if ext == ".py":
|
32
|
+
outline_items = parse_python_outline(lines)
|
33
|
+
outline_type = "python"
|
34
|
+
table = format_outline_table(outline_items)
|
35
|
+
self.report_success(
|
36
|
+
tr(
|
37
|
+
"✅ {count} items ({outline_type})",
|
38
|
+
count=len(outline_items),
|
39
|
+
outline_type=outline_type,
|
40
|
+
)
|
41
|
+
)
|
42
|
+
return (
|
43
|
+
tr(
|
44
|
+
"Outline: {count} items ({outline_type})\n",
|
45
|
+
count=len(outline_items),
|
46
|
+
outline_type=outline_type,
|
47
|
+
)
|
48
|
+
+ table
|
49
|
+
)
|
50
|
+
elif ext == ".md":
|
51
|
+
outline_items = parse_markdown_outline(lines)
|
52
|
+
outline_type = "markdown"
|
53
|
+
table = format_markdown_outline_table(outline_items)
|
54
|
+
self.report_success(
|
55
|
+
tr(
|
56
|
+
"✅ {count} items ({outline_type})",
|
57
|
+
count=len(outline_items),
|
58
|
+
outline_type=outline_type,
|
59
|
+
)
|
60
|
+
)
|
61
|
+
return (
|
62
|
+
tr(
|
63
|
+
"Outline: {count} items ({outline_type})\n",
|
64
|
+
count=len(outline_items),
|
65
|
+
outline_type=outline_type,
|
66
|
+
)
|
67
|
+
+ table
|
68
|
+
)
|
69
|
+
else:
|
70
|
+
outline_type = "default"
|
71
|
+
self.report_success(
|
72
|
+
tr(
|
73
|
+
"✅ {count} lines ({outline_type})",
|
74
|
+
count=len(lines),
|
75
|
+
outline_type=outline_type,
|
76
|
+
)
|
77
|
+
)
|
78
|
+
return tr(
|
79
|
+
"Outline: {count} lines ({outline_type})\nFile has {count} lines.",
|
80
|
+
count=len(lines),
|
81
|
+
outline_type=outline_type,
|
82
|
+
)
|
83
|
+
except Exception as e:
|
84
|
+
self.report_error(tr("❌ Error reading file: {error}", error=e))
|
85
|
+
return tr("Error reading file: {error}", error=e)
|
@@ -0,0 +1,20 @@
|
|
1
|
+
def format_outline_table(outline_items):
|
2
|
+
if not outline_items:
|
3
|
+
return "No classes, functions, or variables found."
|
4
|
+
header = "| Type | Name | Start | End | Parent |\n|---------|-------------|-------|-----|----------|"
|
5
|
+
rows = []
|
6
|
+
for item in outline_items:
|
7
|
+
rows.append(
|
8
|
+
f"| {item['type']:<7} | {item['name']:<11} | {item['start']:<5} | {item['end']:<3} | {item['parent']:<8} |"
|
9
|
+
)
|
10
|
+
return header + "\n" + "\n".join(rows)
|
11
|
+
|
12
|
+
|
13
|
+
def format_markdown_outline_table(outline_items):
|
14
|
+
if not outline_items:
|
15
|
+
return "No headers found."
|
16
|
+
header = "| Level | Header | Line |\n|-------|----------------------------------|------|"
|
17
|
+
rows = []
|
18
|
+
for item in outline_items:
|
19
|
+
rows.append(f"| {item['level']:<5} | {item['title']:<32} | {item['line']:<4} |")
|
20
|
+
return header + "\n" + "\n".join(rows)
|
@@ -0,0 +1,14 @@
|
|
1
|
+
import re
|
2
|
+
from typing import List
|
3
|
+
|
4
|
+
|
5
|
+
def parse_markdown_outline(lines: List[str]):
|
6
|
+
header_pat = re.compile(r"^(#+)\s+(.*)")
|
7
|
+
outline = []
|
8
|
+
for idx, line in enumerate(lines):
|
9
|
+
match = header_pat.match(line)
|
10
|
+
if match:
|
11
|
+
level = len(match.group(1))
|
12
|
+
title = match.group(2).strip()
|
13
|
+
outline.append({"level": level, "title": title, "line": idx + 1})
|
14
|
+
return outline
|
@@ -0,0 +1,71 @@
|
|
1
|
+
import re
|
2
|
+
from typing import List
|
3
|
+
|
4
|
+
|
5
|
+
def parse_python_outline(lines: List[str]):
|
6
|
+
class_pat = re.compile(r"^(\s*)class\s+(\w+)")
|
7
|
+
func_pat = re.compile(r"^(\s*)def\s+(\w+)")
|
8
|
+
assign_pat = re.compile(r"^(\s*)([A-Za-z_][A-Za-z0-9_]*)\s*=.*")
|
9
|
+
outline = []
|
10
|
+
stack = [] # (type, name, indent, start, parent)
|
11
|
+
for idx, line in enumerate(lines):
|
12
|
+
class_match = class_pat.match(line)
|
13
|
+
func_match = func_pat.match(line)
|
14
|
+
assign_match = assign_pat.match(line)
|
15
|
+
indent = len(line) - len(line.lstrip())
|
16
|
+
if class_match:
|
17
|
+
name = class_match.group(2)
|
18
|
+
parent = stack[-1][1] if stack and stack[-1][0] == "class" else ""
|
19
|
+
stack.append(("class", name, indent, idx + 1, parent))
|
20
|
+
elif func_match:
|
21
|
+
name = func_match.group(2)
|
22
|
+
parent = (
|
23
|
+
stack[-1][1]
|
24
|
+
if stack
|
25
|
+
and stack[-1][0] in ("class", "function")
|
26
|
+
and indent > stack[-1][2]
|
27
|
+
else ""
|
28
|
+
)
|
29
|
+
stack.append(("function", name, indent, idx + 1, parent))
|
30
|
+
elif assign_match and indent == 0:
|
31
|
+
var_name = assign_match.group(2)
|
32
|
+
var_type = "const" if var_name.isupper() else "var"
|
33
|
+
outline.append(
|
34
|
+
{
|
35
|
+
"type": var_type,
|
36
|
+
"name": var_name,
|
37
|
+
"start": idx + 1,
|
38
|
+
"end": idx + 1,
|
39
|
+
"parent": "",
|
40
|
+
}
|
41
|
+
)
|
42
|
+
while stack and indent < stack[-1][2]:
|
43
|
+
popped = stack.pop()
|
44
|
+
outline.append(
|
45
|
+
{
|
46
|
+
"type": (
|
47
|
+
popped[0]
|
48
|
+
if popped[0] != "function" or popped[3] == 1
|
49
|
+
else ("method" if popped[4] else "function")
|
50
|
+
),
|
51
|
+
"name": popped[1],
|
52
|
+
"start": popped[3],
|
53
|
+
"end": idx,
|
54
|
+
"parent": popped[4],
|
55
|
+
}
|
56
|
+
)
|
57
|
+
for popped in stack:
|
58
|
+
outline.append(
|
59
|
+
{
|
60
|
+
"type": (
|
61
|
+
popped[0]
|
62
|
+
if popped[0] != "function" or popped[3] == 1
|
63
|
+
else ("method" if popped[4] else "function")
|
64
|
+
),
|
65
|
+
"name": popped[1],
|
66
|
+
"start": popped[3],
|
67
|
+
"end": len(lines),
|
68
|
+
"parent": popped[4],
|
69
|
+
}
|
70
|
+
)
|
71
|
+
return outline
|
@@ -0,0 +1,62 @@
|
|
1
|
+
from typing import List
|
2
|
+
from janito.agent.tool_base import ToolBase
|
3
|
+
from janito.agent.tool_registry import register_tool
|
4
|
+
from janito.i18n import tr
|
5
|
+
import questionary
|
6
|
+
from questionary import Style
|
7
|
+
|
8
|
+
custom_style = Style(
|
9
|
+
[
|
10
|
+
("pointer", "fg:#ffffff bg:#1976d2 bold"),
|
11
|
+
("highlighted", "fg:#ffffff bg:#1565c0 bold"),
|
12
|
+
("answer", "fg:#1976d2 bold"),
|
13
|
+
("qmark", "fg:#1976d2 bold"),
|
14
|
+
]
|
15
|
+
)
|
16
|
+
HAND_EMOJI = "🖐️" # 🖐️
|
17
|
+
|
18
|
+
|
19
|
+
@register_tool(name="present_choices")
|
20
|
+
class PresentChoicesTool(ToolBase):
|
21
|
+
"""
|
22
|
+
Present a list of options to the user and return the selected option(s).
|
23
|
+
|
24
|
+
Args:
|
25
|
+
prompt (str): The prompt/question to display.
|
26
|
+
choices (List[str]): List of options to present. Use \n in option text for explicit line breaks if needed.
|
27
|
+
multi_select (bool): If True, allow multiple selections.
|
28
|
+
Returns:
|
29
|
+
str: The selected option(s) as a string, or a message if cancelled.
|
30
|
+
- For multi_select=True, returns each selection on a new line, each prefixed with '- '.
|
31
|
+
- For multi_select=False, returns the selected option as a string.
|
32
|
+
- If cancelled, returns 'No selection made.'
|
33
|
+
"""
|
34
|
+
|
35
|
+
def run(self, prompt: str, choices: List[str], multi_select: bool = False) -> str:
|
36
|
+
if not choices:
|
37
|
+
return tr("⚠️ No choices provided.")
|
38
|
+
self.report_info(
|
39
|
+
tr(
|
40
|
+
"ℹ️ Prompting user: {prompt} (multi_select={multi_select}) ...",
|
41
|
+
prompt=prompt,
|
42
|
+
multi_select=multi_select,
|
43
|
+
)
|
44
|
+
)
|
45
|
+
if multi_select:
|
46
|
+
result = questionary.checkbox(
|
47
|
+
prompt, choices=choices, style=custom_style, pointer=HAND_EMOJI
|
48
|
+
).ask()
|
49
|
+
if result is None:
|
50
|
+
return tr("No selection made.")
|
51
|
+
return (
|
52
|
+
"\n".join(f"- {item}" for item in result)
|
53
|
+
if isinstance(result, list)
|
54
|
+
else f"- {result}"
|
55
|
+
)
|
56
|
+
else:
|
57
|
+
result = questionary.select(
|
58
|
+
prompt, choices=choices, style=custom_style, pointer=HAND_EMOJI
|
59
|
+
).ask()
|
60
|
+
if result is None:
|
61
|
+
return tr("No selection made.")
|
62
|
+
return str(result)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
"""
|
2
|
+
Test for present_choices tool.
|
3
|
+
|
4
|
+
Note: This test is for manual/interactive verification only, as prompt_toolkit dialogs require user interaction.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from present_choices import present_choices
|
8
|
+
|
9
|
+
if __name__ == "__main__":
|
10
|
+
prompt = "Select your favorite fruits:"
|
11
|
+
choices = ["Apple", "Banana", "Cherry", "Date"]
|
12
|
+
print("Single-select test:")
|
13
|
+
selected = present_choices(prompt, choices, multi_select=False)
|
14
|
+
print(f"Selected: {selected}")
|
15
|
+
|
16
|
+
print("\nMulti-select test:")
|
17
|
+
selected_multi = present_choices(prompt, choices, multi_select=True)
|
18
|
+
print(f"Selected: {selected_multi}")
|
@@ -1,7 +1,7 @@
|
|
1
1
|
from janito.agent.tool_base import ToolBase
|
2
2
|
from janito.agent.tool_registry import register_tool
|
3
|
-
from janito.agent.tools.tools_utils import pluralize
|
4
|
-
|
3
|
+
from janito.agent.tools.tools_utils import pluralize, display_path
|
4
|
+
from janito.i18n import tr
|
5
5
|
import shutil
|
6
6
|
import os
|
7
7
|
import zipfile
|
@@ -10,41 +10,46 @@ import zipfile
|
|
10
10
|
@register_tool(name="remove_directory")
|
11
11
|
class RemoveDirectoryTool(ToolBase):
|
12
12
|
"""
|
13
|
-
Remove a directory.
|
13
|
+
Remove a directory.
|
14
14
|
|
15
15
|
Args:
|
16
|
-
|
17
|
-
recursive (bool, optional):
|
18
|
-
backup (bool, optional): If True, create a backup (.bak.zip) before removing. Recommend using backup=True only in the first call to avoid redundant backups. Defaults to False.
|
16
|
+
file_path (str): Path to the directory to remove.
|
17
|
+
recursive (bool, optional): If True, remove non-empty directories recursively (with backup). If False, only remove empty directories. Defaults to False.
|
19
18
|
Returns:
|
20
19
|
str: Status message indicating result. Example:
|
21
20
|
- "Directory removed: /path/to/dir"
|
22
21
|
- "Error removing directory: <error message>"
|
23
22
|
"""
|
24
23
|
|
25
|
-
def
|
26
|
-
|
27
|
-
) -> str:
|
24
|
+
def run(self, file_path: str, recursive: bool = False) -> str:
|
25
|
+
disp_path = display_path(file_path)
|
28
26
|
self.report_info(
|
29
|
-
|
27
|
+
tr("🗃️ Removing directory: {disp_path} ...", disp_path=disp_path)
|
30
28
|
)
|
29
|
+
backup_zip = None
|
31
30
|
try:
|
32
|
-
if backup and os.path.exists(directory) and os.path.isdir(directory):
|
33
|
-
backup_zip = directory.rstrip("/\\") + ".bak.zip"
|
34
|
-
with zipfile.ZipFile(backup_zip, "w", zipfile.ZIP_DEFLATED) as zipf:
|
35
|
-
for root, dirs, files in os.walk(directory):
|
36
|
-
for file in files:
|
37
|
-
abs_path = os.path.join(root, file)
|
38
|
-
rel_path = os.path.relpath(
|
39
|
-
abs_path, os.path.dirname(directory)
|
40
|
-
)
|
41
|
-
zipf.write(abs_path, rel_path)
|
42
31
|
if recursive:
|
43
|
-
|
32
|
+
# Backup before recursive removal
|
33
|
+
if os.path.exists(file_path) and os.path.isdir(file_path):
|
34
|
+
backup_zip = file_path.rstrip("/\\") + ".bak.zip"
|
35
|
+
with zipfile.ZipFile(backup_zip, "w", zipfile.ZIP_DEFLATED) as zipf:
|
36
|
+
for root, dirs, files in os.walk(file_path):
|
37
|
+
for file in files:
|
38
|
+
abs_path = os.path.join(root, file)
|
39
|
+
rel_path = os.path.relpath(
|
40
|
+
abs_path, os.path.dirname(file_path)
|
41
|
+
)
|
42
|
+
zipf.write(abs_path, rel_path)
|
43
|
+
shutil.rmtree(file_path)
|
44
44
|
else:
|
45
|
-
os.rmdir(
|
46
|
-
self.report_success(
|
47
|
-
|
45
|
+
os.rmdir(file_path)
|
46
|
+
self.report_success(
|
47
|
+
tr("✅ 1 {dir_word}", dir_word=pluralize("directory", 1))
|
48
|
+
)
|
49
|
+
msg = tr("Directory removed: {disp_path}", disp_path=disp_path)
|
50
|
+
if backup_zip:
|
51
|
+
msg += tr(" (backup at {backup_zip})", backup_zip=backup_zip)
|
52
|
+
return msg
|
48
53
|
except Exception as e:
|
49
|
-
self.report_error(
|
50
|
-
return
|
54
|
+
self.report_error(tr(" ❌ Error removing directory: {error}", error=e))
|
55
|
+
return tr("Error removing directory: {error}", error=e)
|
@@ -3,6 +3,7 @@ import shutil
|
|
3
3
|
from janito.agent.tool_registry import register_tool
|
4
4
|
from janito.agent.tools.utils import expand_path, display_path
|
5
5
|
from janito.agent.tool_base import ToolBase
|
6
|
+
from janito.i18n import tr
|
6
7
|
|
7
8
|
|
8
9
|
@register_tool(name="remove_file")
|
@@ -15,26 +16,43 @@ class RemoveFileTool(ToolBase):
|
|
15
16
|
backup (bool, optional): If True, create a backup (.bak) before removing. Recommend using backup=True only in the first call to avoid redundant backups. Defaults to False.
|
16
17
|
Returns:
|
17
18
|
str: Status message indicating the result. Example:
|
18
|
-
- "
|
19
|
-
- "
|
19
|
+
- "✅ Successfully removed the file at ..."
|
20
|
+
- "❗ Cannot remove file: ..."
|
20
21
|
"""
|
21
22
|
|
22
|
-
def
|
23
|
+
def run(self, file_path: str, backup: bool = False) -> str:
|
23
24
|
original_path = file_path
|
24
25
|
path = expand_path(file_path)
|
25
|
-
disp_path = display_path(original_path
|
26
|
+
disp_path = display_path(original_path)
|
27
|
+
backup_path = None
|
26
28
|
if not os.path.exists(path):
|
27
|
-
self.report_error(
|
28
|
-
|
29
|
+
self.report_error(
|
30
|
+
tr("❌ File '{disp_path}' does not exist.", disp_path=disp_path)
|
31
|
+
)
|
32
|
+
return tr("❌ File '{disp_path}' does not exist.", disp_path=disp_path)
|
29
33
|
if not os.path.isfile(path):
|
30
|
-
self.report_error(
|
31
|
-
|
34
|
+
self.report_error(
|
35
|
+
tr("❌ Path '{disp_path}' is not a file.", disp_path=disp_path)
|
36
|
+
)
|
37
|
+
return tr("❌ Path '{disp_path}' is not a file.", disp_path=disp_path)
|
32
38
|
try:
|
33
39
|
if backup:
|
34
|
-
|
40
|
+
backup_path = path + ".bak"
|
41
|
+
shutil.copy2(path, backup_path)
|
35
42
|
os.remove(path)
|
36
|
-
self.report_success(
|
37
|
-
|
43
|
+
self.report_success(
|
44
|
+
tr("✅ File removed: '{disp_path}'", disp_path=disp_path)
|
45
|
+
)
|
46
|
+
msg = tr(
|
47
|
+
"✅ Successfully removed the file at '{disp_path}'.",
|
48
|
+
disp_path=disp_path,
|
49
|
+
)
|
50
|
+
if backup_path:
|
51
|
+
msg += tr(
|
52
|
+
" (backup at {backup_disp})",
|
53
|
+
backup_disp=display_path(original_path + ".bak"),
|
54
|
+
)
|
55
|
+
return msg
|
38
56
|
except Exception as e:
|
39
|
-
self.report_error(
|
40
|
-
return
|
57
|
+
self.report_error(tr("❌ Error removing file: {error}", error=e))
|
58
|
+
return tr("❌ Error removing file: {error}", error=e)
|