janito 2.7.0__py3-none-any.whl → 2.9.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 (121) hide show
  1. janito/__init__.py +0 -1
  2. janito/__main__.py +0 -1
  3. janito/_version.py +0 -3
  4. janito/agent/setup_agent.py +77 -10
  5. janito/agent/templates/profiles/{system_prompt_template_plain_software_developer.txt.j2 → system_prompt_template_Developer_with_Python_Tools.txt.j2} +5 -1
  6. janito/agent/templates/profiles/system_prompt_template_developer.txt.j2 +3 -12
  7. janito/cli/__init__.py +0 -1
  8. janito/cli/chat_mode/bindings.py +1 -1
  9. janito/cli/chat_mode/chat_entry.py +0 -2
  10. janito/cli/chat_mode/prompt_style.py +0 -3
  11. janito/cli/chat_mode/script_runner.py +9 -5
  12. janito/cli/chat_mode/session.py +100 -37
  13. janito/cli/chat_mode/session_profile_select.py +61 -52
  14. janito/cli/chat_mode/shell/commands/__init__.py +1 -5
  15. janito/cli/chat_mode/shell/commands/_priv_check.py +1 -0
  16. janito/cli/chat_mode/shell/commands/_priv_status.py +13 -0
  17. janito/cli/chat_mode/shell/commands/bang.py +10 -3
  18. janito/cli/chat_mode/shell/commands/conversation_restart.py +24 -7
  19. janito/cli/chat_mode/shell/commands/execute.py +22 -7
  20. janito/cli/chat_mode/shell/commands/help.py +4 -1
  21. janito/cli/chat_mode/shell/commands/model.py +13 -5
  22. janito/cli/chat_mode/shell/commands/privileges.py +21 -0
  23. janito/cli/chat_mode/shell/commands/prompt.py +0 -2
  24. janito/cli/chat_mode/shell/commands/read.py +22 -5
  25. janito/cli/chat_mode/shell/commands/tools.py +15 -4
  26. janito/cli/chat_mode/shell/commands/write.py +22 -5
  27. janito/cli/chat_mode/shell/input_history.py +3 -1
  28. janito/cli/chat_mode/shell/session/manager.py +0 -2
  29. janito/cli/chat_mode/toolbar.py +25 -19
  30. janito/cli/cli_commands/list_config.py +31 -0
  31. janito/cli/cli_commands/list_models.py +1 -1
  32. janito/cli/cli_commands/list_profiles.py +79 -0
  33. janito/cli/cli_commands/list_providers.py +1 -0
  34. janito/cli/cli_commands/list_tools.py +35 -7
  35. janito/cli/cli_commands/model_utils.py +5 -3
  36. janito/cli/cli_commands/show_config.py +16 -11
  37. janito/cli/cli_commands/show_system_prompt.py +23 -9
  38. janito/cli/config.py +0 -13
  39. janito/cli/core/getters.py +16 -1
  40. janito/cli/core/runner.py +25 -8
  41. janito/cli/core/setters.py +13 -76
  42. janito/cli/main_cli.py +60 -27
  43. janito/cli/prompt_core.py +19 -18
  44. janito/cli/prompt_setup.py +6 -3
  45. janito/cli/rich_terminal_reporter.py +19 -5
  46. janito/cli/single_shot_mode/handler.py +14 -5
  47. janito/cli/verbose_output.py +5 -1
  48. janito/config.py +1 -0
  49. janito/config_manager.py +15 -2
  50. janito/drivers/azure_openai/driver.py +27 -30
  51. janito/drivers/openai/driver.py +53 -36
  52. janito/formatting_token.py +12 -4
  53. janito/llm/agent.py +15 -6
  54. janito/llm/driver.py +1 -0
  55. janito/llm/provider.py +1 -1
  56. janito/provider_registry.py +31 -70
  57. janito/providers/__init__.py +1 -0
  58. janito/providers/anthropic/model_info.py +0 -1
  59. janito/providers/anthropic/provider.py +9 -14
  60. janito/providers/azure_openai/provider.py +10 -5
  61. janito/providers/deepseek/provider.py +5 -4
  62. janito/providers/google/model_info.py +4 -2
  63. janito/providers/google/provider.py +11 -5
  64. janito/providers/groq/__init__.py +1 -0
  65. janito/providers/groq/model_info.py +45 -0
  66. janito/providers/groq/provider.py +76 -0
  67. janito/providers/moonshotai/provider.py +11 -4
  68. janito/providers/openai/model_info.py +0 -1
  69. janito/providers/openai/provider.py +6 -7
  70. janito/tools/__init__.py +2 -0
  71. janito/tools/adapters/local/__init__.py +2 -1
  72. janito/tools/adapters/local/adapter.py +21 -4
  73. janito/tools/adapters/local/ask_user.py +1 -0
  74. janito/tools/adapters/local/copy_file.py +1 -0
  75. janito/tools/adapters/local/create_directory.py +1 -0
  76. janito/tools/adapters/local/create_file.py +1 -0
  77. janito/tools/adapters/local/delete_text_in_file.py +2 -1
  78. janito/tools/adapters/local/fetch_url.py +1 -0
  79. janito/tools/adapters/local/find_files.py +7 -6
  80. janito/tools/adapters/local/get_file_outline/core.py +1 -0
  81. janito/tools/adapters/local/get_file_outline/java_outline.py +22 -15
  82. janito/tools/adapters/local/get_file_outline/search_outline.py +1 -0
  83. janito/tools/adapters/local/move_file.py +1 -0
  84. janito/tools/adapters/local/open_html_in_browser.py +15 -5
  85. janito/tools/adapters/local/open_url.py +1 -0
  86. janito/tools/adapters/local/python_code_run.py +1 -0
  87. janito/tools/adapters/local/python_command_run.py +1 -0
  88. janito/tools/adapters/local/python_file_run.py +1 -0
  89. janito/tools/adapters/local/read_files.py +19 -4
  90. janito/tools/adapters/local/remove_directory.py +1 -0
  91. janito/tools/adapters/local/remove_file.py +1 -0
  92. janito/tools/adapters/local/replace_text_in_file.py +4 -3
  93. janito/tools/adapters/local/run_bash_command.py +1 -0
  94. janito/tools/adapters/local/run_powershell_command.py +1 -0
  95. janito/tools/adapters/local/search_text/core.py +18 -17
  96. janito/tools/adapters/local/search_text/match_lines.py +5 -5
  97. janito/tools/adapters/local/search_text/pattern_utils.py +1 -1
  98. janito/tools/adapters/local/search_text/traverse_directory.py +7 -7
  99. janito/tools/adapters/local/validate_file_syntax/core.py +1 -1
  100. janito/tools/adapters/local/validate_file_syntax/html_validator.py +8 -1
  101. janito/tools/disabled_tools.py +68 -0
  102. janito/tools/path_security.py +18 -11
  103. janito/tools/permissions.py +6 -0
  104. janito/tools/permissions_parse.py +4 -3
  105. janito/tools/tool_base.py +11 -5
  106. janito/tools/tool_use_tracker.py +1 -4
  107. janito/tools/tool_utils.py +1 -1
  108. janito/tools/tools_adapter.py +57 -25
  109. {janito-2.7.0.dist-info → janito-2.9.0.dist-info}/METADATA +11 -19
  110. janito-2.9.0.dist-info/RECORD +205 -0
  111. janito/cli/chat_mode/shell/commands/livelogs.py +0 -49
  112. janito/drivers/mistralai/driver.py +0 -41
  113. janito/providers/mistralai/model_info.py +0 -37
  114. janito/providers/mistralai/provider.py +0 -72
  115. janito/providers/provider_static_info.py +0 -21
  116. janito-2.7.0.dist-info/RECORD +0 -202
  117. /janito/agent/templates/profiles/{system_prompt_template_assistant.txt.j2 → system_prompt_template_model_conversation_without_tools_or_context.txt.j2} +0 -0
  118. {janito-2.7.0.dist-info → janito-2.9.0.dist-info}/WHEEL +0 -0
  119. {janito-2.7.0.dist-info → janito-2.9.0.dist-info}/entry_points.txt +0 -0
  120. {janito-2.7.0.dist-info → janito-2.9.0.dist-info}/licenses/LICENSE +0 -0
  121. {janito-2.7.0.dist-info → janito-2.9.0.dist-info}/top_level.txt +0 -0
