janito 2.27.1__py3-none-any.whl → 2.28.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. janito/README.md +9 -9
  2. janito/agent/setup_agent.py +29 -16
  3. janito/cli/chat_mode/script_runner.py +1 -1
  4. janito/cli/chat_mode/session.py +17 -5
  5. janito/cli/chat_mode/session_profile_select.py +8 -2
  6. janito/cli/chat_mode/shell/commands/execute.py +4 -2
  7. janito/cli/chat_mode/shell/commands/help.py +2 -0
  8. janito/cli/chat_mode/shell/commands/privileges.py +6 -2
  9. janito/cli/chat_mode/shell/commands/provider.py +7 -4
  10. janito/cli/chat_mode/shell/commands/read.py +4 -2
  11. janito/cli/chat_mode/shell/commands/security/__init__.py +1 -1
  12. janito/cli/chat_mode/shell/commands/security/allowed_sites.py +16 -13
  13. janito/cli/chat_mode/shell/commands/security_command.py +14 -10
  14. janito/cli/chat_mode/shell/commands/tools.py +4 -2
  15. janito/cli/chat_mode/shell/commands/unrestricted.py +17 -12
  16. janito/cli/chat_mode/shell/commands/write.py +4 -2
  17. janito/cli/chat_mode/toolbar.py +4 -4
  18. janito/cli/cli_commands/enable_disable_plugin.py +48 -25
  19. janito/cli/cli_commands/list_models.py +2 -2
  20. janito/cli/cli_commands/list_plugins.py +18 -18
  21. janito/cli/cli_commands/list_profiles.py +6 -6
  22. janito/cli/cli_commands/list_providers.py +1 -1
  23. janito/cli/cli_commands/model_utils.py +45 -20
  24. janito/cli/cli_commands/ping_providers.py +10 -10
  25. janito/cli/cli_commands/set_api_key.py +5 -3
  26. janito/cli/cli_commands/show_config.py +13 -7
  27. janito/cli/cli_commands/show_system_prompt.py +13 -6
  28. janito/cli/core/getters.py +1 -0
  29. janito/cli/core/model_guesser.py +18 -15
  30. janito/cli/core/runner.py +15 -7
  31. janito/cli/core/setters.py +9 -6
  32. janito/cli/main_cli.py +15 -12
  33. janito/cli/prompt_core.py +2 -0
  34. janito/cli/prompt_setup.py +4 -4
  35. janito/cli/single_shot_mode/handler.py +2 -0
  36. janito/config_manager.py +2 -0
  37. janito/docs/GETTING_STARTED.md +9 -9
  38. janito/drivers/cerebras/__init__.py +1 -1
  39. janito/exceptions.py +6 -4
  40. janito/plugins/__init__.py +2 -2
  41. janito/plugins/base.py +48 -40
  42. janito/plugins/builtin.py +13 -9
  43. janito/plugins/config.py +16 -19
  44. janito/plugins/discovery.py +73 -66
  45. janito/plugins/manager.py +62 -60
  46. janito/provider_registry.py +10 -10
  47. janito/providers/__init__.py +1 -1
  48. janito/providers/alibaba/model_info.py +3 -5
  49. janito/providers/alibaba/provider.py +3 -1
  50. janito/providers/cerebras/__init__.py +1 -1
  51. janito/providers/cerebras/model_info.py +12 -27
  52. janito/providers/cerebras/provider.py +11 -9
  53. janito/providers/mistral/__init__.py +1 -1
  54. janito/providers/mistral/model_info.py +1 -1
  55. janito/providers/mistral/provider.py +1 -1
  56. janito/providers/moonshot/__init__.py +1 -0
  57. janito/providers/{moonshotai → moonshot}/model_info.py +3 -3
  58. janito/providers/{moonshotai → moonshot}/provider.py +8 -8
  59. janito/providers/openai/provider.py +3 -1
  60. janito/report_events.py +0 -1
  61. janito/tools/adapters/local/create_file.py +1 -1
  62. janito/tools/adapters/local/fetch_url.py +45 -29
  63. janito/tools/adapters/local/python_command_run.py +2 -1
  64. janito/tools/adapters/local/python_file_run.py +1 -0
  65. janito/tools/adapters/local/run_powershell_command.py +1 -1
  66. janito/tools/adapters/local/search_text/core.py +1 -1
  67. janito/tools/adapters/local/validate_file_syntax/jinja2_validator.py +14 -11
  68. janito/tools/base.py +4 -3
  69. janito/tools/loop_protection.py +24 -22
  70. janito/tools/path_utils.py +7 -7
  71. janito/tools/tool_base.py +0 -2
  72. janito/tools/tools_adapter.py +15 -5
  73. janito/tools/url_whitelist.py +27 -26
  74. {janito-2.27.1.dist-info → janito-2.28.0.dist-info}/METADATA +1 -1
  75. {janito-2.27.1.dist-info → janito-2.28.0.dist-info}/RECORD +79 -79
  76. janito/providers/moonshotai/__init__.py +0 -1
  77. {janito-2.27.1.dist-info → janito-2.28.0.dist-info}/WHEEL +0 -0
  78. {janito-2.27.1.dist-info → janito-2.28.0.dist-info}/entry_points.txt +0 -0
  79. {janito-2.27.1.dist-info → janito-2.28.0.dist-info}/licenses/LICENSE +0 -0
  80. {janito-2.27.1.dist-info → janito-2.28.0.dist-info}/top_level.txt +0 -0
