janito 2.5.1__py3-none-any.whl → 2.6.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/agent/setup_agent.py +231 -223
- janito/agent/templates/profiles/system_prompt_template_software_developer.txt.j2 +39 -0
- janito/cli/chat_mode/bindings.py +1 -26
- janito/cli/chat_mode/session.py +282 -294
- janito/cli/chat_mode/session_profile_select.py +125 -55
- janito/cli/chat_mode/shell/commands/tools.py +51 -48
- janito/cli/chat_mode/toolbar.py +42 -68
- janito/cli/cli_commands/list_tools.py +41 -56
- janito/cli/cli_commands/show_system_prompt.py +70 -49
- janito/cli/core/runner.py +6 -1
- janito/cli/core/setters.py +43 -34
- janito/cli/main_cli.py +25 -1
- janito/cli/prompt_core.py +76 -69
- janito/cli/rich_terminal_reporter.py +22 -1
- janito/cli/single_shot_mode/handler.py +95 -94
- janito/drivers/driver_registry.py +27 -29
- janito/drivers/openai/driver.py +436 -494
- janito/llm/agent.py +54 -68
- janito/provider_registry.py +178 -178
- janito/providers/anthropic/model_info.py +41 -22
- janito/providers/anthropic/provider.py +80 -67
- janito/providers/provider_static_info.py +18 -17
- janito/tools/adapters/local/__init__.py +66 -65
- janito/tools/adapters/local/adapter.py +79 -18
- janito/tools/adapters/local/create_directory.py +9 -9
- janito/tools/adapters/local/create_file.py +12 -12
- janito/tools/adapters/local/delete_text_in_file.py +16 -16
- janito/tools/adapters/local/find_files.py +2 -2
- janito/tools/adapters/local/get_file_outline/core.py +5 -5
- janito/tools/adapters/local/get_file_outline/search_outline.py +4 -4
- janito/tools/adapters/local/open_html_in_browser.py +15 -15
- janito/tools/adapters/local/python_file_run.py +4 -4
- janito/tools/adapters/local/read_files.py +40 -0
- janito/tools/adapters/local/remove_directory.py +5 -5
- janito/tools/adapters/local/remove_file.py +4 -4
- janito/tools/adapters/local/replace_text_in_file.py +21 -21
- janito/tools/adapters/local/run_bash_command.py +1 -1
- janito/tools/adapters/local/search_text/pattern_utils.py +2 -2
- janito/tools/adapters/local/search_text/traverse_directory.py +10 -10
- janito/tools/adapters/local/validate_file_syntax/core.py +7 -7
- janito/tools/adapters/local/validate_file_syntax/css_validator.py +2 -2
- janito/tools/adapters/local/validate_file_syntax/html_validator.py +7 -7
- janito/tools/adapters/local/validate_file_syntax/js_validator.py +2 -2
- janito/tools/adapters/local/validate_file_syntax/json_validator.py +2 -2
- janito/tools/adapters/local/validate_file_syntax/markdown_validator.py +2 -2
- janito/tools/adapters/local/validate_file_syntax/ps1_validator.py +2 -2
- janito/tools/adapters/local/validate_file_syntax/python_validator.py +2 -2
- janito/tools/adapters/local/validate_file_syntax/xml_validator.py +2 -2
- janito/tools/adapters/local/validate_file_syntax/yaml_validator.py +2 -2
- janito/tools/adapters/local/view_file.py +12 -12
- janito/tools/path_security.py +204 -0
- janito/tools/tool_use_tracker.py +12 -12
- janito/tools/tools_adapter.py +66 -34
- {janito-2.5.1.dist-info → janito-2.6.0.dist-info}/METADATA +412 -412
- {janito-2.5.1.dist-info → janito-2.6.0.dist-info}/RECORD +59 -58
- janito/drivers/anthropic/driver.py +0 -113
- janito/tools/adapters/local/get_file_outline/python_outline_v2.py +0 -156
- {janito-2.5.1.dist-info → janito-2.6.0.dist-info}/WHEEL +0 -0
- {janito-2.5.1.dist-info → janito-2.6.0.dist-info}/entry_points.txt +0 -0
- {janito-2.5.1.dist-info → janito-2.6.0.dist-info}/licenses/LICENSE +0 -0
- {janito-2.5.1.dist-info → janito-2.6.0.dist-info}/top_level.txt +0 -0
@@ -1,67 +1,80 @@
|
|
1
|
-
from janito.llm.provider import LLMProvider
|
2
|
-
from janito.llm.model import LLMModelInfo
|
3
|
-
from janito.llm.auth import LLMAuthManager
|
4
|
-
from janito.llm.driver_config import LLMDriverConfig
|
5
|
-
from janito.tools import get_local_tools_adapter
|
6
|
-
from janito.providers.registry import LLMProviderRegistry
|
7
|
-
|
8
|
-
from .model_info import MODEL_SPECS
|
9
|
-
|
10
|
-
from janito.
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
self.
|
32
|
-
self.
|
33
|
-
self.
|
34
|
-
|
35
|
-
if not self.
|
36
|
-
self.
|
37
|
-
|
38
|
-
|
39
|
-
self.fill_missing_device_info(self.
|
40
|
-
self._driver =
|
41
|
-
|
42
|
-
@property
|
43
|
-
def driver(self):
|
44
|
-
if not self.available:
|
45
|
-
raise ImportError(
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
driver =
|
64
|
-
return
|
65
|
-
|
66
|
-
|
67
|
-
|
1
|
+
from janito.llm.provider import LLMProvider
|
2
|
+
from janito.llm.model import LLMModelInfo
|
3
|
+
from janito.llm.auth import LLMAuthManager
|
4
|
+
from janito.llm.driver_config import LLMDriverConfig
|
5
|
+
from janito.tools import get_local_tools_adapter
|
6
|
+
from janito.providers.registry import LLMProviderRegistry
|
7
|
+
|
8
|
+
from .model_info import MODEL_SPECS
|
9
|
+
|
10
|
+
from janito.llm.provider import LLMProvider
|
11
|
+
from janito.llm.model import LLMModelInfo
|
12
|
+
from janito.llm.auth import LLMAuthManager
|
13
|
+
from janito.llm.driver_config import LLMDriverConfig
|
14
|
+
from janito.tools import get_local_tools_adapter
|
15
|
+
from janito.providers.registry import LLMProviderRegistry
|
16
|
+
from .model_info import MODEL_SPECS
|
17
|
+
from janito.drivers.openai.driver import OpenAIModelDriver
|
18
|
+
|
19
|
+
class AnthropicProvider(LLMProvider):
|
20
|
+
name = "anthropic"
|
21
|
+
maintainer = "Needs maintainer"
|
22
|
+
MODEL_SPECS = MODEL_SPECS
|
23
|
+
DEFAULT_MODEL = "claude-3-7-sonnet-20250219"
|
24
|
+
|
25
|
+
def __init__(
|
26
|
+
self, auth_manager: LLMAuthManager = None, config: LLMDriverConfig = None
|
27
|
+
):
|
28
|
+
self._tools_adapter = get_local_tools_adapter()
|
29
|
+
self.auth_manager = auth_manager or LLMAuthManager()
|
30
|
+
self._api_key = self.auth_manager.get_credentials(type(self).name)
|
31
|
+
self._tools_adapter = get_local_tools_adapter()
|
32
|
+
self._driver_config = config or LLMDriverConfig(model=None)
|
33
|
+
if not getattr(self._driver_config, 'model', None):
|
34
|
+
self._driver_config.model = self.DEFAULT_MODEL
|
35
|
+
if not self._driver_config.api_key:
|
36
|
+
self._driver_config.api_key = self._api_key
|
37
|
+
# Set the Anthropic OpenAI-compatible API endpoint
|
38
|
+
self._driver_config.base_url = "https://api.anthropic.com/v1/"
|
39
|
+
self.fill_missing_device_info(self._driver_config)
|
40
|
+
self._driver = None # to be provided by factory/agent
|
41
|
+
|
42
|
+
@property
|
43
|
+
def driver(self) -> OpenAIModelDriver:
|
44
|
+
if not self.available:
|
45
|
+
raise ImportError(f"AnthropicProvider unavailable: {self.unavailable_reason}")
|
46
|
+
return self._driver
|
47
|
+
|
48
|
+
@property
|
49
|
+
def available(self):
|
50
|
+
return OpenAIModelDriver.available
|
51
|
+
|
52
|
+
@property
|
53
|
+
def unavailable_reason(self):
|
54
|
+
return OpenAIModelDriver.unavailable_reason
|
55
|
+
|
56
|
+
def create_driver(self):
|
57
|
+
"""
|
58
|
+
Creates and returns a new OpenAIModelDriver instance configured for Anthropic API.
|
59
|
+
"""
|
60
|
+
driver = OpenAIModelDriver(
|
61
|
+
tools_adapter=self._tools_adapter, provider_name=self.name
|
62
|
+
)
|
63
|
+
driver.config = self._driver_config
|
64
|
+
return driver
|
65
|
+
|
66
|
+
@property
|
67
|
+
def model_name(self):
|
68
|
+
return self._driver_config.model
|
69
|
+
|
70
|
+
@property
|
71
|
+
def driver_config(self):
|
72
|
+
"""Public, read-only access to the provider's LLMDriverConfig object."""
|
73
|
+
return self._driver_config
|
74
|
+
|
75
|
+
def execute_tool(self, tool_name: str, event_bus, *args, **kwargs):
|
76
|
+
self._tools_adapter.event_bus = event_bus
|
77
|
+
return self._tools_adapter.execute_by_name(tool_name, *args, **kwargs)
|
78
|
+
|
79
|
+
|
80
|
+
LLMProviderRegistry.register(AnthropicProvider.name, AnthropicProvider)
|
@@ -1,17 +1,18 @@
|
|
1
|
-
# Provider static metadata registry for listing purposes (name, maintainer, and future fields)
|
2
|
-
STATIC_PROVIDER_METADATA = {
|
3
|
-
"openai": {
|
4
|
-
},
|
5
|
-
"google": {
|
6
|
-
"maintainer": "João Pinto <lamego.pinto@gmail.com>",
|
7
|
-
},
|
8
|
-
"azure_openai": {
|
9
|
-
"maintainer": "João Pinto <lamego.pinto@gmail.com>",
|
10
|
-
},
|
11
|
-
"anthropic": {
|
12
|
-
"maintainer": "
|
13
|
-
},
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
}
|
1
|
+
# Provider static metadata registry for listing purposes (name, maintainer, and future fields)
|
2
|
+
STATIC_PROVIDER_METADATA = {
|
3
|
+
"openai": {
|
4
|
+
},
|
5
|
+
"google": {
|
6
|
+
"maintainer": "João Pinto <lamego.pinto@gmail.com>",
|
7
|
+
},
|
8
|
+
"azure_openai": {
|
9
|
+
"maintainer": "João Pinto <lamego.pinto@gmail.com>",
|
10
|
+
},
|
11
|
+
"anthropic": {
|
12
|
+
"maintainer": "Alberto Minetti <alberto.minetti@gmail.com>",
|
13
|
+
},
|
14
|
+
|
15
|
+
"deepseek": {
|
16
|
+
"maintainer": "João Pinto <lamego.pinto@gmail.com>",
|
17
|
+
},
|
18
|
+
}
|
@@ -1,65 +1,66 @@
|
|
1
|
-
from .adapter import LocalToolsAdapter
|
2
|
-
|
3
|
-
from .ask_user import AskUserTool
|
4
|
-
from .copy_file import CopyFileTool
|
5
|
-
from .create_directory import CreateDirectoryTool
|
6
|
-
from .create_file import CreateFileTool
|
7
|
-
from .fetch_url import FetchUrlTool
|
8
|
-
from .find_files import FindFilesTool
|
9
|
-
from .view_file import ViewFileTool
|
10
|
-
from .
|
11
|
-
from .
|
12
|
-
from .
|
13
|
-
from .
|
14
|
-
from .
|
15
|
-
from .
|
16
|
-
from .
|
17
|
-
from .
|
18
|
-
from .
|
19
|
-
from .
|
20
|
-
from .
|
21
|
-
from .
|
22
|
-
from .get_file_outline.
|
23
|
-
from .
|
24
|
-
from .
|
25
|
-
|
26
|
-
|
27
|
-
from janito.tools.tool_base import ToolPermissions
|
28
|
-
import os
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
return LocalToolsAdapter(workdir=workdir or os.getcwd())
|
36
|
-
|
37
|
-
# Register tools
|
38
|
-
for tool_class in [
|
39
|
-
AskUserTool,
|
40
|
-
CopyFileTool,
|
41
|
-
CreateDirectoryTool,
|
42
|
-
CreateFileTool,
|
43
|
-
FetchUrlTool,
|
44
|
-
FindFilesTool,
|
45
|
-
ViewFileTool,
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
1
|
+
from .adapter import LocalToolsAdapter
|
2
|
+
|
3
|
+
from .ask_user import AskUserTool
|
4
|
+
from .copy_file import CopyFileTool
|
5
|
+
from .create_directory import CreateDirectoryTool
|
6
|
+
from .create_file import CreateFileTool
|
7
|
+
from .fetch_url import FetchUrlTool
|
8
|
+
from .find_files import FindFilesTool
|
9
|
+
from .view_file import ViewFileTool
|
10
|
+
from .read_files import ReadFilesTool
|
11
|
+
from .move_file import MoveFileTool
|
12
|
+
from .open_url import OpenUrlTool
|
13
|
+
from .open_html_in_browser import OpenHtmlInBrowserTool
|
14
|
+
from .python_code_run import PythonCodeRunTool
|
15
|
+
from .python_command_run import PythonCommandRunTool
|
16
|
+
from .python_file_run import PythonFileRunTool
|
17
|
+
from .remove_directory import RemoveDirectoryTool
|
18
|
+
from .remove_file import RemoveFileTool
|
19
|
+
from .replace_text_in_file import ReplaceTextInFileTool
|
20
|
+
from .run_bash_command import RunBashCommandTool
|
21
|
+
from .run_powershell_command import RunPowershellCommandTool
|
22
|
+
from .get_file_outline.core import GetFileOutlineTool
|
23
|
+
from .get_file_outline.search_outline import SearchOutlineTool
|
24
|
+
from .search_text.core import SearchTextTool
|
25
|
+
from .validate_file_syntax.core import ValidateFileSyntaxTool
|
26
|
+
|
27
|
+
from janito.tools.tool_base import ToolPermissions
|
28
|
+
import os
|
29
|
+
from janito.tools.permissions import get_global_allowed_permissions
|
30
|
+
|
31
|
+
# Singleton tools adapter with all standard tools registered
|
32
|
+
local_tools_adapter = LocalToolsAdapter(workdir=os.getcwd())
|
33
|
+
|
34
|
+
def get_local_tools_adapter(workdir=None):
|
35
|
+
return LocalToolsAdapter(workdir=workdir or os.getcwd())
|
36
|
+
|
37
|
+
# Register tools
|
38
|
+
for tool_class in [
|
39
|
+
AskUserTool,
|
40
|
+
CopyFileTool,
|
41
|
+
CreateDirectoryTool,
|
42
|
+
CreateFileTool,
|
43
|
+
FetchUrlTool,
|
44
|
+
FindFilesTool,
|
45
|
+
ViewFileTool,
|
46
|
+
ReadFilesTool,
|
47
|
+
MoveFileTool,
|
48
|
+
OpenUrlTool,
|
49
|
+
OpenHtmlInBrowserTool,
|
50
|
+
PythonCodeRunTool,
|
51
|
+
PythonCommandRunTool,
|
52
|
+
PythonFileRunTool,
|
53
|
+
RemoveDirectoryTool,
|
54
|
+
RemoveFileTool,
|
55
|
+
ReplaceTextInFileTool,
|
56
|
+
RunBashCommandTool,
|
57
|
+
RunPowershellCommandTool,
|
58
|
+
GetFileOutlineTool,
|
59
|
+
SearchOutlineTool,
|
60
|
+
SearchTextTool,
|
61
|
+
ValidateFileSyntaxTool,
|
62
|
+
]:
|
63
|
+
local_tools_adapter.register_tool(tool_class)
|
64
|
+
|
65
|
+
# DEBUG: Print registered tools at startup
|
66
|
+
|
@@ -18,16 +18,51 @@ class LocalToolsAdapter(ToolsAdapter):
|
|
18
18
|
"""
|
19
19
|
|
20
20
|
def __init__(self, tools=None, event_bus=None, workdir=None):
|
21
|
+
"""Create a new LocalToolsAdapter.
|
22
|
+
|
23
|
+
Parameters
|
24
|
+
----------
|
25
|
+
tools : list, optional
|
26
|
+
An optional iterable with tool *classes* (not instances) that should
|
27
|
+
be registered immediately.
|
28
|
+
event_bus : janito.event_bus.bus.EventBus, optional
|
29
|
+
The event bus to which tool-related events will be published. When
|
30
|
+
*None* (default) the **global** :pydata:`janito.event_bus.bus.event_bus`
|
31
|
+
singleton is used so that CLI components such as the
|
32
|
+
:class:`janito.cli.rich_terminal_reporter.RichTerminalReporter` will
|
33
|
+
receive security violation or execution events automatically.
|
34
|
+
workdir : str | pathlib.Path, optional
|
35
|
+
Base directory that path-security checks will allow. Defaults to
|
36
|
+
the current working directory at the time of instantiation.
|
37
|
+
"""
|
38
|
+
# Fall back to the global event bus so that ReportEvents emitted from
|
39
|
+
# the tools adapter (for example path-security violations) are visible
|
40
|
+
# to UI components even if the caller did not supply a custom bus.
|
41
|
+
if event_bus is None:
|
42
|
+
from janito.event_bus.bus import event_bus as global_event_bus
|
43
|
+
event_bus = global_event_bus
|
44
|
+
|
21
45
|
super().__init__(tools=tools, event_bus=event_bus)
|
46
|
+
|
47
|
+
# Internal registry structure: { tool_name: {"class": cls, "instance": obj, "function": obj.run} }
|
22
48
|
self._tools: Dict[str, Dict[str, Any]] = {}
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
49
|
+
|
50
|
+
import os
|
51
|
+
self.workdir = workdir or os.getcwd()
|
52
|
+
# Ensure *some* workdir is set – fallback to CWD.
|
53
|
+
if not self.workdir:
|
54
|
+
self.workdir = os.getcwd()
|
55
|
+
# Normalise by changing the actual process working directory for
|
56
|
+
# consistency with many file-system tools.
|
57
|
+
os.chdir(self.workdir)
|
58
|
+
|
27
59
|
if tools:
|
28
60
|
for tool in tools:
|
29
61
|
self.register_tool(tool)
|
30
62
|
|
63
|
+
# ---------------------------------------------------------------------
|
64
|
+
# Registration helpers
|
65
|
+
# ---------------------------------------------------------------------
|
31
66
|
def register_tool(self, tool_class: Type):
|
32
67
|
instance = tool_class()
|
33
68
|
if not hasattr(instance, "run") or not callable(instance.run):
|
@@ -54,28 +89,43 @@ class LocalToolsAdapter(ToolsAdapter):
|
|
54
89
|
def disable_tool(self, name: str):
|
55
90
|
self.unregister_tool(name)
|
56
91
|
|
92
|
+
# ------------------------------------------------------------------
|
93
|
+
# Lookup helpers used by ToolsAdapterBase
|
94
|
+
# ------------------------------------------------------------------
|
57
95
|
def get_tool(self, name: str):
|
58
96
|
return self._tools[name]["instance"] if name in self._tools else None
|
59
97
|
|
60
98
|
def list_tools(self):
|
61
|
-
return [
|
99
|
+
return [
|
100
|
+
name
|
101
|
+
for name, entry in self._tools.items()
|
102
|
+
if self.is_tool_allowed(entry["instance"])
|
103
|
+
]
|
62
104
|
|
63
105
|
def get_tool_classes(self):
|
64
|
-
return [
|
106
|
+
return [
|
107
|
+
entry["class"]
|
108
|
+
for entry in self._tools.values()
|
109
|
+
if self.is_tool_allowed(entry["instance"])
|
110
|
+
]
|
65
111
|
|
66
112
|
def get_tools(self):
|
67
|
-
return [
|
68
|
-
|
69
|
-
|
113
|
+
return [
|
114
|
+
entry["instance"]
|
115
|
+
for entry in self._tools.values()
|
116
|
+
if self.is_tool_allowed(entry["instance"])
|
117
|
+
]
|
118
|
+
|
119
|
+
# ------------------------------------------------------------------
|
120
|
+
# Convenience methods
|
121
|
+
# ------------------------------------------------------------------
|
70
122
|
def add_tool(self, tool):
|
71
|
-
|
123
|
+
"""Register an *instance* (instead of a class) as a tool."""
|
72
124
|
if not hasattr(tool, "run") or not callable(tool.run):
|
73
125
|
raise TypeError(f"Tool '{tool}' must implement a callable 'run' method.")
|
74
126
|
tool_name = getattr(tool, "tool_name", None)
|
75
127
|
if not tool_name or not isinstance(tool_name, str):
|
76
|
-
raise ValueError(
|
77
|
-
f"Tool '{tool}' must provide a 'tool_name' (str) attribute."
|
78
|
-
)
|
128
|
+
raise ValueError(f"Tool '{tool}' must provide a 'tool_name' (str) attribute.")
|
79
129
|
if tool_name in self._tools:
|
80
130
|
raise ValueError(f"Tool '{tool_name}' is already registered.")
|
81
131
|
self._tools[tool_name] = {
|
@@ -85,17 +135,28 @@ class LocalToolsAdapter(ToolsAdapter):
|
|
85
135
|
}
|
86
136
|
|
87
137
|
|
88
|
-
#
|
89
|
-
|
138
|
+
# -------------------------------------------------------------------------
|
139
|
+
# Decorator helper for quick registration of local tools
|
140
|
+
# -------------------------------------------------------------------------
|
90
141
|
|
91
142
|
def register_local_tool(tool=None):
|
143
|
+
"""Class decorator that registers the tool on the *singleton* adapter.
|
144
|
+
|
145
|
+
Example
|
146
|
+
-------
|
147
|
+
>>> @register_local_tool
|
148
|
+
... class MyTool(BaseTool):
|
149
|
+
... ...
|
150
|
+
"""
|
151
|
+
|
92
152
|
def decorator(cls):
|
93
|
-
|
94
|
-
|
153
|
+
# Register the tool on a *fresh* adapter instance to avoid circular
|
154
|
+
# import issues during package initialisation. This keeps behaviour
|
155
|
+
# identical to the original implementation while still allowing
|
156
|
+
# immediate use via the singleton in janito.tools.adapters.local.
|
95
157
|
LocalToolsAdapter().register_tool(cls)
|
96
158
|
return cls
|
97
159
|
|
98
160
|
if tool is None:
|
99
161
|
return decorator
|
100
162
|
return decorator(tool)
|
101
|
-
|
@@ -10,9 +10,9 @@ import os
|
|
10
10
|
@register_local_tool
|
11
11
|
class CreateDirectoryTool(ToolBase):
|
12
12
|
"""
|
13
|
-
Create a new directory at the specified
|
13
|
+
Create a new directory at the specified path.
|
14
14
|
Args:
|
15
|
-
|
15
|
+
path (str): Path for the new directory.
|
16
16
|
Returns:
|
17
17
|
str: Status message indicating the result. Example:
|
18
18
|
- "5c5 Successfully created the directory at ..."
|
@@ -21,17 +21,17 @@ class CreateDirectoryTool(ToolBase):
|
|
21
21
|
permissions = ToolPermissions(write=True)
|
22
22
|
tool_name = "create_directory"
|
23
23
|
|
24
|
-
def run(self,
|
25
|
-
#
|
26
|
-
# Using
|
27
|
-
disp_path = display_path(
|
24
|
+
def run(self, path: str) -> str:
|
25
|
+
# path = expand_path(path)
|
26
|
+
# Using path as is
|
27
|
+
disp_path = display_path(path)
|
28
28
|
self.report_action(
|
29
29
|
tr("📁 Create directory '{disp_path}' ...", disp_path=disp_path),
|
30
30
|
ReportAction.CREATE,
|
31
31
|
)
|
32
32
|
try:
|
33
|
-
if os.path.exists(
|
34
|
-
if not os.path.isdir(
|
33
|
+
if os.path.exists(path):
|
34
|
+
if not os.path.isdir(path):
|
35
35
|
self.report_error(
|
36
36
|
tr(
|
37
37
|
"❌ Path '{disp_path}' exists and is not a directory.",
|
@@ -52,7 +52,7 @@ class CreateDirectoryTool(ToolBase):
|
|
52
52
|
"❗ Cannot create directory: '{disp_path}' already exists.",
|
53
53
|
disp_path=disp_path,
|
54
54
|
)
|
55
|
-
os.makedirs(
|
55
|
+
os.makedirs(path, exist_ok=True)
|
56
56
|
self.report_success(tr("✅ Directory created"))
|
57
57
|
return tr(
|
58
58
|
"✅ Successfully created the directory at '{disp_path}'.",
|
@@ -16,7 +16,7 @@ class CreateFileTool(ToolBase):
|
|
16
16
|
Create a new file with the given content.
|
17
17
|
|
18
18
|
Args:
|
19
|
-
|
19
|
+
path (str): Path to the file to create.
|
20
20
|
content (str): Content to write to the file.
|
21
21
|
overwrite (bool, optional): Overwrite existing file if True. Default: False. Recommended only after reading the file to be overwritten.
|
22
22
|
Returns:
|
@@ -28,13 +28,13 @@ class CreateFileTool(ToolBase):
|
|
28
28
|
permissions = ToolPermissions(write=True)
|
29
29
|
tool_name = "create_file"
|
30
30
|
|
31
|
-
def run(self,
|
32
|
-
|
33
|
-
disp_path = display_path(
|
34
|
-
|
35
|
-
if os.path.exists(
|
31
|
+
def run(self, path: str, content: str, overwrite: bool = False) -> str:
|
32
|
+
expanded_path = path # Using path as is
|
33
|
+
disp_path = display_path(expanded_path)
|
34
|
+
path = expanded_path
|
35
|
+
if os.path.exists(path) and not overwrite:
|
36
36
|
try:
|
37
|
-
with open(
|
37
|
+
with open(path, "r", encoding="utf-8", errors="replace") as f:
|
38
38
|
existing_content = f.read()
|
39
39
|
except Exception as e:
|
40
40
|
existing_content = f"[Error reading file: {e}]"
|
@@ -44,14 +44,14 @@ class CreateFileTool(ToolBase):
|
|
44
44
|
existing_content=existing_content,
|
45
45
|
)
|
46
46
|
# Determine if we are overwriting an existing file
|
47
|
-
is_overwrite = os.path.exists(
|
47
|
+
is_overwrite = os.path.exists(path) and overwrite
|
48
48
|
if is_overwrite:
|
49
49
|
# Overwrite branch: log only overwrite warning (no create message)
|
50
50
|
self.report_action(
|
51
|
-
tr("⚠️
|
51
|
+
tr("⚠️ Overwriting file '{disp_path}'", disp_path=disp_path),
|
52
52
|
ReportAction.CREATE,
|
53
53
|
)
|
54
|
-
dir_name = os.path.dirname(
|
54
|
+
dir_name = os.path.dirname(path)
|
55
55
|
if dir_name:
|
56
56
|
os.makedirs(dir_name, exist_ok=True)
|
57
57
|
if not is_overwrite:
|
@@ -60,14 +60,14 @@ class CreateFileTool(ToolBase):
|
|
60
60
|
tr("📝 Create file '{disp_path}' ...", disp_path=disp_path),
|
61
61
|
ReportAction.CREATE,
|
62
62
|
)
|
63
|
-
with open(
|
63
|
+
with open(path, "w", encoding="utf-8", errors="replace") as f:
|
64
64
|
f.write(content)
|
65
65
|
new_lines = content.count("\n") + 1 if content else 0
|
66
66
|
self.report_success(
|
67
67
|
tr("✅ {new_lines} lines", new_lines=new_lines), ReportAction.CREATE
|
68
68
|
)
|
69
69
|
# Perform syntax validation and append result
|
70
|
-
validation_result = validate_file_syntax(
|
70
|
+
validation_result = validate_file_syntax(path)
|
71
71
|
if is_overwrite:
|
72
72
|
# Overwrite branch: return minimal overwrite info to user
|
73
73
|
return (
|
@@ -12,7 +12,7 @@ class DeleteTextInFileTool(ToolBase):
|
|
12
12
|
Delete all occurrences of text between start_marker and end_marker (inclusive) in a file, using exact string markers.
|
13
13
|
|
14
14
|
Args:
|
15
|
-
|
15
|
+
path (str): Path to the file to modify.
|
16
16
|
start_marker (str): The starting delimiter string.
|
17
17
|
end_marker (str): The ending delimiter string.
|
18
18
|
|
@@ -24,14 +24,14 @@ class DeleteTextInFileTool(ToolBase):
|
|
24
24
|
|
25
25
|
def run(
|
26
26
|
self,
|
27
|
-
|
27
|
+
path: str,
|
28
28
|
start_marker: str,
|
29
29
|
end_marker: str,
|
30
30
|
backup: bool = False,
|
31
31
|
) -> str:
|
32
32
|
from janito.tools.tool_utils import display_path
|
33
33
|
|
34
|
-
disp_path = display_path(
|
34
|
+
disp_path = display_path(path)
|
35
35
|
info_msg = tr(
|
36
36
|
"📝 Delete text in {disp_path} between markers: '{start_marker}' ... '{end_marker}'",
|
37
37
|
disp_path=disp_path,
|
@@ -40,7 +40,7 @@ class DeleteTextInFileTool(ToolBase):
|
|
40
40
|
)
|
41
41
|
self.report_action(info_msg, ReportAction.CREATE)
|
42
42
|
try:
|
43
|
-
content = self._read_file_content(
|
43
|
+
content = self._read_file_content(path)
|
44
44
|
occurrences, match_lines = self._find_marker_blocks(
|
45
45
|
content, start_marker, end_marker
|
46
46
|
)
|
@@ -49,27 +49,27 @@ class DeleteTextInFileTool(ToolBase):
|
|
49
49
|
tr(" ℹ️ No blocks found between markers."), ReportAction.CREATE
|
50
50
|
)
|
51
51
|
return tr(
|
52
|
-
"No blocks found between markers in {
|
53
|
-
|
52
|
+
"No blocks found between markers in {path}.",
|
53
|
+
path=path,
|
54
54
|
)
|
55
55
|
|
56
56
|
new_content, deleted_blocks = self._delete_blocks(
|
57
57
|
content, start_marker, end_marker
|
58
58
|
)
|
59
|
-
self._write_file_content(
|
60
|
-
validation_result = validate_file_syntax(
|
59
|
+
self._write_file_content(path, new_content)
|
60
|
+
validation_result = validate_file_syntax(path)
|
61
61
|
self._report_success(match_lines)
|
62
62
|
return tr(
|
63
|
-
"Deleted {count} block(s) between markers in {
|
63
|
+
"Deleted {count} block(s) between markers in {path}. ",
|
64
64
|
count=deleted_blocks,
|
65
|
-
|
65
|
+
path=path
|
66
66
|
) + (f"\n{validation_result}" if validation_result else "")
|
67
67
|
except Exception as e:
|
68
68
|
self.report_error(tr(" ❌ Error: {error}", error=e), ReportAction.REPLACE)
|
69
69
|
return tr("Error deleting text: {error}", error=e)
|
70
70
|
|
71
|
-
def _read_file_content(self,
|
72
|
-
with open(
|
71
|
+
def _read_file_content(self, path):
|
72
|
+
with open(path, "r", encoding="utf-8", errors="replace") as f:
|
73
73
|
return f.read()
|
74
74
|
|
75
75
|
def _find_marker_blocks(self, content, start_marker, end_marker):
|
@@ -110,11 +110,11 @@ class DeleteTextInFileTool(ToolBase):
|
|
110
110
|
count += 1
|
111
111
|
return new_content, count
|
112
112
|
|
113
|
-
def _backup_file(self,
|
114
|
-
shutil.copy2(
|
113
|
+
def _backup_file(self, path, backup_path):
|
114
|
+
shutil.copy2(path, backup_path)
|
115
115
|
|
116
|
-
def _write_file_content(self,
|
117
|
-
with open(
|
116
|
+
def _write_file_content(self, path, content):
|
117
|
+
with open(path, "w", encoding="utf-8", errors="replace") as f:
|
118
118
|
f.write(content)
|
119
119
|
|
120
120
|
def _report_success(self, match_lines):
|