@@ -27,25 +27,24 @@ def handle_set(args, config_mgr=None):
27
27
  print(f"Error parsing --set value: {e}")
28
28
  return True
29
29
 
30
+
30
31
  def _validate_set_arg_format(set_arg):
31
32
  if "=" not in set_arg:
32
- print(
33
- "Error: --set requires KEY=VALUE (e.g., --set provider=provider_name)."
34
- )
33
+ print("Error: --set requires KEY=VALUE (e.g., --set provider=provider_name).")
35
34
  return False
36
35
  return True
37
36
 
37
+
38
38
  def _parse_set_arg(set_arg):
39
39
  key, value = set_arg.split("=", 1)
40
40
  return key.strip(), value.strip()
41
41
 
42
+
42
43
  def _dispatch_set_key(key, value):
43
44
  if key == "provider":
44
45
  return _handle_set_config_provider(value)
45
46
  if key == "model":
46
47
  return _handle_set_global_model(value)
47
- if "." in key and key.endswith(".model"):
48
- return _handle_set_provider_model(key, value)
49
48
  if key == "max_tokens":
50
49
  return _handle_set_max_tokens(value)
51
50
  if key == "base_url":
@@ -54,18 +53,24 @@ def _dispatch_set_key(key, value):
54
53
  global_config.file_set("azure_deployment_name", value)