@@ -12,7 +12,7 @@ MODEL_SPECS = {
12
12
  max_cot=8192,
13
13
  ),
14
14
  "qwen-plus": LLMModelInfo(
15
- name="qwen-plus",
15
+ name="qwen-plus",
16
16
  context=131072,
17
17
  max_response=8192,
18
18
  category="Alibaba Qwen Plus Model (OpenAI-compatible)",
@@ -23,7 +23,7 @@ MODEL_SPECS = {
23
23
  ),
24
24
  "qwen-max": LLMModelInfo(
25
25
  name="qwen-max",
26
- context=32768,
26
+ context=32768,
27
27
  max_response=8192,
28
28
  category="Alibaba Qwen Max Model (OpenAI-compatible)",
29
29
  driver="OpenAIModelDriver",
@@ -31,7 +31,6 @@ MODEL_SPECS = {
31
31
  thinking=False,
32
32
  max_cot=8192,
33
33
  ),
34
-
35
34
  "qwen3-coder-plus": LLMModelInfo(
36
35
  name="qwen3-coder-plus",
37
36
  context=1048576,
@@ -52,7 +51,6 @@ MODEL_SPECS = {
52
51
  thinking=False,
53
52
  max_cot=65536,
54
53
  ),
55
-
56
54
  # Qwen3 1M context models (July 2025 update)
57
55
  "qwen3-235b-a22b-thinking-2507": LLMModelInfo(
58
56
  name="qwen3-235b-a22b-thinking-2507",
@@ -94,4 +92,4 @@ MODEL_SPECS = {
94
92
  thinking=False,
95
93
  max_cot=32768,
96
94
  ),
97
- }
95
+ }
@@ -17,7 +17,9 @@ class AlibabaProvider(LLMProvider):
17
17
  NAME = "alibaba"
18
18
  MAINTAINER = "João Pinto <janito@ikignosis.org>"
19
19
  MODEL_SPECS = MODEL_SPECS
20
- DEFAULT_MODEL = "qwen3-235b-a22b-instruct-2507" # 129k context, general-purpose model
20
+ DEFAULT_MODEL = (
21
+ "qwen3-235b-a22b-instruct-2507" # 129k context, general-purpose model
22
+ )
21
23
 
22
24
  def __init__(
23
25
  self, auth_manager: LLMAuthManager = None, config: LLMDriverConfig = None
@@ -1 +1 @@
1
- # Cerebras provider package
1
+ # Cerebras provider package
@@ -11,11 +11,8 @@ MODEL_SPECS = {
11
11
  driver="CerebrasModelDriver",
12
12
  other={
13
13
  "description": "Qwen 3 32B model for general instruction following",
14
- "pricing": {
15
- "input_per_1k_tokens": 0.0002,
16
- "output_per_1k_tokens": 0.0006
17
- }
18
- }
14
+ "pricing": {"input_per_1k_tokens": 0.0002, "output_per_1k_tokens": 0.0006},
15
+ },
19
16
  ),
20
17
  "qwen-3-235b-a22b-instruct-2507": LLMModelInfo(
21
18
  name="qwen-3-235b-a22b-instruct-2507",
@@ -25,11 +22,8 @@ MODEL_SPECS = {
25
22
  driver="CerebrasModelDriver",
26
23
  other={
27
24
  "description": "Qwen 3 235B A22B instruction-tuned model (preview)",
28
- "pricing": {
29
- "input_per_1k_tokens": 0.001,
30
- "output_per_1k_tokens": 0.003
31
- }
32
- }
25
+ "pricing": {"input_per_1k_tokens": 0.001, "output_per_1k_tokens": 0.003},
26
+ },
33
27
  ),
34
28
  "qwen-3-235b-a22b-thinking-2507": LLMModelInfo(
35
29
  name="qwen-3-235b-a22b-thinking-2507",
@@ -39,11 +33,8 @@ MODEL_SPECS = {
39
33
  driver="CerebrasModelDriver",
40
34
  other={
41
35
  "description": "Qwen 3 235B A22B thinking model for reasoning tasks (preview)",
42
- "pricing": {
43
- "input_per_1k_tokens": 0.001,
44
- "output_per_1k_tokens": 0.003
45
- }
46
- }
36
+ "pricing": {"input_per_1k_tokens": 0.001, "output_per_1k_tokens": 0.003},
37
+ },
47
38
  ),
48
39
  "qwen-3-coder-480b": LLMModelInfo(
49
40
  name="qwen-3-coder-480b",
@@ -53,11 +44,8 @@ MODEL_SPECS = {
53
44
  driver="CerebrasModelDriver",
54
45
  other={
55
46
  "description": "Qwen 3 Coder 480B model for programming tasks (preview)",
56
- "pricing": {
57
- "input_per_1k_tokens": 0.002,
58
- "output_per_1k_tokens": 0.006
59
- }
60
- }
47
+ "pricing": {"input_per_1k_tokens": 0.002, "output_per_1k_tokens": 0.006},
48
+ },
61
49
  ),
62
50
  "gpt-oss-120b": LLMModelInfo(
63
51
  name="gpt-oss-120b",
@@ -67,10 +55,7 @@ MODEL_SPECS = {
67
55
  driver="CerebrasModelDriver",
68
56
  other={
69
57
  "description": "GPT-OSS 120B open-source model (preview)",
70
- "pricing": {
71
- "input_per_1k_tokens": 0.0008,
72
- "output_per_1k_tokens": 0.0024
73
- }
74
- }
75
- )
76
- }
58
+ "pricing": {"input_per_1k_tokens": 0.0008, "output_per_1k_tokens": 0.0024},
59
+ },
60
+ ),
61
+ }
@@ -12,14 +12,16 @@ from .model_info import MODEL_SPECS
12
12
 
