janito 2.29.0__py3-none-any.whl → 2.31.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 (29) hide show
  1. janito/README.md +3 -3
  2. janito/agent/setup_agent.py +21 -35
  3. janito/agent/templates/profiles/system_prompt_template_Developer_with_Python_Tools.txt.j2 +6 -0
  4. janito/agent/templates/profiles/system_prompt_template_developer.txt.j2 +6 -0
  5. janito/agent/templates/profiles/system_prompt_template_market_analyst.txt.j2 +7 -1
  6. janito/agent/templates/profiles/system_prompt_template_model_conversation_without_tools_or_context.txt.j2 +7 -1
  7. janito/cli/chat_mode/session.py +19 -51
  8. janito/cli/cli_commands/list_plugins.py +99 -75
  9. janito/cli/cli_commands/show_system_prompt.py +8 -3
  10. janito/cli/core/runner.py +2 -2
  11. janito/cli/main_cli.py +9 -15
  12. janito/cli/prompt_core.py +2 -0
  13. janito/cli/single_shot_mode/handler.py +2 -0
  14. janito/llm/agent.py +6 -1
  15. janito/provider_registry.py +1 -1
  16. janito/providers/openai/provider.py +1 -1
  17. janito/tools/adapters/local/ask_user.py +3 -1
  18. janito/tools/adapters/local/fetch_url.py +20 -28
  19. janito/tools/adapters/local/replace_text_in_file.py +9 -3
  20. janito/tools/adapters/local/search_text/core.py +2 -2
  21. janito/tools/loop_protection_decorator.py +12 -16
  22. janito/tools/tools_adapter.py +18 -4
  23. {janito-2.29.0.dist-info → janito-2.31.0.dist-info}/METADATA +1 -1
  24. {janito-2.29.0.dist-info → janito-2.31.0.dist-info}/RECORD +28 -29
  25. janito/cli/chat_mode/session_profile_select.py +0 -182
  26. {janito-2.29.0.dist-info → janito-2.31.0.dist-info}/WHEEL +0 -0
  27. {janito-2.29.0.dist-info → janito-2.31.0.dist-info}/entry_points.txt +0 -0
  28. {janito-2.29.0.dist-info → janito-2.31.0.dist-info}/licenses/LICENSE +0 -0
  29. {janito-2.29.0.dist-info → janito-2.31.0.dist-info}/top_level.txt +0 -0
janito/README.md CHANGED
@@ -116,11 +116,11 @@ Janito includes powerful built-in tools for:
116
116
  - System commands
117
117
  - And more...
118
118
 
119
- ### Profiles and Roles
119
+ ### Profiles
120
120
  Use predefined system prompts:
121
121
  ```bash
122
- janito --profile developer "Create a REST API"
123
- janito --role python-expert "Optimize this algorithm"
122
+ janito --developer "Create a REST API" # Same as --profile developer
123
+ janito --market "Analyze market trends" # Same as --profile market-analyst
124
124
  ```
125
125
 
126
126
  ### Environment Variables
@@ -25,10 +25,14 @@ def _load_template_content(profile, templates_dir):
25
25
 
26
26
  Spaces in the profile name are converted to underscores to align with the file-naming convention (e.g. "Developer with Python Tools" ➜ "Developer_with_Python_Tools" (matches: system_prompt_template_Developer_with_Python_Tools.txt.j2)).
27
27
  """
28
- # Sanitize profile for filename resolution (convert whitespace to underscores)
29
- sanitized_profile = re.sub(r"\s+", "_", profile.strip()) if profile else profile
30
-
28
+ sanitized_profile = re.sub(r"\s+", "_", profile.strip())
31
29
  template_filename = f"system_prompt_template_{sanitized_profile}.txt.j2"
30
+
31
+ return _find_template_file(template_filename, templates_dir)
32
+
33
+
34
+ def _find_template_file(template_filename, templates_dir):
35
+ """Find and load template file from various locations."""
32
36
  template_path = templates_dir / template_filename
33
37
 
34
38
  # 1) Check local templates directory
@@ -96,39 +100,9 @@ def _load_template_content(profile, templates_dir):
96
100
  )
97
101
 
98
102
  raise FileNotFoundError(error_msg)
99
- # Replace spaces in profile name with underscores for filename resolution
100
- sanitized_profile = re.sub(r"\\s+", "_", profile.strip()) if profile else profile
101
- """
102
- Loads the template content for the given profile from the specified directory or package resources.
103
- If the profile template is not found in the default locations, tries to load from the user profiles directory ~/.janito/profiles.
104
- """
105
-
106
- # Sanitize profile name by replacing spaces with underscores to match filename conventions
107
- sanitized_profile = re.sub(r"\\s+", "_", profile.strip())
108
- template_filename = f"system_prompt_template_{sanitized_profile}.txt.j2"
109
- template_path = templates_dir / template_filename
110
- if template_path.exists():
111
- with open(template_path, "r", encoding="utf-8") as file:
112
- return file.read(), template_path
113
- # Try package import fallback
114
- try:
115
- with importlib.resources.files("janito.agent.templates.profiles").joinpath(
116
- template_filename
117
- ).open("r", encoding="utf-8") as file:
118
- return file.read(), template_path
119
- except (FileNotFoundError, ModuleNotFoundError, AttributeError):
120
- # Try user profiles directory
121
- user_profiles_dir = Path(os.path.expanduser("~/.janito/profiles"))
122
- user_template_path = user_profiles_dir / profile
123
- if user_template_path.exists():
124
- with open(user_template_path, "r", encoding="utf-8") as file:
125
- return file.read(), user_template_path
126
- raise FileNotFoundError(
127
- f"[janito] Could not find profile-specific template '{template_filename}' in {template_path} nor in janito.agent.templates.profiles package nor in user profiles directory {user_template_path}."
128
- )
129
103
 
130
104
 
131
- def _prepare_template_context(role, profile, allowed_permissions):
105
+ def _prepare_template_context(role, profile, allowed_permissions, args=None):
132
106
  """
133
107
  Prepares the context dictionary for Jinja2 template rendering.