55
54
  print(f"Azure deployment name set to '{value}'.")
56
55
  return True
57
- if ".max_tokens" in key or ".base_url" in key:
58
- return _handle_set_provider_level_setting(key, value)
59
56
  if key == "tool_permissions":
60
57
  from janito.tools.permissions_parse import parse_permissions_string
61
58
  from janito.tools.permissions import set_global_allowed_permissions
59
+
62
60
  perms = parse_permissions_string(value)
63
61
  global_config.file_set("tool_permissions", value)
64
62
  set_global_allowed_permissions(perms)
65
63
  print(f"Tool permissions set to '{value}' (parsed: {perms})")
66
64
  return True
65
+ if key == "disabled_tools":
66
+ from janito.tools.disabled_tools import set_disabled_tools
67
+
68
+ set_disabled_tools(value)
69
+ global_config.file_set("disabled_tools", value)
70
+ print(f"Disabled tools set to '{value}'")
71
+ return True
67
72
  print(
68
- f"Error: Unknown config key '{key}'. Supported: provider, model, <provider>.model, max_tokens, base_url, azure_deployment_name, <provider>.max_tokens, <provider>.base_url, <provider>.<model>.max_tokens, <provider>.<model>.base_url, tool_permissions"
73
+ f"Error: Unknown config key '{key}'. Supported: provider, model, max_tokens, base_url, azure_deployment_name, tool_permissions, disabled_tools"
69
74
  )
70
75
  return True
71
76
 
@@ -87,48 +92,6 @@ def _handle_set_base_url(value):
87
92
  return True
88
93
 
89
94
 
90
- def _handle_set_provider_level_setting(key, value):
91
- parts = key.split(".")
92
- if len(parts) == 2:
93
- provider, par_key = parts
94
- if par_key == "max_tokens":
95
- try:
96
- ival = int(value)
97
- except Exception:
98
- print("Error: max_tokens must be set to an integer value.")
99
- return True
100
- global_config.set_provider_config(provider, "max_tokens", ival)
101
- print(f"max_tokens for provider '{provider}' set to {ival}.")
102
- return True
103
- if par_key == "base_url":
104
- global_config.set_provider_config(provider, "base_url", value)
105
- print(f"base_url for provider '{provider}' set to {value}.")
106
- return True
107
- elif len(parts) == 3:
108
- provider, model, mk = parts
109
- if mk == "max_tokens":
110
- try:
111
- ival = int(value)
112
- except Exception:
113
- print("Error: max_tokens must be set to an integer value.")
114
- return True
115
- global_config.set_provider_model_config(provider, model, "max_tokens", ival)
116
- print(
117
- f"max_tokens for provider '{provider}', model '{model}' set to {ival}."
118
- )
119
- return True
120
- if mk == "base_url":
121
- global_config.set_provider_model_config(provider, model, "base_url", value)
122
- print(
123
- f"base_url for provider '{provider}', model '{model}' set to {value}."
124
- )
125
- return True
126
- print(
127
- f"Error: Unknown config key '{key}'. Supported: provider, model, <provider>.model, max_tokens, base_url, <provider>.max_tokens, <provider>.base_url, <provider>.<model>.max_tokens, <provider>.<model>.base_url"
128
- )
129
- return True
130
-
131
-
132
95
  def _handle_set_config_provider(value):
133
96
  try:
134
97
  supported = ProviderRegistry().get_provider(value)
@@ -144,32 +107,6 @@ def _handle_set_config_provider(value):
144
107
  return True
145
108
 
146
109
 
147
- def _handle_set_provider_model(key, value):
148
- provider_name, suffix = key.rsplit(".", 1)
149
- if suffix != "model":
150
- print(
151
- f"Error: Only <provider>.model is supported for provider-specific model override. Not: '{key}'"
152
- )
153
- return True
154
- try:
155
- provider_cls = ProviderRegistry().get_provider(provider_name)
156
- provider_instance = provider_cls()
157
- except Exception:
158
- print(
159
- f"Error: Provider '{provider_name}' is not supported. Run '--list-providers' to see the supported list."
160
- )
161
- return True
162
- model_info = provider_instance.get_model_info(value)
163
- if not model_info:
164
- print(
165
- f"Error: Model '{value}' is not defined for provider '{provider_name}'. Run '-p {provider_name} -l' to see models."
166
- )
167
- return True
168
- global_config.set_provider_config(provider_name, "model", value)
169
- print(f"Default model for provider '{provider_name}' set to '{value}'.")
170
- return True
171
-
172
-
173
110
  def _handle_set_global_model(value):