13
13
  class CerebrasProvider(LLMProvider):
14
14
  """Cerebras Inference API provider."""
15
-
15
+
16
16
  name = "cerebras"
17
17
  NAME = "cerebras"
18
18
  DEFAULT_MODEL = "qwen-3-coder-480b"
19
19
  MAINTAINER = "João Pinto <janito@ikignosis.org>"
20
20
  MODEL_SPECS = MODEL_SPECS
21
21
 
22
- def __init__(self, auth_manager: LLMAuthManager = None, config: LLMDriverConfig = None):
22
+ def __init__(
23
+ self, auth_manager: LLMAuthManager = None, config: LLMDriverConfig = None
24
+ ):
23
25
  """Initialize Cerebras provider with optional configuration."""
24
26
  super().__init__()
25
27
  self._tools_adapter = get_local_tools_adapter()
@@ -32,10 +34,10 @@ class CerebrasProvider(LLMProvider):
32
34
  self._initialize_config(auth_manager, config)
33
35
  self._setup_model_config()
34
36
  self.fill_missing_device_info(self._driver_config)
35
-
37
+
36
38
  if not self.available:
37
39
  return
38
-
40
+
39
41
  self._initialize_config(None, None)
40
42
  self._driver_config.base_url = "https://api.cerebras.ai/v1"
41
43
 
@@ -72,11 +74,11 @@ class CerebrasProvider(LLMProvider):
72
74
  # Set context length
73
75
  if hasattr(model_spec, "context") and model_spec.context:
74
76
  self._driver_config.context_length = model_spec.context
75
-
77
+
76
78
  # Set max tokens based on model spec
77
79
  if hasattr(model_spec, "max_response") and model_spec.max_response:
78
80
  self._driver_config.max_tokens = model_spec.max_response
79
-
81
+
80
82
  # Set max completion tokens if thinking is supported
81
83
  if getattr(model_spec, "thinking_supported", False):
82
84
  max_cot = getattr(model_spec, "max_cot", None)
@@ -130,10 +132,10 @@ class CerebrasProvider(LLMProvider):
130
132
  name: model_info.to_dict()
131
133
  for name, model_info in self.MODEL_SPECS.items()
132
134
  }
133
-
135
+
134
136
  if model_name in self.MODEL_SPECS:
135
137
  return self.MODEL_SPECS[model_name].to_dict()
136
-
138
+
137
139
  return None
138
140
 
139
141
  def execute_tool(self, tool_name: str, event_bus, *args, **kwargs):
@@ -142,4 +144,4 @@ class CerebrasProvider(LLMProvider):
142
144
 
143
145
 
144
146
  # Register the provider
145
- LLMProviderRegistry.register(CerebrasProvider.name, CerebrasProvider)
147
+ LLMProviderRegistry.register(CerebrasProvider.name, CerebrasProvider)
@@ -1 +1 @@
1
- # Codestral provider module
1
+ # Codestral provider module
@@ -78,4 +78,4 @@ MODEL_SPECS = {
78
78
  open="mistral",
79
79
  driver="OpenAIModelDriver",
80
80
  ),
81
- }
81
+ }
@@ -121,4 +121,4 @@ class MistralProvider(LLMProvider):
121
121
  return self._tools_adapter.execute_by_name(tool_name, *args, **kwargs)
122
122
 
123
123
 
124
- LLMProviderRegistry.register(MistralProvider.NAME, MistralProvider)
124
+ LLMProviderRegistry.register(MistralProvider.NAME, MistralProvider)
@@ -0,0 +1 @@
1
+ # Moonshot provider package
@@ -1,6 +1,6 @@
1
1
  from janito.llm.model import LLMModelInfo
2
2
 
