janito 1.7.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 +1 -1
- janito/agent/config_defaults.py +2 -2
- janito/agent/conversation.py +70 -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 -8
- janito/agent/openai_schema_generator.py +23 -4
- janito/agent/profile_manager.py +15 -83
- janito/agent/queued_message_handler.py +22 -3
- janito/agent/rich_message_handler.py +66 -72
- 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 +8 -9
- janito/agent/tools/ask_user.py +19 -11
- janito/agent/tools/create_directory.py +43 -28
- 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 -32
- janito/agent/tools/get_lines.py +54 -18
- janito/agent/tools/memory.py +32 -52
- 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 +47 -50
- janito/agent/tools/run_powershell_command.py +52 -36
- janito/agent/tools/run_python_command.py +49 -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/arg_parser.py +36 -4
- janito/cli/logging_setup.py +7 -2
- janito/cli/main.py +96 -2
- janito/cli/runner/_termweb_log_utils.py +17 -0
- janito/cli/runner/cli_main.py +119 -77
- janito/cli/runner/config.py +2 -2
- janito/cli/termweb_starter.py +73 -0
- janito/cli_chat_shell/chat_loop.py +42 -7
- janito/cli_chat_shell/chat_state.py +1 -1
- janito/cli_chat_shell/chat_ui.py +0 -1
- janito/cli_chat_shell/commands/__init__.py +15 -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/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/session_manager.py +9 -1
- janito/cli_chat_shell/shell_command_completer.py +20 -0
- janito/cli_chat_shell/ui.py +110 -99
- 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 +4 -4
- {janito-1.7.0.dist-info → janito-1.8.0.dist-info}/METADATA +58 -25
- janito-1.8.0.dist-info/RECORD +127 -0
- {janito-1.7.0.dist-info → janito-1.8.0.dist-info}/WHEEL +1 -1
- janito/agent/templates/profiles/system_prompt_template_base.toml +0 -76
- janito/agent/templates/profiles/system_prompt_template_default.toml +0 -3
- janito/agent/templates/profiles/system_prompt_template_technical.toml +0 -13
- janito/agent/tests/test_prompt_toml.py +0 -61
- janito/agent/tool_registry_core.py +0 -2
- janito/agent/tools/get_file_outline.py +0 -146
- janito/agent/tools/py_compile_file.py +0 -40
- janito/agent/tools/replace_file.py +0 -51
- janito/agent/tools/search_files.py +0 -65
- janito/cli/runner/scan.py +0 -57
- janito/cli_chat_shell/commands/system.py +0 -73
- janito-1.7.0.dist-info/RECORD +0 -89
- {janito-1.7.0.dist-info → janito-1.8.0.dist-info}/entry_points.txt +0 -0
- {janito-1.7.0.dist-info → janito-1.8.0.dist-info}/licenses/LICENSE +0 -0
- {janito-1.7.0.dist-info → janito-1.8.0.dist-info}/top_level.txt +0 -0
janito/agent/tools/memory.py
CHANGED
@@ -1,68 +1,48 @@
|
|
1
|
-
"""
|
2
|
-
In-memory memory tools for storing and retrieving reusable information during an agent session.
|
3
|
-
These tools allow the agent to remember and recall arbitrary key-value pairs for the duration of the process.
|
4
|
-
"""
|
5
|
-
|
6
1
|
from janito.agent.tool_base import ToolBase
|
7
2
|
from janito.agent.tool_registry import register_tool
|
8
|
-
|
9
|
-
# Simple in-memory store (process-local, not persistent)
|
10
|
-
_memory_store = {}
|
3
|
+
from janito.i18n import tr
|
11
4
|
|
12
5
|
|
13
|
-
@register_tool(name="
|
14
|
-
class
|
6
|
+
@register_tool(name="memory")
|
7
|
+
class MemoryTool(ToolBase):
|
15
8
|
"""
|
16
|
-
|
17
|
-
|
18
|
-
Args:
|
19
|
-
key (str): The identifier for the value to store.
|
20
|
-
value (str): The value to store for later retrieval.
|
21
|
-
Returns:
|
22
|
-
str: Status message indicating success or error. Example:
|
23
|
-
- "✅ Stored value for key: 'foo'"
|
24
|
-
- "❗ Error storing value: ..."
|
9
|
+
Simple in-memory key-value store for demonstration purposes.
|
25
10
|
"""
|
26
11
|
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
12
|
+
def __init__(self):
|
13
|
+
super().__init__()
|
14
|
+
self.memory = {}
|
15
|
+
|
16
|
+
def run(self, action: str, key: str, value: str = None) -> str:
|
17
|
+
if action == "set":
|
18
|
+
self.report_info(tr("ℹ️ Storing value for key: '{key}' ...", key=key))
|
19
|
+
self.memory[key] = value
|
20
|
+
msg = tr("Value stored for key: '{key}'.", key=key)
|
32
21
|
self.report_success(msg)
|
33
22
|
return msg
|
34
|
-
|
35
|
-
|
36
|
-
self.
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
@register_tool(name="retrieve_memory")
|
41
|
-
class RetrieveMemoryTool(ToolBase):
|
42
|
-
"""
|
43
|
-
Retrieve a value previously stored using a key. Use this tool to recall information remembered earlier in the session.
|
44
|
-
|
45
|
-
Args:
|
46
|
-
key (str): The identifier for the value to retrieve.
|
47
|
-
Returns:
|
48
|
-
str: The stored value, or a warning message if not found. Example:
|
49
|
-
- "🔎 Retrieved value for key: 'foo': bar"
|
50
|
-
- "⚠️ No value found for key: 'notfound'"
|
51
|
-
"""
|
52
|
-
|
53
|
-
def call(self, key: str) -> str:
|
54
|
-
self.report_info(f"Retrieving value for key: '{key}'")
|
55
|
-
try:
|
56
|
-
if key in _memory_store:
|
57
|
-
value = _memory_store[key]
|
58
|
-
msg = f"🔎 Retrieved value for key: '{key}': {value}"
|
23
|
+
elif action == "get":
|
24
|
+
self.report_info(tr("ℹ️ Retrieving value for key: '{key}' ...", key=key))
|
25
|
+
if key in self.memory:
|
26
|
+
msg = tr(
|
27
|
+
"Value for key '{key}': {value}", key=key, value=self.memory[key]
|
28
|
+
)
|
59
29
|
self.report_success(msg)
|
60
30
|
return msg
|
61
31
|
else:
|
62
|
-
msg =
|
32
|
+
msg = tr("Key '{key}' not found.", key=key)
|
63
33
|
self.report_warning(msg)
|
64
34
|
return msg
|
65
|
-
|
66
|
-
|
35
|
+
elif action == "delete":
|
36
|
+
if key in self.memory:
|
37
|
+
del self.memory[key]
|
38
|
+
msg = tr("Key '{key}' deleted.", key=key)
|
39
|
+
self.report_success(msg)
|
40
|
+
return msg
|
41
|
+
else:
|
42
|
+
msg = tr("Key '{key}' not found.", key=key)
|
43
|
+
self.report_error(msg)
|
44
|
+
return msg
|
45
|
+
else:
|
46
|
+
msg = tr("Unknown action: {action}", action=action)
|
67
47
|
self.report_error(msg)
|
68
48
|
return msg
|
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)
|