134
108
  """
@@ -148,6 +122,11 @@ def _prepare_template_context(role, profile, allowed_permissions):
148
122
  perm_str += "x"
149
123
  allowed_permissions = perm_str or None
150
124
  context["allowed_permissions"] = allowed_permissions
125
+
126
+ # Add emoji flag for system prompt
127
+ context["emoji_enabled"] = (
128
+ getattr(args, "emoji", False) if "args" in locals() else False
129
+ )
151
130
  # Inject platform info if execute permission is present
152
131
  if allowed_permissions and "x" in allowed_permissions:
153
132
  pd = PlatformDiscovery()
@@ -171,6 +150,11 @@ def _prepare_template_context(role, profile, allowed_permissions):
171
150
  else:
172
151
  context["allowed_sites_info"] = f"Restricted to: {', '.join(allowed_sites)}"
173
152
 
153
+ # Add emoji flag for system prompt
154
+ context["emoji_enabled"] = (
155
+ getattr(args, "emoji", False) if "args" in locals() else False
156
+ )
157
+
174
158
  return context
175
159
 
176
160
 
@@ -267,7 +251,9 @@ def setup_agent(
267
251
  template_content, template_path = _load_template_content(profile, templates_dir)
268
252
 
269
253
  template = Template(template_content)
270
- context = _prepare_template_context(role, profile, allowed_permissions)
254
+ context = _prepare_template_context(
255
+ role, profile, allowed_permissions, locals().get("args")
256
+ )
271
257
 
272
258
  # Debug output if requested
273
259
  debug_flag = False
@@ -88,4 +88,10 @@ You are: {{ role | default('developer') }}
88
88
  {# Trying to prevent surrogates generation, found this frequently in gpt4.1/windows #}
89
89
  - While writing code, if you need an emoji or special Unicode character in a string, then insert the actual character (e.g., 📖) directly instead of using surrogate pairs or escape sequences.
90
90
  {% endif %}
91
+ {% if emoji_enabled %}
92
+ ## Emoji Usage
93
+ - Feel free to use emojis in your responses to make them more engaging and expressive 🎉
94
+ - Use appropriate emojis to enhance communication: ✅ for success, ⚠️ for warnings, 🔄 for progress, etc.
95
+ - Emojis should complement the message, not replace clear explanations
96
+ {% endif %}
91
97
 
@@ -78,4 +78,10 @@ You are: {{ role | default('software developer') }}
78
78
  {# Trying to prevent surrogates generation, found this frequently in gpt4.1/windows #}
79
79
  - While writing code, if you need an emoji or special Unicode character in a string, then insert the actual character (e.g., 📖) directly instead of using surrogate pairs or escape sequences.
80
80
  {% endif %}
81
+ {% if emoji_enabled %}
82
+ ## Emoji Usage
83
+ - Feel free to use emojis in your responses to make them more engaging and expressive 🎉
84
+ - Use appropriate emojis to enhance communication: ✅ for success, ⚠️ for warnings, 🔄 for progress, etc.
85
+ - Emojis should complement the message, not replace clear explanations
86
+ {% endif %}
81
87
 
@@ -107,4 +107,10 @@ When asked about specific stocks or market movements, provide detailed analysis
107
107
  {% endfor %}
108
108
  {% endif %}
109
109
 
110
- ## Guidelines
110
+ ## Guidelines
111
+ {% if emoji_enabled %}
112
+ ## Emoji Usage
113
+ - Feel free to use emojis in your responses to make them more engaging and expressive 📊
114
+ - Use appropriate emojis to enhance communication: 📈 for uptrends, 📉 for downtrends, 💰 for profits, ⚠️ for warnings, etc.
115
+ - Emojis should complement the message, not replace clear financial analysis
116
+ {% endif %}
@@ -50,4 +50,10 @@ You are: {{ role | default('helpful assistant') }}
50
50
  ## Guidelines
51
51
  - Provide helpful, accurate, and concise responses
52
52
  - Ask clarifying questions when needed
53
- - Maintain a friendly and professional tone
53
+ - Maintain a friendly and professional tone
54
+ {% if emoji_enabled %}
55
+ ## Emoji Usage
56
+ - Feel free to use emojis in your responses to make them more engaging and expressive 😊
57
+ - Use appropriate emojis to enhance communication: ✅ for confirmations, ❓ for questions, 💡 for ideas, etc.
58
+ - Emojis should complement the message, not replace clear communication
59
+ {% endif %}
@@ -119,9 +119,7 @@ class ChatSession:
119
119
  profile_system_prompt = None
120
120
  no_tools_mode = False
121
121
 
122
- profile = self._determine_profile(
123
- profile, role_arg, python_profile, market_profile
124
- )
122
+ profile = self._determine_profile(profile, python_profile, market_profile)
125
123
 
126
124
  if (
127
125
  profile is None
@@ -136,31 +134,8 @@ class ChatSession:
136
134
  if skip_profile_selection:
137
135
  profile = "Developer with Python Tools" # Default for non-interactive commands
138
136
  else:
139
- try:
140
- from janito.cli.chat_mode.session_profile_select import (
141
- select_profile,
142
- )
143
-
144
- result = select_profile()
145
- if isinstance(result, dict):
146
- profile = result.get("profile")
147
- profile_system_prompt = result.get("profile_system_prompt")
148
- no_tools_mode = result.get("no_tools_mode", False)
149
- elif isinstance(result, str) and result.startswith("role:"):
150
- role = result[len("role:") :].strip()
151
- profile = "Developer with Python Tools"
152
- else:
153
- profile = (
154
- "Developer with Python Tools"
155
- if result == "Developer"
156
- else result
157
- )
158
- except ImportError:
159
- profile = "Raw Model Session (no tools, no context)"
160
- if role_arg is not None:
161
- role = role_arg
162
- if profile is None:
163
137
  profile = "Developer with Python Tools"
138
+
164
139
  return profile, role, profile_system_prompt, no_tools_mode
165
140
 
166
141
  def _create_conversation_history(self):
@@ -308,25 +283,6 @@ class ChatSession:
308
283
  )
309
284
  )
310
285
 
311
- for candidate in candidates:
312
- try:
313
- if not candidate:
314
- continue
315
- parsed = urlparse(str(candidate))
316
- host = parsed.netloc or parsed.path
317
- if host:
318
- backend_hostname = host
319
- break
320
- except Exception:
321
- backend_hostname = str(candidate)
322
- break
323
-
324
- self.console.print(
325
- Rule(
326
- f"[bold blue]Model: {model_name} ({provider_name}) | Backend: {backend_hostname}[/bold blue]"
327
- )
328
- )
329
-
330
286
  self._prompt_handler.run_prompt(cmd_input)
331
287
  end_time = time.time()
332
288
  elapsed = end_time - start_time
@@ -337,6 +293,8 @@ class ChatSession:
337
293
  print_token_message_summary(
338
294
  self.console, self.msg_count, usage, elapsed=elapsed
339
295
  )
296
+ # Send terminal bell character to trigger TUI bell after printing token summary
297
+ print("\a", end="", flush=True)
340
298
  if final_event and hasattr(final_event, "metadata"):
341
299
  exit_reason = (
342
300
  final_event.metadata.get("exit_reason")
@@ -356,18 +314,18 @@ class ChatSession:
356
314
  def _extract_args(self, args):
357
315
  """Extract profile and role arguments from args."""
358
316
  profile = getattr(args, "profile", None) if args is not None else None
359
- role_arg = getattr(args, "role", None) if args is not None else None
317
+ role_arg = None
360
318
  python_profile = (
361
319
  getattr(args, "developer", False) if args is not None else False
362
320
  )
363
321
  market_profile = getattr(args, "market", False) if args is not None else False
364
322
  return profile, role_arg, python_profile, market_profile
365
323
 
366
- def _determine_profile(self, profile, role_arg, python_profile, market_profile):
324
+ def _determine_profile(self, profile, python_profile, market_profile):
367
325
  """Determine the profile based on flags and arguments."""
368
- if python_profile and profile is None and role_arg is None:
326
+ if python_profile and profile is None:
369
327
  return "Developer with Python Tools"
370
- if market_profile and profile is None and role_arg is None:
328
+ if market_profile and profile is None:
371
329
  return "Market Analyst"
372
330
  return profile
373
331
 
@@ -517,9 +475,19 @@ class ChatSession:
517
475
  else:
518
476
  duration_str = f"{session_duration/3600:.1f}h"
519
477
 
478
+ # Format tokens in k/m/t as appropriate
479
+ if total_tokens >= 1_000_000_000:
480
+ token_str = f"{total_tokens/1_000_000_000:.1f}t"
481
+ elif total_tokens >= 1_000_000:
482
+ token_str = f"{total_tokens/1_000_000:.1f}m"
483
+ elif total_tokens >= 1_000:
484
+ token_str = f"{total_tokens/1_000:.1f}k"
485
+ else:
486
+ token_str = f"{total_tokens}"
487
+
520
488
  self.console.print(f"[bold yellow]Session completed![/bold yellow]")
521
489
  self.console.print(
522
- f"[dim]Session time: {duration_str} | Total tokens: {total_tokens:,}[/dim]"
490
+ f"[dim]Session time: {duration_str} | Total tokens: {token_str}[/dim]"
523
491
  )
524
492
  self.console.print("[bold yellow]Goodbye![/bold yellow]")
525
493
 
@@ -14,80 +14,104 @@ def handle_list_plugins(args: argparse.Namespace) -> None:
14
14
  """List plugins command handler."""
15
15
 
16
16
  if getattr(args, "list_plugins_available", False):
17
- # List available plugins
18
- available = list_available_plugins()
19
- builtin_plugins = BuiltinPluginRegistry.list_builtin_plugins()
20
-
21
- if available or builtin_plugins:
22
- print("Available plugins:")
23
-
24
- # Show builtin plugins first
25
- if builtin_plugins:
26
- print(" Builtin plugins:")
27
- for plugin in builtin_plugins:
28
- print(f" - {plugin} [BUILTIN]")
29
-
30
- # Show other available plugins
31
- other_plugins = [p for p in available if p not in builtin_plugins]
32
- if other_plugins:
33
- print(" External plugins:")
34
- for plugin in other_plugins:
35
- print(f" - {plugin}")
36
- else:
37
- print("No plugins found in search paths")
38
- print("Search paths:")
39
- print(f" - {os.getcwd()}/plugins")
40
- print(f" - {os.path.expanduser('~')}/.janito/plugins")
17
+ _list_available_plugins()
41
18
  elif getattr(args, "list_resources", False):
42
- # List all resources from loaded plugins
43
- manager = PluginManager()
44
- all_resources = manager.list_all_resources()
45
-
46
- if all_resources:
47
- print("Plugin Resources:")
48
- for plugin_name, resources in all_resources.items():
49
- metadata = manager.get_plugin_metadata(plugin_name)
50
- print(
51
- f"\n{plugin_name} v{metadata.version if metadata else 'unknown'}:"
52
- )
53
-
54
- # Group resources by type
55
- tools = [r for r in resources if r["type"] == "tool"]
56
- commands = [r for r in resources if r["type"] == "command"]
57
- configs = [r for r in resources if r["type"] == "config"]
58
-
59
- if tools:
60
- print(" Tools:")
61
- for tool in tools:
62
- print(f" - {tool['name']}: {tool['description']}")
63
-
64
- if commands:
65
- print(" Commands:")
66
- for cmd in commands:
67
- print(f" - {cmd['name']}: {cmd['description']}")
68
-
69
- if configs:
70
- print(" Configuration:")
71
- for config in configs:
72
- print(f" - {config['name']}: {config['description']}")
73
- else:
74
- print("No plugins loaded")
19
+ _list_plugin_resources()
75
20
  else:
76
- # List loaded plugins
77
- manager = PluginManager()
78
- loaded = manager.list_plugins()
79
-
80
- if loaded:
81
- print("Loaded plugins:")
82
- for plugin_name in loaded:
83
- metadata = manager.get_plugin_metadata(plugin_name)
84
- is_builtin = BuiltinPluginRegistry.is_builtin(plugin_name)
85
- if metadata:
86
- builtin_tag = " [BUILTIN]" if is_builtin else ""
87
- print(f" - {metadata.name} v{metadata.version}{builtin_tag}")
88
- print(f" {metadata.description}")
89
- if metadata.author:
90
- print(f" Author: {metadata.author}")
91
- print()
92
- else:
93
- print("No plugins loaded")
21
+ _list_loaded_plugins()
22
+
23
+
24
+ def _list_available_plugins():
25
+ """List available plugins."""
26
+ available = list_available_plugins()
27
+ builtin_plugins = BuiltinPluginRegistry.list_builtin_plugins()
28
+
29
+ if available or builtin_plugins:
30
+ print("Available plugins:")
31
+ _print_builtin_plugins(builtin_plugins)
32
+ _print_external_plugins(available, builtin_plugins)
33
+ else:
34
+ print("No plugins found in search paths")
35
+ print("Search paths:")
36
+ print(f" - {os.getcwd()}/plugins")
37
+ print(f" - {os.path.expanduser('~')}/.janito/plugins")
38
+
39
+
40
+ def _print_builtin_plugins(builtin_plugins):
41
+ """Print builtin plugins."""
42
+ if builtin_plugins:
43
+ print(" Builtin plugins:")
44
+ for plugin in builtin_plugins:
45
+ print(f" - {plugin} [BUILTIN]")
46
+
47
+
48
+ def _print_external_plugins(available, builtin_plugins):
49
+ """Print external plugins."""
50
+ other_plugins = [p for p in available if p not in builtin_plugins]
51
+ if other_plugins:
52
+ print(" External plugins:")
53
+ for plugin in other_plugins:
54
+ print(f" - {plugin}")
55
+
56
+
57
+ def _list_plugin_resources():
58
+ """List all resources from loaded plugins."""
59
+ manager = PluginManager()
60
+ all_resources = manager.list_all_resources()
61
+
62
+ if all_resources:
63
+ print("Plugin Resources:")
64
+ for plugin_name, resources in all_resources.items():
65
+ metadata = manager.get_plugin_metadata(plugin_name)
66
+ print(f"\n{plugin_name} v{metadata.version if metadata else 'unknown'}:")
67
+ _print_resources_by_type(resources)
68
+ else:
69
+ print("No plugins loaded")
70
+
71
+
72
+ def _print_resources_by_type(resources):
73
+ """Print resources grouped by type."""
74
+ tools = [r for r in resources if r["type"] == "tool"]
75
+ commands = [r for r in resources if r["type"] == "command"]
76
+ configs = [r for r in resources if r["type"] == "config"]
77
+
78
+ if tools:
79
+ print(" Tools:")
80
+ for tool in tools:
81
+ print(f" - {tool['name']}: {tool['description']}")
82
+
83
+ if commands:
84
+ print(" Commands:")
85
+ for cmd in commands:
86
+ print(f" - {cmd['name']}: {cmd['description']}")
87
+
88
+ if configs:
89
+ print(" Configuration:")
90
+ for config in configs:
91
+ print(f" - {config['name']}: {config['description']}")
92
+
93
+
94
+ def _list_loaded_plugins():
95
+ """List loaded plugins."""
96
+ manager = PluginManager()
97
+ loaded = manager.list_plugins()
98
+
99
+ if loaded:
100
+ print("Loaded plugins:")
101
+ for plugin_name in loaded:
102
+ _print_plugin_details(manager, plugin_name)
103
+ else:
104
+ print("No plugins loaded")
105
+
106
+
107
+ def _print_plugin_details(manager, plugin_name):
108
+ """Print details for a loaded plugin."""
109
+ metadata = manager.get_plugin_metadata(plugin_name)
110
+ is_builtin = BuiltinPluginRegistry.is_builtin(plugin_name)
111
+ if metadata:
112
+ builtin_tag = " [BUILTIN]" if is_builtin else ""
113
+ print(f" - {metadata.name} v{metadata.version}{builtin_tag}")
114
+ print(f" {metadata.description}")
115
+ if metadata.author:
116
+ print(f" Author: {metadata.author}")
117
+ print()
@@ -35,6 +35,7 @@ def _prepare_context(args, agent_role, allowed_permissions):
35
35
  context["role"] = agent_role or "developer"
36
36
  context["profile"] = getattr(args, "profile", None)
37
37
  context["allowed_permissions"] = allowed_permissions
38
+ context["emoji_enabled"] = getattr(args, "emoji", False)
38
39
  if allowed_permissions and "x" in allowed_permissions:
39
40
  pd = PlatformDiscovery()
40
41
  context["platform"] = pd.get_platform_name()
@@ -122,6 +123,10 @@ def handle_show_system_prompt(args):
122
123
  if profile is None and getattr(args, "market", False):
123
124
  profile = "Market Analyst"
124
125
 
126
+ # Handle --developer flag mapping to Developer With Python Tools profile
127
+ if profile is None and getattr(args, "developer", False):
128
+ profile = "Developer With Python Tools"
129
+
125
130
  if not profile:
126
131
  print(
127
132
  "[janito] No profile specified. The main agent runs without a system prompt template.\n"
@@ -152,9 +157,9 @@ def handle_show_system_prompt(args):
152
157
  system_prompt = template.render(**context)
153
158
  system_prompt = re.sub(r"\n{3,}", "\n\n", system_prompt)
154
159
 
155
- print(
156
- f"\n--- System Prompt (resolved, profile: {getattr(args, 'profile', 'main')}) ---\n"
157
- )
160
+ # Use the actual profile name for display, not the resolved value
161
+ display_profile = profile or "main"
162
+ print(f"\n--- System Prompt (resolved, profile: {display_profile}) ---\n")
158
163
  print(system_prompt)
159
164
  print("\n-------------------------------\n")
160
165
  if agent_role:
janito/cli/core/runner.py CHANGED
@@ -109,8 +109,8 @@ def prepare_llm_driver_config(args, modifiers):
109
109
  llm_driver_config = LLMDriverConfig(**driver_config_data)
110
110
  if getattr(llm_driver_config, "verbose_api", None):
111
111
  pass
112
- # If both --role and --profile are provided, --role takes precedence for agent_role
113
- agent_role = modifiers.get("role") or modifiers.get("profile") or "developer"
112
+
113
+ agent_role = modifiers.get("profile") or "developer"
114
114
  return provider, llm_driver_config, agent_role
115
115
 
116
116
 
janito/cli/main_cli.py CHANGED
@@ -51,14 +51,6 @@ definition = [
51
51
  "help": "Start with the Market Analyst profile (equivalent to --profile 'Market Analyst')",
52
52
  },
53
53
  ),
54
- (
55
- ["--role"],
56
- {
57
- "metavar": "ROLE",
58
- "help": "Select the developer role name (overrides profile, e.g. 'python-expert').",
59
- "default": None,
60
- },
61
- ),
62
54
  (
63
55
  ["-W", "--workdir"],
64
56
  {
@@ -205,6 +197,13 @@ definition = [
205
197
  "help": "Set the reasoning effort for models that support it (low, medium, high, none)",
206
198
  },
207
199
  ),
200
+ (
201
+ ["--emoji"],
202
+ {
203
+ "action": "store_true",
204
+ "help": "Enable emoji usage in responses to make output more engaging and expressive",
205
+ },
206
+ ),
208
207
  (["user_prompt"], {"nargs": argparse.REMAINDER, "help": "Prompt to submit"}),
209
208
  (
210
209
  ["-e", "--event-log"],
@@ -244,7 +243,6 @@ definition = [
244
243
  MODIFIER_KEYS = [
245
244
  "provider",
246
245
  "model",
247
- "role",
248
246
  "profile",
249
247
  "developer",
250
248
  "market",
@@ -257,6 +255,7 @@ MODIFIER_KEYS = [
257
255
  "exec",
258
256
  "read",
259
257
  "write",
258
+ "emoji",
260
259
  ]
261
260
  SETTER_KEYS = ["set", "set_provider", "set_api_key", "unset"]
262
261
  GETTER_KEYS = [
@@ -372,9 +371,7 @@ class JanitoCLI:
372
371
  for k in MODIFIER_KEYS
373
372
  if getattr(self.args, k, None) is not None
374
373
  }
375
- # If --role is provided, override role in modifiers
376
- if getattr(self.args, "role", None):
377
- modifiers["role"] = getattr(self.args, "role")
374
+
378
375
  return modifiers
379
376
 
380
377
  def classify(self):
@@ -420,9 +417,6 @@ class JanitoCLI:
420
417
  self.args.exec = True
421
418
  # Remove the /rwx prefix from the prompt
422
419
  self.args.user_prompt = self.args.user_prompt[1:]
423
- elif self.args.user_prompt and self.args.user_prompt[0].startswith("/"):
424
- # Skip LLM processing for other commands that start with /
425
- return
426
420
 
427
421
  # If running in single shot mode and --profile is not provided, default to 'developer' profile
428
422
  # Skip profile selection for list commands that don't need it
janito/cli/prompt_core.py CHANGED
@@ -216,6 +216,8 @@ class PromptHandler:
216
216
  if on_event and final_event is not None:
217
217
  on_event(final_event)
218
218
  global_event_bus.publish(final_event)
219
+ # Terminal bell moved to token summary printing in session.py and handler.py
220
+ pass # print('\a', end='', flush=True)
219
221
  except KeyboardInterrupt:
220
222
  # Capture user interrupt / cancellation
221
223
  self.console.print("[red]Interrupted by the user.[/red]")
@@ -125,6 +125,8 @@ class PromptHandler:
125
125
  print_token_message_summary(
126
126
  shared_console, msg_count=1, usage=usage, elapsed=elapsed
127
127
  )
128
+ # Send terminal bell character to trigger TUI bell after printing token summary
129
+ print("\a", end="", flush=True)
128
130
  self._cleanup_driver_and_console()
129
131
 
130
132
  def _cleanup_driver_and_console(self):
janito/llm/agent.py CHANGED
@@ -244,7 +244,12 @@ class LLMAgent:
244
244
  f"[agent] [DEBUG] Tool call detected: {getattr(part, 'name', repr(part))} with arguments: {getattr(part, 'arguments', None)}"
245
245
  )
246
246
  tool_calls.append(part)
247
- result = self.tools_adapter.execute_function_call_message_part(part)
247
+ try:
248
+ result = self.tools_adapter.execute_function_call_message_part(part)
249
+ except Exception as e:
250
+ # Catch any exception during tool execution and return as string
251
+ # instead of letting it propagate to the user
252
+ result = str(e)
248
253
  tool_results.append(result)
249
254
  if tool_calls:
250
255
  # Prepare tool_calls message for assistant
@@ -41,7 +41,7 @@ class ProviderRegistry:
41
41
  rows.append(info[:3])
42
42
 
43
43
  # Group providers by openness (open-source first, then proprietary)
44
- open_providers = {"cerebras", "deepseek", "alibaba", "moonshot", "zai"}
44
+ open_providers = {"cerebras", "deepseek", "alibaba", "moonshotai", "zai"}
45
45
 
46
46
  def sort_key(row):
47
47
  provider_name = row[0]
@@ -18,7 +18,7 @@ class OpenAIProvider(LLMProvider):
18
18
  MAINTAINER = "João Pinto <janito@ikignosis.org>"
19
19
  MODEL_SPECS = MODEL_SPECS
20
20
  DEFAULT_MODEL = (
21
- "gpt-5" # Options: gpt-4.1, gpt-4o, o3-mini, o4-mini, gpt-5, gpt-5-nano
21
+ "gpt-4.1" # Options: gpt-4.1, gpt-4o, o3-mini, o4-mini, gpt-5, gpt-5-nano
22
22
  )
23
23
 
24
24
  def __init__(
@@ -5,6 +5,7 @@ from janito.tools.loop_protection_decorator import protect_against_loops
5
5
  from rich import print as rich_print
6
6
  from janito.i18n import tr
7
7
  from rich.panel import Panel
8
+ from rich.markdown import Markdown
8
9
  from prompt_toolkit import PromptSession
9
10
  from prompt_toolkit.key_binding import KeyBindings
10
11
  from prompt_toolkit.enums import EditingMode
@@ -36,7 +37,7 @@ class AskUserTool(ToolBase):
36
37
  def run(self, question: str) -> str:
37
38
 
38
39
  print() # Print an empty line before the question panel
39
- rich_print(Panel.fit(question, title=tr("Question"), style="cyan"))
40
+ rich_print(Panel.fit(Markdown(question), title=tr("Question"), style="cyan"))
40
41
 
41
42
  bindings = KeyBindings()
42
43
  mode = {"multiline": False}
@@ -107,4 +108,5 @@ class AskUserTool(ToolBase):
107
108
  rich_print(
108
109
  "[yellow]Warning: Some characters in your input were not valid UTF-8 and have been replaced.[/yellow]"
109
110
  )
111
+ print("\a", end="", flush=True) # Print bell character
110
112
  return sanitized
@@ -196,25 +196,15 @@ class FetchUrlTool(ToolBase):
196
196
  whitelist_manager = get_url_whitelist_manager()
197
197
 
198
198
  if not whitelist_manager.is_url_allowed(url):
199
- error_message = tr(
200
- "Warning: URL blocked by whitelist: {url}",
201
- url=url,
202
- )
199
+ error_message = tr("Blocked")
203
200
  self.report_error(
204
- tr(
205
- "❗ URL blocked by whitelist: {url}",
206
- url=url,
207
- ),
201
+ tr("❗ Blocked"),
208
202
  ReportAction.READ,
209
203
  )
210
204
  return error_message
211
205
 
212
206
  # Check session cache first
213
207
  if url in self.session_cache:
214
- self.report_warning(
215
- tr("ℹ️ Using session cache"),
216
- ReportAction.READ,
217
- )
218
208
  return self.session_cache[url]
219
209
 
220
210
  # Check persistent cache for known errors
@@ -258,9 +248,8 @@ class FetchUrlTool(ToolBase):
258
248
  status_code = http_err.response.status_code if http_err.response else None
259
249
  if status_code and 400 <= status_code < 500:
260
250
  error_message = tr(
261
- "Warning: HTTP {status_code} error for URL: {url}",
251
+ "HTTP {status_code}",
262
252
  status_code=status_code,
263
- url=url,
264
253
  )
265
254
  # Cache 403 and 404 errors
266
255
  if status_code in [403, 404]:
@@ -268,9 +257,8 @@ class FetchUrlTool(ToolBase):
268
257
 
269
258
  self.report_error(
270
259
  tr(
271
- "❗ HTTP {status_code} error for URL: {url}",
260
+ "❗ HTTP {status_code}",
272
261
  status_code=status_code,
273
- url=url,
274
262
  ),
275
263
  ReportAction.READ,
276
264
  )
@@ -278,25 +266,21 @@ class FetchUrlTool(ToolBase):
278
266
  else:
279
267
  self.report_error(
280
268
  tr(
281
- "❗ HTTP error for URL: {url}: {err}",
282
- url=url,
283
- err=str(http_err),
269
+ "❗ HTTP {status_code}",
270
+ status_code=status_code or "Error",
284
271
  ),
285
272
  ReportAction.READ,
286
273
  )
287
274
  return tr(
288
- "Warning: HTTP error for URL: {url}: {err}",
289
- url=url,
290
- err=str(http_err),
275
+ "HTTP {status_code}",
276
+ status_code=status_code or "Error",
291
277
  )
292
278
  except Exception as err:
293
279
  self.report_error(
294
- tr("❗ Error fetching URL: {url}: {err}", url=url, err=str(err)),
280
+ tr("❗ Error"),
295
281
  ReportAction.READ,
296
282
  )
297
- return tr(
298
- "Warning: Error fetching URL: {url}: {err}", url=url, err=str(err)
299
- )
283
+ return tr("Error")
300
284
 
301
285
  def _extract_and_clean_text(self, html_content: str) -> str:
302
286
  """Extract and clean text from HTML content."""
@@ -370,7 +354,11 @@ class FetchUrlTool(ToolBase):
370
354
  cookies=cookies,
371
355
  follow_redirects=follow_redirects,
372
356
  )
373
- if html_content.startswith("Warning:"):
357
+ if (
358
+ html_content.startswith("HTTP ")
359
+ or html_content == "Error"
360
+ or html_content == "Blocked"
361
+ ):
374
362
  return html_content
375
363
 
376
364
  try:
@@ -399,7 +387,11 @@ class FetchUrlTool(ToolBase):
399
387
  cookies=cookies,
400
388
  follow_redirects=follow_redirects,
401
389
  )
402
- if html_content.startswith("Warning:"):
390
+ if (
391
+ html_content.startswith("HTTP ")
392
+ or html_content == "Error"
393
+ or html_content == "Blocked"
394
+ ):
403
395
  return html_content
404
396
 
405
397
  # Extract and clean text
@@ -159,16 +159,22 @@ class ReplaceTextInFileTool(ToolBase):
159
159
  )
160
160
  return warning, concise_warning
161
161
 
162
- def _report_success(self, match_lines):
162
+ def _report_success(self, match_lines, line_delta_str=""):
163
163
  """Report success with line numbers where replacements occurred."""
164
164
  if match_lines:
165
165
  lines_str = ", ".join(str(line_no) for line_no in match_lines)
166
166
  self.report_success(
167
- tr(" ✅ replaced at {lines_str}", lines_str=lines_str),
167
+ tr(
168
+ " ✅ replaced at {lines_str}{delta}",
169
+ lines_str=lines_str,
170
+ delta=line_delta_str,
171
+ ),
168
172
  ReportAction.CREATE,
169
173
  )
170
174
  else:
171
- self.report_success(tr(" ✅ replaced (lines unknown)"), ReportAction.CREATE)
175
+ self.report_success(
176
+ tr(" ✅ replaced{delta}", delta=line_delta_str), ReportAction.CREATE
177
+ )
172
178
 
173
179
  def _get_line_delta_str(self, content, new_content):
174
180
  """Return a string describing the net line change after replacement."""
@@ -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(
@@ -144,7 +144,7 @@ class SearchTextTool(ToolBase):
144
144
  file_word_max = file_word + (" (max)" if dir_limit_reached else "")
145
145
  self.report_success(
146
146
  tr(
147
- " ✅ {count} {file_word} from {num_files} {file_label}",
147
+ " ✅ {count} {file_word}/{num_files} {file_label}",
148
148
  count=count,
149
149
  file_word=file_word_max,
150
150
  num_files=num_files,
@@ -119,22 +119,18 @@ def protect_against_loops(
119
119
  current_time - timestamp <= time_window
120
120
  for timestamp in _decorator_call_tracker[op_name]
121
121
  ):
122
- # Define the error reporting function
123
- def _report_error_and_raise(args, operation_type):
124
- # Get the tool instance to access report_error method if available
125
- tool_instance = args[0] if args else None
126
- error_msg = f"Loop protection: Too many {operation_type} operations in a short time period ({max_calls} calls in {time_window}s)"
127
-
128
- # Try to report the error through the tool's reporting mechanism
129
- if hasattr(tool_instance, "report_error"):
130
- try:
131
- tool_instance.report_error(error_msg)
132
- except Exception:
133
- pass # If reporting fails, we still raise the error
134
-
135
- raise RuntimeError(error_msg)
136
-
137
- _report_error_and_raise(args, op_name)
122
+ # Return loop protection message as string instead of raising exception
123
+ error_msg = f"Loop protection: Too many {op_name} operations in a short time period ({max_calls} calls in {time_window}s). Please try a different approach or wait before retrying."
124
+
125
+ # Try to report the error through the tool's reporting mechanism
126
+ tool_instance = args[0] if args else None
127
+ if hasattr(tool_instance, "report_error"):
128
+ try:
129
+ tool_instance.report_error(error_msg)
130
+ except Exception:
131
+ pass # If reporting fails, we still return the message
132
+
133
+ return error_msg
138
134
 
139
135
  # Record this call
140
136
  if op_name not in _decorator_call_tracker:
@@ -460,9 +460,9 @@ class ToolsAdapterBase:
460
460
  raise ToolCallException(tool_name, error_msg, arguments=arguments)
461
461
 
462
462
  def _handle_execution_error(self, tool_name, request_id, exception, arguments):
463
- # Check if this is a loop protection error that should be returned as a string
463
+ # Check if this is a loop protection error that should trigger a new strategy
464
464
  if isinstance(exception, RuntimeError) and "Loop protection:" in str(exception):
465
- error_msg = str(exception) # Return the loop protection message directly
465
+ error_msg = str(exception)
466
466
  if self._event_bus:
467
467
  self._event_bus.publish(
468
468
  ToolCallError(
@@ -473,8 +473,22 @@ class ToolsAdapterBase:
473
473
  arguments=arguments,
474
474
  )
475
475
  )
476
- # Return the error message instead of raising an exception
477
- return error_msg
476
+ # Return the loop protection message as string to trigger new strategy
477
+ return f"Loop protection triggered - requesting new strategy: {error_msg}"
478
+
479
+ # Check if this is a string return from loop protection (new behavior)
480
+ if isinstance(exception, str) and "Loop protection:" in exception:
481
+ error_msg = str(exception)
482
+ if self._event_bus:
483
+ self._event_bus.publish(
484
+ ToolCallError(
485
+ tool_name=tool_name,
486
+ request_id=request_id,
487
+ error=error_msg,
488
+ arguments=arguments,
489
+ )
490
+ )
491
+ return f"Loop protection triggered - requesting new strategy: {error_msg}"
478
492
 
479
493
  error_msg = f"Exception during execution of tool '{tool_name}': {exception}"
480
494
  if self._event_bus:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: janito
3
- Version: 2.29.0
3
+ Version: 2.31.0
4
4
  Summary: A new Python package called janito.
5
5
  Author-email: João Pinto <janito@ikignosis.org>
6
6
  Project-URL: Homepage, https://github.com/ikignosis/janito
@@ -1,4 +1,4 @@
1
- janito/README.md,sha256=G84OZfHInp0F84_01mmcTZ_b1T-qPBUFMMHUJeRSPcY,4701
1
+ janito/README.md,sha256=Kd4GcEYIt04520J2AIMCZbp1enAGRzlLswCfyi1g5AY,4737
2
2
  janito/__init__.py,sha256=a0pFui3A_AfWJiUfg93yE-Vf4868bqG3y9yg2fkTIuY,244
3
3
  janito/__main__.py,sha256=lPQ8kAyYfyeS1KopmJ8EVY5g1YswlIqCS615mM_B_rM,70
4
4
  janito/_version.py,sha256=PtAVr2K9fOS5sv6aXzmcb7UaR5NLGMFOofL7Ndjh75o,2344
@@ -16,21 +16,21 @@ janito/perf_singleton.py,sha256=g1h0Sdf4ydzegeEpJlMhQt4H0GQZ2hryXrdYOTL-b30,113
16
16
  janito/performance_collector.py,sha256=RYu4av16Trj3RljJZ8-2Gbn1KlGdJUosrcVFYtwviNI,6285
17
17
  janito/platform_discovery.py,sha256=JN3kC7hkxdvuj-AyrJTlbbDJjtNHke3fdlZDqGi_uz0,4621
18
18
  janito/provider_config.py,sha256=acn2FEgWsEIyi2AxZiuCLoP2rXDd-nXcP5VB4CZHaeE,3189
19
- janito/provider_registry.py,sha256=IRNB35Cjn4PSXMWOxKBjPg0DfUEOoL4vh63OSPxhMtk,6925
19
+ janito/provider_registry.py,sha256=VMxwIatPxrkCo-racXm9UxMd37iCyEdi_viotqaspXo,6927
20
20
  janito/report_events.py,sha256=q4OR_jTZNfcqaQF_fzTjgqo6_VlUIxSGWfhpT4nJWcw,938
21
21
  janito/shell.bak.zip,sha256=hznHbmgfkAkjuQDJ3w73XPQh05yrtUZQxLmtGbanbYU,22
22
22
  janito/utils.py,sha256=eXSsMgM69YyzahgCNrJQLcEbB8ssLI1MQqaa20ONxbE,376
23
- janito/agent/setup_agent.py,sha256=kE60LMVBoskHE3l6cViPEUNynF2a9irA_y8XwsezKcc,13570
24
- janito/agent/templates/profiles/system_prompt_template_Developer_with_Python_Tools.txt.j2,sha256=R-LxBq4wtsOeexZoQSYrdfGNdp5rdzHCP5Lt0pbejzE,3600
25
- janito/agent/templates/profiles/system_prompt_template_developer.txt.j2,sha256=vO0RGuj_tkL-o_rrrd0H7iD7bVe9NWGcyBR4h0T81hY,3283
26
- janito/agent/templates/profiles/system_prompt_template_market_analyst.txt.j2,sha256=A4WxfDdcGA1g1ndUP3Swu5lQSAMl7nP7W7MVPgyKcZM,3764
27
- janito/agent/templates/profiles/system_prompt_template_model_conversation_without_tools_or_context.txt.j2,sha256=K7sYNF0Yh7DchdqyhhvCLpbqiCJ36gX1M67XRZMw20c,1256
23
+ janito/agent/setup_agent.py,sha256=Qo9fm9O5IUd65ao4KODOMd6f7baw_mEiluj1pBNxrIg,12228
24
+ janito/agent/templates/profiles/system_prompt_template_Developer_with_Python_Tools.txt.j2,sha256=Q6p57GakGJv4damUITO0iq8rwfhPxVlek6y3I7nnkK8,3931
25
+ janito/agent/templates/profiles/system_prompt_template_developer.txt.j2,sha256=lG6ihPRoGqJ0ho2jb1sgBPs9itwpSYpy9CrLjXwZxB0,3614
26
+ janito/agent/templates/profiles/system_prompt_template_market_analyst.txt.j2,sha256=TCoZITPBOMvN3zA6QXg6UCNrHWj_iDKRxAREwYev5Ks,4123
27
+ janito/agent/templates/profiles/system_prompt_template_model_conversation_without_tools_or_context.txt.j2,sha256=ynhuZESiVvmwHW0pKNUCu9wXexYkFxs6vf4AcC_r24g,1589
28
28
  janito/cli/__init__.py,sha256=xaPDOrWphBbCR63Xpcx_yfpXSJIlCaaICc4j2qpWqrM,194
29
29
  janito/cli/config.py,sha256=HkZ14701HzIqrvaNyDcDhGlVHfpX_uHlLp2rHmhRm_k,872
30
30
  janito/cli/console.py,sha256=gJolqzWL7jEPLxeuH-CwBDRFpXt976KdZOEAB2tdBDs,64
31
31
  janito/cli/main.py,sha256=s5odou0txf8pzTf1ADk2yV7T5m8B6cejJ81e7iu776U,312
32
- janito/cli/main_cli.py,sha256=Axubqtwl7LVikdCZ4edgTurLgq_F11641CjVbTi2oow,17008
33
- janito/cli/prompt_core.py,sha256=F68J4Xl6jZMYFN4oBBYZFj15Jp-HTYoLub4bw2XpNRU,11648
32
+ janito/cli/main_cli.py,sha256=FXpoybaEbDRlK5-oMOMlcdM7TpVfnOdr6-mOkgJHeTo,16648
33
+ janito/cli/prompt_core.py,sha256=s7FInCOlz3CMAsARcNWMyWXGep--zeySx22m29b2l7Q,11799
34
34
  janito/cli/prompt_handler.py,sha256=SnPTlL64noeAMGlI08VBDD5IDD8jlVMIYA4-fS8zVLg,215
35
35
  janito/cli/prompt_setup.py,sha256=s48gvNfZhKjsEhf4EzL1tKIGm4wDidPMDvlM6TAPYes,2116
36
36
  janito/cli/rich_terminal_reporter.py,sha256=K48Ywwj6xz_NikuezzBmYJM1PANmQD-G48sE4NjQhn0,6835
@@ -40,8 +40,7 @@ janito/cli/chat_mode/bindings.py,sha256=odjc5_-YW1t2FRhBUNRNoBMoQIg5sMz3ktV7xG0A
40
40
  janito/cli/chat_mode/chat_entry.py,sha256=RFdPd23jsA2DMHRacpjAdwI_1dFBaWrtnwyQEgb2fHA,475
41
41
  janito/cli/chat_mode/prompt_style.py,sha256=vsqQ9xxmrYjj1pWuVe9CayQf39fo2EIXrkKPkflSVn4,805
42
42
  janito/cli/chat_mode/script_runner.py,sha256=WFTFVWzg_VQrD2Ujj02XWjscfGgHwmjBeRxaEjWw9ps,6505
43
- janito/cli/chat_mode/session.py,sha256=JNawNraHt_N6lUy46GTLUBojLW9zhgluApQ7RSTVyJg,20107
44
- janito/cli/chat_mode/session_profile_select.py,sha256=bEM8Q41c6taxxMCalXlLMUO18y6WRB84zaVEb-j6Wj4,6441
43
+ janito/cli/chat_mode/session.py,sha256=RA4vQVVNEIWsl3gG3j08FioqUEpv7_j5hjd3fmQRvd4,18678
45
44
  janito/cli/chat_mode/toolbar.py,sha256=SzdWAJdcY1g2rTPZCPL6G5X8jO6ZQYjwko2-nw54_nU,3397
46
45
  janito/cli/chat_mode/shell/autocomplete.py,sha256=lE68MaVaodbA2VfUM0_YLqQVLBJAE_BJsd5cMtwuD-g,793
47
46
  janito/cli/chat_mode/shell/commands.bak.zip,sha256=I7GFjXg2ORT5NzFpicH1vQ3kchhduQsZinzqo0xO8wU,74238
@@ -82,7 +81,7 @@ janito/cli/cli_commands/enable_disable_plugin.py,sha256=IIEg5Gz2aAW_7BKrMQTXSGF0
82
81
  janito/cli/cli_commands/list_config.py,sha256=oiQEGaGPjwjG-PrOcakpNMbbqISTsBEs7rkGH3ceQsI,1179
83
82
  janito/cli/cli_commands/list_drivers.py,sha256=r2ENykUcvf_9XYp6LHd3RvLXGXyVUA6oe_Pr0dyv92I,5124
84
83
  janito/cli/cli_commands/list_models.py,sha256=QF3Wa7OhNcJFKeBxaw0C_rDfsvJFNb-siz5uorajBvo,1595
85
- janito/cli/cli_commands/list_plugins.py,sha256=UfQARomd0O9oHmNowvrUAbf_foYhdgkou1WRdwNa00A,3686
84
+ janito/cli/cli_commands/list_plugins.py,sha256=lha2XX7AKIGtFattATpJgsEKRSxRULPHXI1vNSQiQcg,3846
86
85
  janito/cli/cli_commands/list_profiles.py,sha256=O4k6U9iCEeNH3lM-NP_XX_E9W0h__hheLSn23241dkA,3538
87
86
  janito/cli/cli_commands/list_providers.py,sha256=oilrBjNL5mot1nz45XQQY6oeiSxoNvphhQYspNcEJpw,391
88
87
  janito/cli/cli_commands/list_providers_region.py,sha256=qrMj_gtgEMty8UH0P_O5SgWCVJ9ZKxGUp_GdsE4_EH4,2548
@@ -92,16 +91,16 @@ janito/cli/cli_commands/model_utils.py,sha256=4t2ZN8DYA8jxluXHiiliV8gMbF_90nKGtg
92
91
  janito/cli/cli_commands/ping_providers.py,sha256=hetZAKKZzQYRpRDT5OvRTOe4jYUVNZGjo8gFoyeRA3I,1921
93
92
  janito/cli/cli_commands/set_api_key.py,sha256=IR_hUcLjK-2oJmiIVdjc8epPsQAzqEN9MS7lSTVqmKM,1060
94
93
  janito/cli/cli_commands/show_config.py,sha256=ammzVEqJQCAdWFRrhI1zjjmzgTCH2l38REoT4gYJPP0,3467
95
- janito/cli/cli_commands/show_system_prompt.py,sha256=JBFjaLnZZQn3Q0ChFYKYZaohmKuqgJNcH8asYFe2rH0,6002
94
+ janito/cli/cli_commands/show_system_prompt.py,sha256=WQclY_bmJrHbIBRU1qx1WV4VyooyXVx_XQyX_4Rb1hs,6335
96
95
  janito/cli/core/__init__.py,sha256=YH95fhgY9yBX8RgqX9dSrEkl4exjV0T4rbmJ6xUpG-Y,196
97
96
  janito/cli/core/event_logger.py,sha256=1X6lR0Ax7AgF8HlPWFoY5Ystuu7Bh4ooTo78vXzeGB0,2008
98
97
  janito/cli/core/getters.py,sha256=opmcSz86J-I95Klsh0c4y6lsYvNakrvRqvuA0o5ARWI,2869
99
98
  janito/cli/core/model_guesser.py,sha256=V7LBkIllSp_tP9-2B1gcl5b4b-La7mrOvE3AZQQm8lk,1716
100
- janito/cli/core/runner.py,sha256=7JS9-mN9XNFg6htjQUyOgpQFG86as_4gWMxxwRLc424,9156
99
+ janito/cli/core/runner.py,sha256=gi8xke6re9AoHHNCivV50i0eUAliw8QTUdXyqMkMplM,9044
101
100
  janito/cli/core/setters.py,sha256=zjSUxy6iUzcrmEunFk7dA90KqbMaq2O7LTGP8wn2HxA,5378
102
101
  janito/cli/core/unsetters.py,sha256=FEw9gCt0vRvoCt0kRSNfVB2tzi_TqppJIx2nHPP59-k,2012
103
102
  janito/cli/single_shot_mode/__init__.py,sha256=Ct99pKe9tINzVW6oedZJfzfZQKWpXz-weSSCn0hrwHY,115
104
- janito/cli/single_shot_mode/handler.py,sha256=d251ObY-5bkUyccV9NYkKDF0VCKrQTrGEnwt3mtj61w,5529
103
+ janito/cli/single_shot_mode/handler.py,sha256=aOTh9s7HyHBLs2sgquEVbVhFWC_7Udu4u_9LoQ0f78E,5667
105
104
  janito/docs/GETTING_STARTED.md,sha256=Yx3vi1LQWyDWlE_JYuz4V9EL-Gh4WU6cOBqCr8XidF4,4960
106
105
  janito/drivers/dashscope.bak.zip,sha256=9Pv4Xyciju8jO1lEMFVgYXexoZkxmDO3Ig6vw3ODfL8,4936
107
106
  janito/drivers/openai_responses.bak.zip,sha256=E43eDCHGa2tCtdjzj_pMnWDdnsOZzj8BJTR5tJp8wcM,13352
@@ -122,7 +121,7 @@ janito/i18n/messages.py,sha256=fBuwOTFoygyHPkYphm6Y0r1iE8497Z4iryVAmPhMEkg,1851
122
121
  janito/i18n/pt.py,sha256=NlTgpDSftUfFG7FGbs7TK54vQlJVMyaZDHGcWjelwMc,4168
123
122
  janito/llm/README.md,sha256=6GRqCu_a9va5HCB1YqNqbshyWKFyAGlnXugrjom-xj8,1213
124
123
  janito/llm/__init__.py,sha256=dpyVH51qVRCw-PDyAFLAxq0zd4jl5MDcuV6Cri0D-dQ,134
125
- janito/llm/agent.py,sha256=RYX1LAQKDkdE4H76bAT7ZeFst5ZP_npefuwhevJpbDg,21013
124
+ janito/llm/agent.py,sha256=T0JfeMoOudTWsHwWCcaocrHyq9k0TvkL4_YePlXvZfo,21269
126
125
  janito/llm/auth.py,sha256=8Dl_orUEPhn2X6XjkO2Nr-j1HFT2YDxk1qJl9hSFI88,2286
127
126
  janito/llm/auth_utils.py,sha256=7GH7bIScKhVWJW6ugcDrJLcYRamj5dl_l8N1rrvR4Ws,663
128
127
  janito/llm/driver.py,sha256=stiicPe_MXTuWW4q6MSwK7PCj8UZcA_30pGACu6xYUQ,10039
@@ -169,7 +168,7 @@ janito/providers/moonshot/model_info.py,sha256=PpdUkmuR7g6SyiEzS9nePskPjn5xI1ZM2
169
168
  janito/providers/moonshot/provider.py,sha256=LJxNoC7Oo-ZoFKs2ulK2lXzUEx7kV-79HJ8JG4J-UWU,3856
170
169
  janito/providers/openai/__init__.py,sha256=f0m16-sIqScjL9Mp4A0CQBZx6H3PTEy0cnE08jeaB5U,38
171
170
  janito/providers/openai/model_info.py,sha256=VTkq3xcx2vk0tXlFVHQxKeFzl-DL1T1J2elVOEwCdHI,4265
172
- janito/providers/openai/provider.py,sha256=PPr_qmSe5GyysnZCxhjeUVhE2LWKjKOSRel-8aaxq_U,4761
171
+ janito/providers/openai/provider.py,sha256=VBU5S5eSpqE3qX-nHdxLwsp7usgvUFzOsPzKJKu1-10,4763
173
172
  janito/providers/openai/schema_generator.py,sha256=hTqeLcPTR8jeKn5DUUpo7b-EZ-V-g1WwXiX7MbHnFzE,2234
174
173
  janito/providers/zai/__init__.py,sha256=qtIr9_QBFaXG8xB6cRDGhS7se6ir11CWseI9azLMRBo,24
175
174
  janito/providers/zai/model_info.py,sha256=ldwD8enpxXv1G-YsDw4YJn31YsVueQ4vj5HgoYvnPxo,1183
@@ -186,7 +185,7 @@ janito/tools/base.py,sha256=R38A9xWYh3JRYZMDSom2d1taNDy9J7HpLbZo9X2wH_o,316
186
185
  janito/tools/disabled_tools.py,sha256=Tx__16wtMWZ9z34cYLdH1gukwot5MCL-9kLjd5MPX6Y,2110
187
186
  janito/tools/inspect_registry.py,sha256=Jo7PrMPRKLuR-j_mBAk9PBcTzeJf1eQrS1ChGofgQk0,538
188
187
  janito/tools/loop_protection.py,sha256=WQ2Cqt459vXvrO0T1EqkEHynHlRkPzfaC83RSmXzjkM,4718
189
- janito/tools/loop_protection_decorator.py,sha256=bS28p9MTVEDlrw2vRYX2B-HADFJL3768aThz65U24qw,6668
188
+ janito/tools/loop_protection_decorator.py,sha256=R1j2ouscKbVcDm2wlxRZ6zQuKExgj633ijeDq4j0oO0,6457
190
189
  janito/tools/outline_file.bak.zip,sha256=EeI2cBXCwTdWVgJDNiroxKeYlkjwo6NLKeXz3J-2iZI,15607
191
190
  janito/tools/path_security.py,sha256=40b0hV0X3449Dht93A04Q3c9AYSsBQsBFy2BjzM83lA,8214
192
191
  janito/tools/path_utils.py,sha256=Rg5GE4kiu7rky6I2KTtivW6wPXzc9Qmq0_lOjwkPYlI,832
@@ -197,18 +196,18 @@ janito/tools/tool_events.py,sha256=czRtC2TYakAySBZvfHS_Q6_NY_7_krxzAzAL1ggRFWA,1
197
196
  janito/tools/tool_run_exception.py,sha256=43yWgTaGBGEtRteo6FvTrane6fEVGo9FU1uOdjMRWJE,525
198
197
  janito/tools/tool_use_tracker.py,sha256=IaEmA22D6RuL1xMUCScOMGv0crLPwEJVGmj49cydIaM,2662
199
198
  janito/tools/tool_utils.py,sha256=alPm9DvtXSw_zPRKvP5GjbebPRf_nfvmWk2TNlL5Cws,1219
200
- janito/tools/tools_adapter.py,sha256=FHQwAHIUD_pwbdmNI1w_BGKWKDG1zSO2KakZNhsvQZA,21044
199
+ janito/tools/tools_adapter.py,sha256=F1Wkji222dY53HMaZWf3vqVas1Bimm3UXERKvxF54Ew,21687
201
200
  janito/tools/tools_schema.py,sha256=rGrKrmpPNR07VXHAJ_haGBRRO-YGLOF51BlYRep9AAQ,4415
202
201
  janito/tools/url_whitelist.py,sha256=0CPLkHTp5HgnwgjxwgXnJmwPeZQ30q4j3YjW59hiUUE,4295
203
202
  janito/tools/adapters/__init__.py,sha256=XKixOKtUJs1R-rGwGDXSLVLg5-Kp090gvWbsseWT4LI,92
204
203
  janito/tools/adapters/local/__init__.py,sha256=8xJw8Qv3T_wwkiGBVVgs9p7pH1ONIAipccEUqY2II8A,2231
205
204
  janito/tools/adapters/local/adapter.py,sha256=u4nLHTaYdwZXMi1J8lsKvlG6rOmdq9xjey_3zeyCG4k,8707
206
- janito/tools/adapters/local/ask_user.py,sha256=4xY8SUndw_OYAPzeIRxugmihxxn7Y-b2v9xYT_LGdkY,3941
205
+ janito/tools/adapters/local/ask_user.py,sha256=-shjMRKrRe7HNHM2w_6YAl7eEgl8QXaIV6LKrUDEBxU,4060
207
206
  janito/tools/adapters/local/copy_file.py,sha256=SBJm19Ipe5dqRE1Mxl6JSrn4bNmfObVnDr5b1mcEu6c,3682
208
207
  janito/tools/adapters/local/create_directory.py,sha256=LxwqQEsnOrEphCIoaMRRx9P9bu0MzidP3Fc5q6letxc,2584
209
208
  janito/tools/adapters/local/create_file.py,sha256=nZf8iPScO9_nrvmHwXqOcqpLZkLABTh9uLVNddC4PCk,3760
210
209
  janito/tools/adapters/local/delete_text_in_file.py,sha256=uEeedRxXAR7_CqUc_qhbEdM0OzRi_pgnP-iDjs2Zvjk,5087
211
- janito/tools/adapters/local/fetch_url.py,sha256=mt5C85_PJG-Dj1ffFZDxemuRV5TsTHcrSPq10NEy9ZU,16484
210
+ janito/tools/adapters/local/fetch_url.py,sha256=bkrFFE41h8pWepqnFpmsJ1gF0oDDd5htBsEwFniSKMQ,16064
212
211
  janito/tools/adapters/local/find_files.py,sha256=Zbag3aP34vc7ffJh8bOqAwXj3KiZhV--uzTVHtNb-fI,6250
213
212
  janito/tools/adapters/local/move_file.py,sha256=LMGm8bn3NNyIPJG4vrlO09smXQcgzA09EwoooZxkIA8,4695
214
213
  janito/tools/adapters/local/open_html_in_browser.py,sha256=XqICIwVx5vEE77gHkaNAC-bAeEEy0DBmDksATiL-sRY,2101
@@ -220,7 +219,7 @@ janito/tools/adapters/local/read_chart.py,sha256=qQebp_MEE_x2AL_pl85uA58A4lbhLQs
220
219
  janito/tools/adapters/local/read_files.py,sha256=LzlNrQEadYF8dF97Wm8AHde2nuqbMkN5vVskQLhvFdA,2329
221
220
  janito/tools/adapters/local/remove_directory.py,sha256=DEhHdmwJRl5Yp9eEukIIooMrpATCtXcv5HmaDRn8vH8,1913
222
221
  janito/tools/adapters/local/remove_file.py,sha256=Imra4jGkBfAd6pnUAmbUsjN0exj2vzZWuNRXq_GOMsI,2093
223
- janito/tools/adapters/local/replace_text_in_file.py,sha256=Y6mbPuoTpsFXKrSp51s0rz7Hhz1WHkG1sY4pZ3RoZsU,10790
222
+ janito/tools/adapters/local/replace_text_in_file.py,sha256=zJIDecviF2YRpWxbvhtka4Iaje-QYhcaqQX1PxWolzE,10966
224
223
  janito/tools/adapters/local/run_bash_command.py,sha256=7fABqAeAu7WJwzzwHmT54_m5OSwPMcgpQ74lQhPG7TA,7955
225
224
  janito/tools/adapters/local/run_powershell_command.py,sha256=uQSJVQe40wSGbesyvZxDmIKJthAbDJFaxXm1dEN3gBs,9313
226
225
  janito/tools/adapters/local/view_file.py,sha256=cBKcbwbfH-UMyvQ7PmYTgsshcFmorjWtyH1kaYi7oNY,7379
@@ -231,7 +230,7 @@ janito/tools/adapters/local/get_file_outline/markdown_outline.py,sha256=bXEBg0D9
231
230
  janito/tools/adapters/local/get_file_outline/python_outline.py,sha256=RAcf9Vxec08lA06drYaNre5HCJ2lTzrRAskZ3rlyE-U,10326
232
231
  janito/tools/adapters/local/get_file_outline/search_outline.py,sha256=bski24TpnJVf3L0TNzkx3HfvaXwttQl4EVkwk2POQOw,1348
233
232
  janito/tools/adapters/local/search_text/__init__.py,sha256=FEYpF5tTtf0fiAyRGIGSn-kV-MJDkhdFIbus16mYW8Y,34
234
- janito/tools/adapters/local/search_text/core.py,sha256=Ryi3XOZNvk2dx5c1rAttXJKQI5yBKckav2baKYMKqyM,7882
233
+ janito/tools/adapters/local/search_text/core.py,sha256=obCq4WoJ4ld2kpd57pCyt91wpXVsrvQ0PBTSL8Lxpk0,7882
235
234
  janito/tools/adapters/local/search_text/match_lines.py,sha256=RLR8fZFP-Q57rY0fTENbMItmt3dJZiYX0otmGHVRjfw,2131
236
235
  janito/tools/adapters/local/search_text/pattern_utils.py,sha256=D7vtAr8oT0tGV0C_UUarAXS9XQtP-MTYmmc8Yg8iVTg,2362
237
236
  janito/tools/adapters/local/search_text/traverse_directory.py,sha256=EpL1qywAV0H29pm8-QsHrjKchKP4i4sRUOENVuNptCo,4000
@@ -247,9 +246,9 @@ janito/tools/adapters/local/validate_file_syntax/ps1_validator.py,sha256=TeIkPt0
247
246
  janito/tools/adapters/local/validate_file_syntax/python_validator.py,sha256=BfCO_K18qy92m-2ZVvHsbEU5e11OPo1pO9Vz4G4616E,130
248
247
  janito/tools/adapters/local/validate_file_syntax/xml_validator.py,sha256=AijlsP_PgNuC8ZbGsC5vOTt3Jur76otQzkd_7qR0QFY,284
249
248
  janito/tools/adapters/local/validate_file_syntax/yaml_validator.py,sha256=TgyI0HRL6ug_gBcWEm5TGJJuA4E34ZXcIzMpAbv3oJs,155
250
- janito-2.29.0.dist-info/licenses/LICENSE,sha256=GSAKapQH5ZIGWlpQTA7v5YrfECyaxaohUb1vJX-qepw,1090
251
- janito-2.29.0.dist-info/METADATA,sha256=qs80f05hLB5SVF0hPk5h1vwBvWt0S1B5mWwYBr5egSE,16945
252
- janito-2.29.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
253
- janito-2.29.0.dist-info/entry_points.txt,sha256=wIo5zZxbmu4fC-ZMrsKD0T0vq7IqkOOLYhrqRGypkx4,48
254
- janito-2.29.0.dist-info/top_level.txt,sha256=m0NaVCq0-ivxbazE2-ND0EA9Hmuijj_OGkmCbnBcCig,7
255
- janito-2.29.0.dist-info/RECORD,,
249
+ janito-2.31.0.dist-info/licenses/LICENSE,sha256=GSAKapQH5ZIGWlpQTA7v5YrfECyaxaohUb1vJX-qepw,1090
250
+ janito-2.31.0.dist-info/METADATA,sha256=PTDIEejdQE8T6pYMV-_SZubwGBauxg0aER9EbzwAt_s,16945
251
+ janito-2.31.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
252
+ janito-2.31.0.dist-info/entry_points.txt,sha256=wIo5zZxbmu4fC-ZMrsKD0T0vq7IqkOOLYhrqRGypkx4,48
253
+ janito-2.31.0.dist-info/top_level.txt,sha256=m0NaVCq0-ivxbazE2-ND0EA9Hmuijj_OGkmCbnBcCig,7
254
+ janito-2.31.0.dist-info/RECORD,,
@@ -1,182 +0,0 @@
1
- import os
2
- from pathlib import Path
3
- import questionary
4
- from questionary import Style
5
- import os
6
- from pathlib import Path
7
- from prompt_toolkit.formatted_text import HTML
8
- from prompt_toolkit import PromptSession
9
- from prompt_toolkit.key_binding import KeyBindings
10
- from prompt_toolkit.enums import EditingMode
11
- from prompt_toolkit.formatted_text import HTML
12
- from .prompt_style import chat_shell_style
13
-
14
-
15
- """
16
- Profile selection logic for Janito Chat CLI using questionary.
17
- """
18
-
19
-
20
- def _handle_raw_model_session_no_tools():
21
- return {
22
- "profile": "model_conversation_without_tools_or_context",
23
- "profile_system_prompt": None,
24
- "no_tools_mode": True,
25
- } # Raw Model Session (no tools, no context)
26
-
27
-
28
- def _handle_using_role():
29
- role_name = questionary.text("Enter the role name:").ask()
30
- return f"role:{role_name}"
31
-
32
-
33
- def _get_toolbar(mode):
34
- if mode["multiline"]:
35
- return HTML(
36
- "<b>Multiline mode (Esc+Enter to submit). Type /single to switch.</b>"
37
- )
38
- else:
39
- return HTML(
40
- "<b>Single-line mode (Enter to submit). Type /multi for multiline.</b>"
41
- )
42
-
43
-
44
- def _handle_custom_system_prompt():
45
- print(
46
- "\n[Custom System Prompt]\nPlease enter the message that will be used as the model system prompt. This will define how the AI behaves for this session.\nYou can use /multi for multiline mode, and /single to return to single-line mode.\n"
47
- )
48
- mode = {"multiline": False}
49
- bindings = KeyBindings()
50
-
51
- @bindings.add("c-r")
52
- def _(event):
53
- pass
54
-
55
- @bindings.add("f12")
56
- def _(event):
57
- buf = event.app.current_buffer
58
- buf.text = "Do It"
59
- buf.validate_and_handle()
60
-
61
- session = PromptSession(
62
- multiline=False,
63
- key_bindings=bindings,
64
- editing_mode=EditingMode.EMACS,
65
- bottom_toolbar=lambda: _get_toolbar(mode),
66
- style=chat_shell_style,
67
- )
68
- prompt_icon = HTML("<inputline>📝 </inputline>")
69
- while True:
70
- try:
71
- response = session.prompt(prompt_icon)
72
- except KeyboardInterrupt:
73
- print("[Custom System Prompt] Exited by the user.")
74
- import sys
75
-
76
- sys.exit(0)
77
- if not mode["multiline"] and response.strip() == "/multi":
78
- mode["multiline"] = True
79
- session.multiline = True
80
- continue
81
- elif mode["multiline"] and response.strip() == "/single":
82
- mode["multiline"] = False
83
- session.multiline = False
84
- continue
85
- else:
86
- sanitized = response.strip()
87
- try:
88
- sanitized.encode("utf-8")
89
- except UnicodeEncodeError:
90
- sanitized = sanitized.encode("utf-8", errors="replace").decode("utf-8")
91
- return {"profile": None, "profile_system_prompt": sanitized}
92
-
93
-
94
- def _load_user_profiles():
95
- user_profiles_dir = Path.home() / ".janito" / "profiles"
96
- profiles = {}
97
- if user_profiles_dir.exists() and user_profiles_dir.is_dir():
98
- for profile_file in user_profiles_dir.glob("*"):
99
- if profile_file.is_file():
100
- try:
101
- with open(profile_file, "r", encoding="utf-8") as f:
102
- profiles[profile_file.stem] = f.read().strip()
103
- except Exception:
104
- # Ignore unreadable files
105
- pass
106
- return profiles
107
-
108
-
109
- def select_profile():
110
- user_profiles = _load_user_profiles()
111
- choices = [
112
- "Developer with Python Tools",
113
- "Developer",
114
- "Market Analyst",
115
- "Custom system prompt...",
116
- "Raw Model Session (no tools, no context)",
117
- ]
118
- # Add user profiles to choices
119
- if user_profiles:
120
- choices.extend(user_profiles.keys())
121
-
122
- custom_style = Style(
123
- [
124
- ("highlighted", "bg:#00aaff #ffffff"), # background for item under cursor
125
- ("question", "fg:#00aaff bold"),
126
- ]
127
- )
128
- answer = questionary.select(
129
- "Select a profile to use:", choices=choices, default=None, style=custom_style
130
- ).ask()
131
-
132
- if not answer:
133
- import sys
134
-
135
- sys.exit(0)
136
-
137
- if answer == "Raw Model Session (no tools, no context)":
138
- return _handle_raw_model_session_no_tools()
139
- elif answer == "Custom system prompt...":
140
- return _handle_custom_system_prompt()
141
- elif answer in user_profiles:
142
- # Return the content of the user profile as a custom system prompt
143
- return {"profile": None, "profile_system_prompt": user_profiles[answer]}
144
- elif answer == "Developer":
145
- # Return the content of the built-in Developer profile prompt
146
- from pathlib import Path
147
- from jinja2 import Template
148
- from janito.agent.setup_agent import _prepare_template_context
149
-
150
- # Get the absolute path relative to the current script location
151
- current_dir = Path(__file__).parent
152
- template_path = (
153
- current_dir
154
- / "../../agent/templates/profiles/system_prompt_template_developer.txt.j2"
155
- )
156
- with open(template_path, "r", encoding="utf-8") as f:
157
- template_content = f.read()
158
-
159
- template = Template(template_content)
160
- context = _prepare_template_context("developer", "Developer", None)
161
- prompt = template.render(**context)
162
- return {"profile": "Developer", "profile_system_prompt": prompt}
163
- elif answer == "Market Analyst":
164
- # Return the content of the built-in Market Analyst profile prompt
165
- from pathlib import Path
166
- from jinja2 import Template
167
- from janito.agent.setup_agent import _prepare_template_context
168
-
169
- # Get the absolute path relative to the current script location
170
- current_dir = Path(__file__).parent
171
- template_path = (
172
- current_dir
173
- / "../../agent/templates/profiles/system_prompt_template_market_analyst.txt.j2"
174
- )
175
- with open(template_path, "r", encoding="utf-8") as f:
176
- template_content = f.read()
177
-
178
- template = Template(template_content)
179
- context = _prepare_template_context("market_analyst", "Market Analyst", None)
180
- prompt = template.render(**context)
181
- return {"profile": "Market Analyst", "profile_system_prompt": prompt}
182
- return answer