3
- MOONSHOTAI_MODEL_SPECS = {
3
+ MOONSHOT_MODEL_SPECS = {
4
4
  "kimi-k2-0711-preview": LLMModelInfo(
5
5
  name="kimi-k2-0711-preview",
6
6
  context=128000,
@@ -9,7 +9,7 @@ MOONSHOTAI_MODEL_SPECS = {
9
9
  max_response=4096,
10
10
  thinking_supported=False,
11
11
  default_temp=0.2,
12
- open="moonshotai",
12
+ open="moonshot",
13
13
  driver="OpenAIModelDriver",
14
14
  ),
15
15
  "kimi-k2-turbo-preview": LLMModelInfo(
@@ -20,7 +20,7 @@ MOONSHOTAI_MODEL_SPECS = {
20
20
  max_response=4096,
21
21
  thinking_supported=False,
22
22
  default_temp=0.2,
23
- open="moonshotai",
23
+ open="moonshot",
24
24
  driver="OpenAIModelDriver",
25
25
  ),
26
26
  }
@@ -4,14 +4,14 @@ from janito.llm.driver_config import LLMDriverConfig
4
4
  from janito.drivers.openai.driver import OpenAIModelDriver
5
5
  from janito.tools import get_local_tools_adapter
6
6
  from janito.providers.registry import LLMProviderRegistry
7
- from .model_info import MOONSHOTAI_MODEL_SPECS
7
+ from .model_info import MOONSHOT_MODEL_SPECS
8
8
 
9
9
 
10
- class MoonshotAIProvider(LLMProvider):
11
- name = "moonshotai"
12
- NAME = "moonshotai"
10
+ class MoonshotProvider(LLMProvider):
11
+ name = "moonshot"
12
+ NAME = "moonshot"
13
13
  MAINTAINER = "João Pinto <janito@ikignosis.org>"
14
- MODEL_SPECS = MOONSHOTAI_MODEL_SPECS
14
+ MODEL_SPECS = MOONSHOT_MODEL_SPECS
15
15
  DEFAULT_MODEL = "kimi-k2-turbo-preview"
16
16
 
17
17
  def __init__(
@@ -34,7 +34,7 @@ class MoonshotAIProvider(LLMProvider):
34
34
  if not self._api_key:
35
35
  from janito.llm.auth_utils import handle_missing_api_key
36
36
 
37
- handle_missing_api_key(self.name, "MOONSHOTAI_API_KEY")
37
+ handle_missing_api_key(self.name, "MOONSHOT_API_KEY")
38
38
 
39
39
  self._driver_config = config or LLMDriverConfig(model=None)
40
40
  if not self._driver_config.model:
@@ -69,7 +69,7 @@ class MoonshotAIProvider(LLMProvider):
69
69
  def driver(self) -> OpenAIModelDriver:
70
70
  if not self.available:
71
71
  raise ImportError(
72
- f"MoonshotAIProvider unavailable: {self.unavailable_reason}"
72
+ f"MoonshotProvider unavailable: {self.unavailable_reason}"
73
73
  )
74
74
  return self._driver
75
75
 
@@ -101,4 +101,4 @@ class MoonshotAIProvider(LLMProvider):
101
101
  return self._tools_adapter.execute_by_name(tool_name, *args, **kwargs)
102
102
 
103
103
 
104
- LLMProviderRegistry.register(MoonshotAIProvider.NAME, MoonshotAIProvider)
104
+ LLMProviderRegistry.register(MoonshotProvider.NAME, MoonshotProvider)
@@ -17,7 +17,9 @@ class OpenAIProvider(LLMProvider):
17
17
  NAME = "openai"
18
18
  MAINTAINER = "João Pinto <janito@ikignosis.org>"
19
19
  MODEL_SPECS = MODEL_SPECS
20
- DEFAULT_MODEL = "gpt-5" # Options: gpt-4.1, gpt-4o, o3-mini, o4-mini, gpt-5, gpt-5-nano
20
+ DEFAULT_MODEL = (
21
+ "gpt-4.1" # Options: gpt-4.1, gpt-4o, o3-mini, o4-mini, gpt-5, gpt-5-nano
22
+ )
21
23
 
22
24
  def __init__(
23
25
  self, auth_manager: LLMAuthManager = None, config: LLMDriverConfig = None
janito/report_events.py CHANGED
@@ -14,7 +14,6 @@ class ReportSubtype(Enum):
14
14
  PROGRESS = "progress"
15
15
 
16
16
 
17
-
18
17
  class ReportAction(Enum):
19
18
  READ = "READ"
20
19
  CREATE = "CREATE"
@@ -25,7 +25,7 @@ class CreateFileTool(ToolBase):
25
25
  - "✅ Successfully created the file at ..."
26
26
 
27
27
  Note: Syntax validation is automatically performed after this operation.
28
-
28
+
29
29
  Security: This tool includes loop protection to prevent excessive file creation operations.
30
30
  Maximum 5 calls per 10 seconds for the same file path.
31
31
  """
@@ -68,19 +68,21 @@ class FetchUrlTool(ToolBase):
68
68
  {}
69
69
  ) # In-memory session cache - lifetime matches tool instance
70
70
  self._load_cache()
71
-
71
+
72
72
  # Browser-like session with cookies and headers
73
73
  self.session = requests.Session()
74
- self.session.headers.update({
75
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
76
- 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
77
- 'Accept-Language': 'en-US,en;q=0.5',
78
- 'Accept-Encoding': 'gzip, deflate, br',
79
- 'DNT': '1',
80
- 'Connection': 'keep-alive',
81
- 'Upgrade-Insecure-Requests': '1',
82
- })
83
-
74
+ self.session.headers.update(
75
+ {
76
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
77
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
78
+ "Accept-Language": "en-US,en;q=0.5",
79
+ "Accept-Encoding": "gzip, deflate, br",
80
+ "DNT": "1",
81
+ "Connection": "keep-alive",
82
+ "Upgrade-Insecure-Requests": "1",
83
+ }
84
+ )
85
+
84
86
  # Load cookies from disk if they exist
85
87
  self.cookies_file = self.cache_dir / "cookies.json"
86
88
  self._load_cookies()
@@ -120,12 +122,14 @@ class FetchUrlTool(ToolBase):
120
122
  try:
121
123
  cookies_data = []
122
124
  for cookie in self.session.cookies:
123
- cookies_data.append({
124
- 'name': cookie.name,
125
- 'value': cookie.value,
126
- 'domain': cookie.domain,
127
- 'path': cookie.path
128
- })
125
+ cookies_data.append(
126
+ {
127
+ "name": cookie.name,
128
+ "value": cookie.value,
129
+ "domain": cookie.domain,
130
+ "path": cookie.path,
131
+ }
132
+ )
129
133
  with open(self.cookies_file, "w", encoding="utf-8") as f:
130
134
  json.dump(cookies_data, f, indent=2)
131
135
  except IOError:
@@ -170,8 +174,14 @@ class FetchUrlTool(ToolBase):
170
174
  }
171
175
  self._save_cache()
172
176
 
173
- def _fetch_url_content(self, url: str, timeout: int = 10, headers: Optional[Dict[str, str]] = None,
174
- cookies: Optional[Dict[str, str]] = None, follow_redirects: bool = True) -> str:
177
+ def _fetch_url_content(
178
+ self,
179
+ url: str,
180
+ timeout: int = 10,
181
+ headers: Optional[Dict[str, str]] = None,
182
+ cookies: Optional[Dict[str, str]] = None,
183
+ follow_redirects: bool = True,
184
+ ) -> str:
175
185
  """Fetch URL content and handle HTTP errors.
176
186
 
177
187
  Implements two-tier caching:
@@ -224,23 +234,23 @@ class FetchUrlTool(ToolBase):
224
234
  request_headers = self.session.headers.copy()
225
235
  if headers:
226
236
  request_headers.update(headers)
227
-
237
+
228
238
  # Merge custom cookies
229
239
  if cookies:
230
240
  self.session.cookies.update(cookies)
231
241
 
232
242
  response = self.session.get(
233
- url,
234
- timeout=timeout,
243
+ url,
244
+ timeout=timeout,
235
245
  headers=request_headers,
236
- allow_redirects=follow_redirects
246
+ allow_redirects=follow_redirects,
237
247
  )
238
248
  response.raise_for_status()
239
249
  content = response.text
240
-
250
+
241
251
  # Save cookies after successful request
242
252
  self._save_cookies()
243
-
253
+
244
254
  # Cache successful responses in session cache
245
255
  self.session_cache[url] = content
246
256
  return content
@@ -354,8 +364,11 @@ class FetchUrlTool(ToolBase):
354
364
  # Check if we should save to file
355
365
  if save_to_file:
356
366
  html_content = self._fetch_url_content(
357
- url, timeout=timeout, headers=headers, cookies=cookies,
358
- follow_redirects=follow_redirects
367
+ url,
368
+ timeout=timeout,
369
+ headers=headers,
370
+ cookies=cookies,
371
+ follow_redirects=follow_redirects,
359
372
  )
360
373
  if html_content.startswith("Warning:"):
361
374
  return html_content
@@ -380,8 +393,11 @@ class FetchUrlTool(ToolBase):
380
393
 
381
394
  # Normal processing path
382
395
  html_content = self._fetch_url_content(
383
- url, timeout=timeout, headers=headers, cookies=cookies,
384
- follow_redirects=follow_redirects
396
+ url,
397
+ timeout=timeout,
398
+ headers=headers,
399
+ cookies=cookies,
400
+ follow_redirects=follow_redirects,
385
401
  )
386
402
  if html_content.startswith("Warning:"):
387
403
  return html_content
@@ -32,7 +32,8 @@ class PythonCommandRunTool(ToolBase):
32
32
  return tr("Warning: Empty code provided. Operation skipped.")
33
33
  if not silent:
34
34
  self.report_action(
35
- tr("🐍 Running: python -c ...\n{code}\n", code=code), ReportAction.EXECUTE
35
+ tr("🐍 Running: python -c ...\n{code}\n", code=code),
36
+ ReportAction.EXECUTE,
36
37
  )
37
38
  self.report_stdout("\n")
38
39
  else:
@@ -28,6 +28,7 @@ class PythonFileRunTool(ToolBase):
28
28
 
29
29
  def run(self, path: str, timeout: int = 60, silent: bool = False) -> str:
30
30
  from janito.tools.path_utils import expand_path
31
+
31
32
  path = expand_path(path)
32
33
  if not silent:
33
34
  self.report_action(
@@ -43,7 +43,7 @@ class RunPowershellCommandTool(ToolBase):
43
43
  if require_confirmation:
44
44
  self.report_warning(
45
45
  tr("⚠️ Confirmation requested, but no handler (auto-confirmed)."),
46
- ReportAction.EXECUTE
46
+ ReportAction.EXECUTE,
47
47
  )
48
48
  return True # Auto-confirm for now
49
49
  return True
@@ -98,7 +98,7 @@ class SearchTextTool(ToolBase):
98
98
  if max_depth > 0:
99
99
  info_str += tr(" [max_depth={max_depth}]", max_depth=max_depth)
100
100
  if count_only:
101
- info_str += " [count]"
101
+ info_str += " [count-only]"
102
102
  self.report_action(info_str, ReportAction.READ)
103
103
  if os.path.isfile(search_path):
104
104
  dir_output, dir_limit_reached, per_file_counts = self._handle_file(
@@ -8,40 +8,43 @@ def validate_jinja2(path: str) -> str:
8
8
  """Validate Jinja2 template syntax."""
9
9
  try:
10
10
  from jinja2 import Environment, TemplateSyntaxError
11
-
11
+
12
12
  with open(path, "r", encoding="utf-8") as f:
13
13
  content = f.read()
14
-
14
+
15
15
  # Create a Jinja2 environment and try to parse the template
16
16
  env = Environment()
17
17
  try:
18
18
  env.parse(content)
19
19
  return tr("✅ Syntax OK")
20
20
  except TemplateSyntaxError as e:
21
- line_num = getattr(e, 'lineno', 0)
22
- return tr("⚠️ Warning: Syntax error: {error} at line {line}",
23
- error=str(e), line=line_num)
21
+ line_num = getattr(e, "lineno", 0)
22
+ return tr(
23
+ "⚠️ Warning: Syntax error: {error} at line {line}",
24
+ error=str(e),
25
+ line=line_num,
26
+ )
24
27
  except Exception as e:
25
28
  return tr("⚠️ Warning: Syntax error: {error}", error=str(e))
26
-
29
+
27
30
  except ImportError:
28
31
  # If jinja2 is not available, just check basic structure
29
32
  try:
30
33
  with open(path, "r", encoding="utf-8") as f:
31
34
  content = f.read()
32
-
35
+
33
36
  # Basic checks for common Jinja2 syntax issues
34
37
  open_tags = content.count("{%")
35
38
  close_tags = content.count("%}")
36
39
  open_vars = content.count("{{")
37
40
  close_vars = content.count("}}")
38
-
41
+
39
42
  if open_tags != close_tags:
40
43
  return tr("⚠️ Warning: Syntax error: Mismatched Jinja2 tags")
41
44
  if open_vars != close_vars:
42
45
  return tr("⚠️ Warning: Syntax error: Mismatched Jinja2 variables")
43
-
46
+
44
47
  return tr("✅ Syntax OK (basic validation)")
45
-
48
+
46
49
  except Exception as e:
47
- return tr("⚠️ Warning: Syntax error: {error}", error=str(e))
50
+ return tr("⚠️ Warning: Syntax error: {error}", error=str(e))
janito/tools/base.py CHANGED
@@ -1,11 +1,12 @@
1
1
  class BaseTool:
2
2
  """Base class for all tools."""
3
+
3
4
  tool_name: str = ""
4
-
5
+
5
6
  def __init__(self):
6
7
  if not self.tool_name:
7
8
  self.tool_name = self.__class__.__name__.lower()
8
-
9
+
9
10
  def run(self, *args, **kwargs) -> str:
10
11
  """Execute the tool."""
11
- raise NotImplementedError
12
+ raise NotImplementedError