174
111
  # Try to validate model choice (against current provider if possible)
175
112
  provider_name = global_config.get("provider")
janito/cli/main_cli.py CHANGED
@@ -21,7 +21,6 @@ definition = [
21
21
  "help": "Disable path security: allow tool arguments to use any file/directory path (DANGEROUS)",
22
22
  },
23
23
  ),
24
-
25
24
  (
26
25
  ["--profile"],
27
26
  {
@@ -99,6 +98,14 @@ definition = [
99
98
  (["--version"], {"action": "version", "version": None}),
100
99
  (["--list-tools"], {"action": "store_true", "help": "List all registered tools"}),
101
100
  (["--show-config"], {"action": "store_true", "help": "Show the current config"}),
101
+ (
102
+ ["--list-config"],
103
+ {"action": "store_true", "help": "List all configuration files"},
104
+ ),
105
+ (
106
+ ["--list-profiles"],
107
+ {"action": "store_true", "help": "List available system prompt profiles"},
108
+ ),
102
109
  (
103
110
  ["--list-providers"],
104
111
  {"action": "store_true", "help": "List supported LLM providers"},
@@ -114,7 +121,7 @@ definition = [
114
121
  "help": "Set API key for the provider (requires -p PROVIDER)",
115
122
  },
116
123
  ),
117
- (["--set"], {"metavar": "[PROVIDER_NAME.]KEY=VALUE", "help": "Set a config key"}),
124
+ (["--set"], {"metavar": "KEY=VALUE", "help": "Set a config key"}),
118
125
  (["-s", "--system"], {"metavar": "SYSTEM_PROMPT", "help": "Set a system prompt"}),
119
126
  (
120
127
  ["-S", "--show-system"],
@@ -141,22 +148,7 @@ definition = [
141
148
  },
142
149
  ),
143
150
  (
144
- ["--web"],
145
- {
146
- "action": "store_true",
147
- "default": False,
148
- "help": "Enable the builtin lightweight web file viewer for terminal links (disabled by default)",
149
- },
150
- ),
151
- (
152
- ["---port"],
153
- {
154
- "type": int,
155
- "default": 8088,
156
- "help": "Port for the server (default: 8088)",
157
- },
158
- ),
159
- (["--effort"],
151
+ ["--effort"],
160
152
  {
161
153
  "choices": ["low", "medium", "high", "none"],
162
154
  "default": None,
@@ -175,6 +167,13 @@ definition = [
175
167
  "help": "Print debug info on event subscribe/submit methods",
176
168
  },
177
169
  ),
170
+ (
171
+ ["-c", "--config"],
172
+ {
173
+ "metavar": "NAME",
174
+ "help": "Use custom configuration file ~/.janito/configs/NAME.json instead of default config.json",
175
+ },
176
+ ),
178
177
  ]
179
178
 
180
179
  MODIFIER_KEYS = [
@@ -184,11 +183,8 @@ MODIFIER_KEYS = [
184
183
  "profile",
185
184
  "system",
186
185
  "temperature",
187
-
188
186
  "verbose",
189
187
  "raw",
190
- "web",
191
- "_port",
192
188
  "verbose_api",
193
189
  "verbose_tools",
194
190
  "exec",
@@ -196,7 +192,14 @@ MODIFIER_KEYS = [
196
192
  "write",
197
193
  ]
198
194
  SETTER_KEYS = ["set", "set_provider", "set_api_key", "unset"]
199
- GETTER_KEYS = ["show_config", "list_providers", "list_models", "list_tools"]
195
+ GETTER_KEYS = [
196
+ "show_config",
197
+ "list_providers",
198
+ "list_profiles",
199
+ "list_models",
200
+ "list_tools",
201
+ "list_config",
202
+ ]
200
203
 
201
204
 
202
205
  class RunMode(enum.Enum):
@@ -217,14 +220,38 @@ class JanitoCLI:
217
220
  self._define_args()
218
221
  self.args = self.parser.parse_args()
219
222
  self._set_all_arg_defaults()
223
+ # Support custom config file via -c/--config
224
+ if getattr(self.args, "config", None):
225
+ from janito import config as global_config
226
+ from janito.config_manager import ConfigManager
227
+ import sys
228
+ import importlib
229
+
230
+ config_name = self.args.config
231
+ # Re-initialize the global config singleton
232
+ new_config = ConfigManager(config_name=config_name)
233
+ # Ensure the config path is updated when the singleton already existed
234
+ from pathlib import Path
235
+
236
+ new_config.config_path = (
237
+ Path.home() / ".janito" / "configs" / f"{config_name}.json"
238
+ )
239
+ # Reload config from the selected file
240
+ new_config._load_file_config()
241
+ # Patch the global singleton reference
242
+ import janito.config as config_module
243
+
244
+ config_module.config = new_config
245
+ sys.modules["janito.config"].config = new_config
220
246
  # Support reading prompt from stdin if no user_prompt is given
221
247
  import sys
248
+
222
249
  if not sys.stdin.isatty():
223
250
  stdin_input = sys.stdin.read().strip()
224
251
  if stdin_input:
225
252
  if self.args.user_prompt and len(self.args.user_prompt) > 0:
226
253
  # Prefix the prompt argument to the stdin input
227
- combined = ' '.join(self.args.user_prompt) + ' ' + stdin_input
254
+ combined = " ".join(self.args.user_prompt) + " " + stdin_input
228
255
  self.args.user_prompt = [combined]
229
256
  else:
230
257
  self.args.user_prompt = [stdin_input]
@@ -289,13 +316,19 @@ class JanitoCLI:
289
316
  return
290
317
  # Special handling: provider is not required for list_providers, list_tools, show_config
291
318
  if run_mode == RunMode.GET and (
292
- self.args.list_providers or self.args.list_tools or self.args.show_config
319
+ self.args.list_providers
320
+ or self.args.list_tools
321
+ or self.args.list_profiles
322
+ or self.args.show_config
323
+ or self.args.list_config
293
324
  ):
294
325
  self._maybe_print_verbose_provider_model()
295
326
  handle_getter(self.args)
296
327
  return
297
328
  # If running in single shot mode and --profile is not provided, default to 'developer' profile
298
- if get_prompt_mode(self.args) == "single_shot" and not getattr(self.args, "profile", None):
329
+ if get_prompt_mode(self.args) == "single_shot" and not getattr(
330
+ self.args, "profile", None
331
+ ):
299
332
  self.args.profile = "developer"
300
333
  provider = self._get_provider_or_default()
301
334
  if provider is None:
@@ -316,14 +349,14 @@ class JanitoCLI:
316
349
  if run_mode == RunMode.RUN:
317
350
  self._maybe_print_verbose_run_mode()
318
351
  # DEBUG: Print exec_enabled propagation at main_cli
319
-
352
+
320
353
  handle_runner(
321
354
  self.args,
322
355
  provider,
323
356
  llm_driver_config,
324
357
  agent_role,
325
358
  verbose_tools=self.args.verbose_tools,
326
- )
359
+ )
327
360
  elif run_mode == RunMode.GET:
328
361
  handle_getter(self.args)
329
362
 
janito/cli/prompt_core.py CHANGED
@@ -8,7 +8,12 @@ from janito.performance_collector import PerformanceCollector
8
8
  from rich.status import Status
9
9
  from rich.console import Console
10
10
  from typing import Any, Optional, Callable
11
- from janito.driver_events import RequestStarted, RequestFinished, RequestStatus, RateLimitRetry
11
+ from janito.driver_events import (
12
+ RequestStarted,
13
+ RequestFinished,
14
+ RequestStatus,
15
+ RateLimitRetry,
16
+ )
12
17
  from janito.tools.tool_events import ToolCallError
13
18
  import threading
14
19
  from janito.cli.verbose_output import print_verbose_header
@@ -56,7 +61,10 @@ class PromptHandler:
56
61
  if isinstance(inner_event, RequestFinished):
57
62
  if getattr(inner_event, "status", None) == "error":
58
63
  return self._handle_request_finished_error(inner_event, status)
59
- if getattr(inner_event, "status", None) in (RequestStatus.EMPTY_RESPONSE, RequestStatus.TIMEOUT):
64
+ if getattr(inner_event, "status", None) in (
65
+ RequestStatus.EMPTY_RESPONSE,
66
+ RequestStatus.TIMEOUT,
67
+ ):
60
68
  return self._handle_empty_or_timeout(inner_event, status)
61
69
  status.update("[bold green]Received response![bold green]")
62
70
  return "break"
@@ -78,7 +86,9 @@ class PromptHandler:
78
86
  return None
79
87
 
80
88
  def _handle_rate_limit_retry(self, inner_event, status):
81
- status.update(f"[yellow]Rate limited. Waiting {inner_event.retry_delay:.0f}s before retry (attempt {inner_event.attempt}).[yellow]")
89
+ status.update(
90
+ f"[yellow]Rate limited. Waiting {inner_event.retry_delay:.0f}s before retry (attempt {inner_event.attempt}).[yellow]"
91
+ )
82
92
  return None
83
93
 
84
94
  def _handle_request_finished_error(self, inner_event, status):
@@ -89,9 +99,7 @@ class PromptHandler:
89
99
  "Status 429" in error_msg
90
100
  and "Service tier capacity exceeded for this model" in error_msg
91
101
  ):
92
- status.update(
93
- "[yellow]Service tier capacity exceeded, retrying...[yellow]"
94
- )
102
+ status.update("[yellow]Service tier capacity exceeded, retrying...[yellow]")
95
103
  return "break"
96
104
  status.update(f"[bold red]Error: {error_msg}[bold red]")
97
105
  self.console.print(f"[red]Error: {error_msg}[red]")
@@ -99,18 +107,12 @@ class PromptHandler:
99
107
 
100
108
  def _handle_tool_call_error(self, inner_event, status):
101
109
  error_msg = (
102
- inner_event.error
103
- if hasattr(inner_event, "error")
104
- else "Unknown tool error"
110
+ inner_event.error if hasattr(inner_event, "error") else "Unknown tool error"
105
111
  )
106
112
  tool_name = (
107
- inner_event.tool_name
108
- if hasattr(inner_event, "tool_name")
109
- else "unknown"
110
- )
111
- status.update(
112
- f"[bold red]Tool Error in '{tool_name}': {error_msg}[bold red]"
113
+ inner_event.tool_name if hasattr(inner_event, "tool_name") else "unknown"
113
114
  )
115
+ status.update(f"[bold red]Tool Error in '{tool_name}': {error_msg}[bold red]")
114
116
  self.console.print(f"[red]Tool Error in '{tool_name}': {error_msg}[red]")
115
117
  return "break"
116
118
 
@@ -118,9 +120,7 @@ class PromptHandler:
118
120
  details = getattr(inner_event, "details", None) or {}
119
121
  block_reason = details.get("block_reason")
120
122
  block_msg = details.get("block_reason_message")
121
- msg = details.get(
122
- "message", "LLM returned an empty or incomplete response."
123
- )
123
+ msg = details.get("message", "LLM returned an empty or incomplete response.")
124
124
  driver_name = getattr(inner_event, "driver_name", "unknown driver")
125
125
  if block_reason or block_msg:
126
126
  status.update(
@@ -221,6 +221,7 @@ class PromptHandler:
221
221
  self.console.print("[red]Interrupted by the user.[/red]")
222
222
  try:
223
223
  from janito.driver_events import RequestFinished, RequestStatus
224
+
224
225
  # Record a synthetic "cancelled" final event so that downstream
225
226
  # handlers (e.g. single_shot_mode.handler._post_prompt_actions)
226
227
  # can reliably detect that the prompt was interrupted by the
@@ -4,6 +4,7 @@ both single–shot and chat modes can reuse. Having one central place avoids th
4
4
  code duplication that previously existed in `chat_mode.session.ChatSession` and
5
5
  `single_shot_mode.handler.PromptHandler`.
6
6
  """
7
+
7
8
  from __future__ import annotations
8
9
 
9
10
  from janito.agent.setup_agent import create_configured_agent
@@ -21,7 +22,6 @@ def setup_agent_and_prompt_handler(
21
22
  role: Optional[str] = None,
22
23
  verbose_tools: bool = False,
23
24
  verbose_agent: bool = False,
24
-
25
25
  allowed_permissions: Optional[list[str]] = None,
26
26
  profile: Optional[str] = None,
27
27
  profile_system_prompt: Optional[str] = None,
@@ -34,16 +34,19 @@ def setup_agent_and_prompt_handler(
34
34
  across *single-shot* and *chat* modes – both of which need an agent plus a
35
35
  prompt handler that points to that agent.
36
36
  """
37
+ no_tools_mode = False
38
+ if hasattr(args, 'no_tools_mode'):
39
+ no_tools_mode = getattr(args, 'no_tools_mode', False)
37
40
  agent = create_configured_agent(
38
41
  provider_instance=provider_instance,
39
42
  llm_driver_config=llm_driver_config,
40
43
  role=role,
41
44
  verbose_tools=verbose_tools,
42
45
  verbose_agent=verbose_agent,
43
-
44
46
  allowed_permissions=allowed_permissions,
45
47
  profile=profile,
46
48
  profile_system_prompt=profile_system_prompt,
49
+ no_tools_mode=no_tools_mode,
47
50
  )
48
51
 
49
52
  prompt_handler = GenericPromptHandler(
@@ -53,4 +56,4 @@ def setup_agent_and_prompt_handler(
53
56
  )
54
57
  prompt_handler.agent = agent
55
58
 
56
- return agent, prompt_handler
59
+ return agent, prompt_handler
@@ -12,6 +12,7 @@ from janito.llm import message_parts
12
12
 
13
13
  import sys
14
14
 
15
+
15
16
  class RichTerminalReporter(EventHandlerBase):
16
17
  """
17
18
  Handles UI rendering for janito events using Rich.
@@ -31,6 +32,7 @@ class RichTerminalReporter(EventHandlerBase):
31
32
  import janito.report_events as report_events
32
33
 
33
34
  import janito.tools.tool_events as tool_events
35
+
34
36
  super().__init__(driver_events, report_events, tool_events)
35
37
  self._waiting_printed = False
36
38
 
@@ -53,7 +55,9 @@ class RichTerminalReporter(EventHandlerBase):
53
55
  model = getattr(event, "model_name", None)
54
56
  if not model:
55
57
  model = "?"
56
- self.console.print(f"[bold cyan]Waiting for {provider} (model: {model})...[/bold cyan]", end="")
58
+ self.console.print(
59
+ f"[bold cyan]Waiting for {provider} (model: {model})...[/bold cyan]", end=""
60
+ )
57
61
 
58
62
  def on_ResponseReceived(self, event):
59
63
  parts = event.parts if hasattr(event, "parts") else None
@@ -70,7 +74,7 @@ class RichTerminalReporter(EventHandlerBase):
70
74
  """
71
75
  Clears the entire current line in the terminal and returns the cursor to column 1.
72
76
  """
73
- sys.stdout.write('\033[2K\r')
77
+ sys.stdout.write("\033[2K\r")
74
78
  sys.stdout.flush()
75
79
 
76
80
  def on_RequestFinished(self, event):
@@ -118,9 +122,15 @@ class RichTerminalReporter(EventHandlerBase):
118
122
  action = getattr(event, "action", None)
119
123
  tool = getattr(event, "tool", None)
120
124
  context = getattr(event, "context", None)
121
- if subtype == ReportSubtype.ERROR and msg and "[SECURITY] Path access denied" in msg:
125
+ if (
126
+ subtype == ReportSubtype.ERROR
127
+ and msg
128
+ and "[SECURITY] Path access denied" in msg
129
+ ):
122
130
  # Highlight security errors with a distinct style
123
- self.console.print(Panel(f"{msg}", title="[red]SECURITY VIOLATION[/red]", style="bold red"))
131
+ self.console.print(
132
+ Panel(f"{msg}", title="[red]SECURITY VIOLATION[/red]", style="bold red")
133
+ )
124
134
  self.console.file.flush()
125
135
  return
126
136
 
@@ -135,7 +145,11 @@ class RichTerminalReporter(EventHandlerBase):
135
145
  getattr(ReportAction, "WRITE", None),
136
146
  getattr(ReportAction, "DELETE", None),
137
147
  )
138
- style = "orange1" if getattr(event, "action", None) in modification_actions else "cyan"
148
+ style = (
149
+ "orange1"
150
+ if getattr(event, "action", None) in modification_actions
151
+ else "cyan"
152
+ )
139
153
  self.console.print(Text(msg, style=style), end="")
140
154
  self.console.file.flush()
141
155
  elif subtype in (
@@ -11,12 +11,19 @@ import time
11
11
 
12
12
 
13
13
  class PromptHandler:
14
- def __init__(self, args, provider_instance, llm_driver_config, role=None, allowed_permissions=None):
14
+ def __init__(
15
+ self,
16
+ args,
17
+ provider_instance,
18
+ llm_driver_config,
19
+ role=None,
20
+ allowed_permissions=None,
21
+ ):
15
22
  self.args = args
16
23
  self.provider_instance = provider_instance
17
24
  self.llm_driver_config = llm_driver_config
18
25
  self.role = role
19
- # Instantiate agent together with prompt handler using the shared helper
26
+ # Instantiate agent together with prompt handler using the shared helper
20
27
  self.agent, self.generic_handler = setup_agent_and_prompt_handler(
21
28
  args=args,
22
29
  provider_instance=provider_instance,
@@ -24,7 +31,6 @@ class PromptHandler:
24
31
  role=role,
25
32
  verbose_tools=getattr(args, "verbose_tools", False),
26
33
  verbose_agent=getattr(args, "verbose_agent", False),
27
-
28
34
  allowed_permissions=allowed_permissions,
29
35
  profile=getattr(args, "profile", None),
30
36
  )
@@ -43,6 +49,7 @@ class PromptHandler:
43
49
  "[yellow]Warning: Some characters in your input were not valid UTF-8 and have been replaced.[/yellow]"
44
50
  )
45
51
  import time
52
+
46
53
  try:
47
54
  start_time = time.time()
48
55
  self.generic_handler.handle_prompt(
@@ -67,13 +74,15 @@ class PromptHandler:
67
74
  import sys
68
75
  from janito.formatting_token import print_token_message_summary
69
76
  from janito.perf_singleton import performance_collector
77
+
70
78
  usage = performance_collector.get_last_request_usage()
71
79
  # If running in stdin mode, do not print token usage
72
80
  if sys.stdin.isatty():
73
- print_token_message_summary(shared_console, msg_count=1, usage=usage, elapsed=elapsed)
81
+ print_token_message_summary(
82
+ shared_console, msg_count=1, usage=usage, elapsed=elapsed
83
+ )
74
84
  self._cleanup_driver_and_console()
75
85
 
76
-
77
86
  def _cleanup_driver_and_console(self):
78
87
  if hasattr(self.agent, "join_driver"):
79
88
  if (
@@ -19,7 +19,11 @@ def print_verbose_header(agent, args):
19
19
  parts = [
20
20
  f"Janito {VERSION}",
21
21
  f"Provider: {agent.llm_provider.__class__.__name__}",
22
- (f"Model: {agent.llm_provider.driver_config.extra.get('azure_deployment_name', agent.llm_provider.driver_config.model)}{role_part}" if agent.llm_provider.__class__.__name__ == 'AzureOpenAIProvider' else f"Model: {getattr(agent.llm_provider.driver_config, 'model', '-')}{role_part}"),
22
+ (
23
+ f"Model: {agent.llm_provider.driver_config.extra.get('azure_deployment_name', agent.llm_provider.driver_config.model)}{role_part}"
24
+ if agent.llm_provider.__class__.__name__ == "AzureOpenAIProvider"
25
+ else f"Model: {getattr(agent.llm_provider.driver_config, 'model', '-')}{role_part}"
26
+ ),
23
27
  f"Driver: {agent.llm_provider.__class__.__module__.split('.')[-2] if len(agent.llm_provider.__class__.__module__.split('.')) > 1 else agent.llm_provider.__class__.__name__}",
24
28
  ]
25
29
  if hasattr(args, "think") and args.think:
janito/config.py CHANGED
@@ -2,4 +2,5 @@
2
2
  from janito.config_manager import ConfigManager
3
3
 
4
4
  # Only one global instance! Used by CLI, provider_config, others:
5
+ # If you want to use a custom config, re-initialize this singleton with config_name or config_path before use.
5
6
  config = ConfigManager(config_path=None)
janito/config_manager.py CHANGED
@@ -20,13 +20,22 @@ class ConfigManager:
20
20
  cls._instance = super(ConfigManager, cls).__new__(cls)
21
21
  return cls._instance
22
22
 
23
- def __init__(self, config_path=None, defaults=None, runtime_overrides=None):
23
+ def __init__(
24
+ self, config_path=None, defaults=None, runtime_overrides=None, config_name=None
25
+ ):
24
26
  # Lazy single-init
25
27
  if hasattr(self, "_initialized") and self._initialized:
26
28
  return
27
29
  self._initialized = True
28
30
 
29
- self.config_path = Path(config_path or Path.home() / ".janito" / "config.json")
31
+ if config_name:
32
+ self.config_path = (
33
+ Path.home() / ".janito" / "configs" / f"{config_name}.json"
34
+ )
35
+ else:
36
+ self.config_path = Path(
37
+ config_path or Path.home() / ".janito" / "config.json"
38
+ )
30
39
  self.defaults = dict(defaults) if defaults else {}
31
40
  self.file_config = {}
32
41
  self.runtime_overrides = dict(runtime_overrides) if runtime_overrides else {}
@@ -40,11 +49,15 @@ class ConfigManager:
40
49
  try:
41
50
  from janito.tools.permissions_parse import parse_permissions_string
42
51
  from janito.tools.permissions import set_global_allowed_permissions
52
+
43
53
  perms = parse_permissions_string(perm_str)
44
54
  set_global_allowed_permissions(perms)
45
55
  except Exception as e:
46
56
  print(f"Warning: Failed to apply tool_permissions from config: {e}")
47
57
 
58
+ # Load disabled tools from config - skip during startup to avoid circular imports
59
+ # This will be handled by the CLI when needed
60
+
48
61
  def _load_file_config(self):
49
62
  if self.config_path.exists():
50
63
  with open(self.config_path, "r", encoding="utf-8") as f: