janito 2.28.0__py3-none-any.whl → 2.30.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 (32) 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 +154 -96
  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 +0 -2
  13. janito/cli/rich_terminal_reporter.py +2 -1
  14. janito/cli/single_shot_mode/handler.py +0 -2
  15. janito/llm/agent.py +6 -1
  16. janito/provider_registry.py +1 -1
  17. janito/providers/openai/provider.py +1 -1
  18. janito/tools/adapters/local/ask_user.py +3 -1
  19. janito/tools/adapters/local/fetch_url.py +20 -28
  20. janito/tools/adapters/local/replace_text_in_file.py +9 -3
  21. janito/tools/adapters/local/search_text/core.py +2 -2
  22. janito/tools/loop_protection_decorator.py +12 -16
  23. janito/tools/tools_adapter.py +18 -4
  24. janito-2.30.0.dist-info/METADATA +83 -0
  25. {janito-2.28.0.dist-info → janito-2.30.0.dist-info}/RECORD +29 -30
  26. janito-2.30.0.dist-info/licenses/LICENSE +201 -0
  27. janito/cli/chat_mode/session_profile_select.py +0 -182
  28. janito-2.28.0.dist-info/METADATA +0 -431
  29. janito-2.28.0.dist-info/licenses/LICENSE +0 -21
  30. {janito-2.28.0.dist-info → janito-2.30.0.dist-info}/WHEEL +0 -0
  31. {janito-2.28.0.dist-info → janito-2.30.0.dist-info}/entry_points.txt +0 -0
  32. {janito-2.28.0.dist-info → janito-2.30.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 %}
@@ -63,6 +63,7 @@ class ChatSession:
63
63
  allowed_permissions=None,
64
64
  ):
65
65
  self.console = console
66
+ self.session_start_time = time.time()
66
67
  self.user_input_history = UserInputHistory()
67
68
  self.input_dicts = self.user_input_history.load()
68
69
  self.mem_history = InMemoryHistory()
@@ -114,22 +115,11 @@ class ChatSession:
114
115
  self.multi_line_mode = getattr(args, "multi", False) if args else False
115
116
 
116
117
  def _select_profile_and_role(self, args, role):
117
- profile = getattr(args, "profile", None) if args is not None else None
118
- role_arg = getattr(args, "role", None) if args is not None else None
119
- python_profile = (
120
- getattr(args, "developer", False) if args is not None else False
121
- )
122
- market_profile = getattr(args, "market", False) if args is not None else False
118
+ profile, role_arg, python_profile, market_profile = self._extract_args(args)
123
119
  profile_system_prompt = None
124
120
  no_tools_mode = False
125
121
 
126
- # Handle --developer flag
127
- if python_profile and profile is None and role_arg is None:
128
- profile = "Developer with Python Tools"
129
-
130
- # Handle --market flag
131
- if market_profile and profile is None and role_arg is None:
132
- profile = "Market Analyst"
122
+ profile = self._determine_profile(profile, python_profile, market_profile)
133
123
 
134
124
  if (
135
125
  profile is None
@@ -137,45 +127,15 @@ class ChatSession:
137
127
  and not python_profile
138
128
  and not market_profile
139
129
  ):
140
- # Skip interactive profile selection for list commands
141
- from janito.cli.core.getters import GETTER_KEYS
142
-
143
- # Check if any getter command is active - these don't need interactive mode
130
+ skip_profile_selection = self._should_skip_profile_selection(args)
131
+ else:
144
132
  skip_profile_selection = False
145
- if args is not None:
146
- for key in GETTER_KEYS:
147
- if getattr(args, key, False):
148
- skip_profile_selection = True
149
- break
150
133
 
151
134
  if skip_profile_selection:
152
135
  profile = "Developer with Python Tools" # Default for non-interactive commands
153
136
  else:
154
- try:
155
- from janito.cli.chat_mode.session_profile_select import (
156
- select_profile,
157
- )
158
-
159
- result = select_profile()
160
- if isinstance(result, dict):
161
- profile = result.get("profile")
162
- profile_system_prompt = result.get("profile_system_prompt")
163
- no_tools_mode = result.get("no_tools_mode", False)
164
- elif isinstance(result, str) and result.startswith("role:"):
165
- role = result[len("role:") :].strip()
166
- profile = "Developer with Python Tools"
167
- else:
168
- profile = (
169
- "Developer with Python Tools"
170
- if result == "Developer"
171
- else result
172
- )
173
- except ImportError:
174
- profile = "Raw Model Session (no tools, no context)"
175
- if role_arg is not None:
176
- role = role_arg
177
- if profile is None:
178
137
  profile = "Developer with Python Tools"
138
+
179
139
  return profile, role, profile_system_prompt, no_tools_mode
180
140
 
181
141
  def _create_conversation_history(self):
@@ -314,52 +274,8 @@ class ChatSession:
314
274
  )
315
275
  start_time = time.time()
316
276
 
317
- # Print rule line with model info before processing prompt
318
- model_name = (
319
- self.agent.get_model_name()
320
- if hasattr(self.agent, "get_model_name")
321
- else "Unknown"
322
- )
323
- provider_name = (
324
- self.agent.get_provider_name()
325
- if hasattr(self.agent, "get_provider_name")
326
- else "Unknown"
327
- )
328
-
329
- backend_hostname = "Unknown"
330
- candidates = []
331
- drv = getattr(self.agent, "driver", None)
332
- if drv is not None:
333
- cfg = getattr(drv, "config", None)
334
- if cfg is not None:
335
- b = getattr(cfg, "base_url", None)
336
- if b:
337
- candidates.append(b)
338
- direct_base = getattr(drv, "base_url", None)
339
- if direct_base:
340
- candidates.append(direct_base)
341
- cfg2 = getattr(self.agent, "config", None)
342
- if cfg2 is not None:
343
- b2 = getattr(cfg2, "base_url", None)
344
- if b2:
345
- candidates.append(b2)
346
- top_base = getattr(self.agent, "base_url", None)
347
- if top_base:
348
- candidates.append(top_base)
349
- from urllib.parse import urlparse
350
-
351
- for candidate in candidates:
352
- try:
353
- if not candidate:
354
- continue
355
- parsed = urlparse(str(candidate))
356
- host = parsed.netloc or parsed.path
357
- if host:
358
- backend_hostname = host
359
- break
360
- except Exception:
361
- backend_hostname = str(candidate)
362
- break
277
+ model_name, provider_name = self._get_model_info()
278
+ backend_hostname = self._get_backend_hostname()
363
279
 
364
280
  self.console.print(
365
281
  Rule(
@@ -377,8 +293,6 @@ class ChatSession:
377
293
  print_token_message_summary(
378
294
  self.console, self.msg_count, usage, elapsed=elapsed
379
295
  )
380
- # Send terminal bell character to trigger TUI bell after printing token summary
381
- print("\a", end="", flush=True)
382
296
  if final_event and hasattr(final_event, "metadata"):
383
297
  exit_reason = (
384
298
  final_event.metadata.get("exit_reason")
@@ -395,6 +309,102 @@ class ChatSession:
395
309
 
396
310
  self.console.print(traceback.format_exc())
397
311
 
312
+ def _extract_args(self, args):
313
+ """Extract profile and role arguments from args."""
314
+ profile = getattr(args, "profile", None) if args is not None else None
315
+ role_arg = None
316
+ python_profile = (
317
+ getattr(args, "developer", False) if args is not None else False
318
+ )
319
+ market_profile = getattr(args, "market", False) if args is not None else False
320
+ return profile, role_arg, python_profile, market_profile
321
+
322
+ def _determine_profile(self, profile, python_profile, market_profile):
323
+ """Determine the profile based on flags and arguments."""
324
+ if python_profile and profile is None:
325
+ return "Developer with Python Tools"
326
+ if market_profile and profile is None:
327
+ return "Market Analyst"
328
+ return profile
329
+
330
+ def _should_skip_profile_selection(self, args):
331
+ """Check if profile selection should be skipped for getter commands."""
332
+ from janito.cli.core.getters import GETTER_KEYS
333
+
334
+ if args is None:
335
+ return False
336
+
337
+ for key in GETTER_KEYS:
338
+ if getattr(args, key, False):
339
+ return True
340
+ return False
341
+
342
+ def _get_model_info(self):
343
+ """Get model and provider information."""
344
+ model_name = (
345
+ self.agent.get_model_name()
346
+ if hasattr(self.agent, "get_model_name")
347
+ else "Unknown"
348
+ )
349
+ provider_name = (
350
+ self.agent.get_provider_name()
351
+ if hasattr(self.agent, "get_provider_name")
352
+ else "Unknown"
353
+ )
354
+ return model_name, provider_name
355
+
356
+ def _get_backend_hostname(self):
357
+ """Extract backend hostname from agent configuration."""
358
+ candidates = self._collect_base_urls()
359
+ return self._parse_hostname_from_urls(candidates)
360
+
361
+ def _collect_base_urls(self):
362
+ """Collect all possible base URLs from agent configuration."""
363
+ candidates = []
364
+
365
+ # Collect from driver
366
+ drv = getattr(self.agent, "driver", None)
367
+ if drv is not None:
368
+ cfg = getattr(drv, "config", None)
369
+ if cfg is not None:
370
+ b = getattr(cfg, "base_url", None)
371
+ if b:
372
+ candidates.append(b)
373
+ direct_base = getattr(drv, "base_url", None)
374
+ if direct_base:
375
+ candidates.append(direct_base)
376
+
377
+ # Collect from agent config
378
+ cfg2 = getattr(self.agent, "config", None)
379
+ if cfg2 is not None:
380
+ b2 = getattr(cfg2, "base_url", None)
381
+ if b2:
382
+ candidates.append(b2)
383
+
384
+ # Collect from agent directly
385
+ top_base = getattr(self.agent, "base_url", None)
386
+ if top_base:
387
+ candidates.append(top_base)
388
+
389
+ return candidates
390
+
391
+ def _parse_hostname_from_urls(self, candidates):
392
+ """Parse hostname from a list of URL candidates."""
393
+ from urllib.parse import urlparse
394
+
395
+ for candidate in candidates:
396
+ try:
397
+ if not candidate:
398
+ continue
399
+ parsed = urlparse(str(candidate))
400
+ host = parsed.netloc or parsed.path
401
+ if host:
402
+ return host
403
+ except Exception:
404
+ return str(candidate)
405
+
406
+ return "Unknown"
407
+
398
408
  def _create_prompt_session(self):
399
409
  return PromptSession(
400
410
  style=chat_shell_style,
@@ -416,7 +426,25 @@ class ChatSession:
416
426
  else:
417
427
  try:
418
428
  cmd_input = session.prompt(HTML("<inputline>💬 </inputline>"))
419
- except (KeyboardInterrupt, EOFError):
429
+ except KeyboardInterrupt:
430
+ # Ask for confirmation on Ctrl+C
431
+ from prompt_toolkit import prompt
432
+
433
+ try:
434
+ confirm = prompt(
435
+ "Are you sure you want to exit? (y/n): ",
436
+ style=self._create_prompt_session().style,
437
+ )
438
+ if confirm.lower() == "y":
439
+ self._handle_exit()
440
+ return None
441
+ else:
442
+ return "" # Return empty string to continue
443
+ except (KeyboardInterrupt, EOFError):
444
+ # Handle second Ctrl+C or Ctrl+D as immediate exit
445
+ self._handle_exit()
446
+ return None
447
+ except EOFError:
420
448
  self._handle_exit()
421
449
  return None
422
450
  sanitized = cmd_input.strip()
@@ -430,7 +458,37 @@ class ChatSession:
430
458
  return sanitized
431
459
 
432
460
  def _handle_exit(self):
433
- self.console.print("[bold yellow]Exiting chat. Goodbye![/bold yellow]")
461
+ session_duration = time.time() - self.session_start_time
462
+
463
+ # Get total token usage from performance collector
464
+ from janito.perf_singleton import performance_collector
465
+
466
+ total_tokens = performance_collector.get_token_usage().get("total_tokens", 0)
467
+
468
+ # Format session duration
469
+ if session_duration < 60:
470
+ duration_str = f"{session_duration:.1f}s"
471
+ elif session_duration < 3600:
472
+ duration_str = f"{session_duration/60:.1f}m"
473
+ else:
474
+ duration_str = f"{session_duration/3600:.1f}h"
475
+
476
+ # Format tokens in k/m/t as appropriate
477
+ if total_tokens >= 1_000_000_000:
478
+ token_str = f"{total_tokens/1_000_000_000:.1f}t"
479
+ elif total_tokens >= 1_000_000:
480
+ token_str = f"{total_tokens/1_000_000:.1f}m"
481
+ elif total_tokens >= 1_000:
482
+ token_str = f"{total_tokens/1_000:.1f}k"
483
+ else:
484
+ token_str = f"{total_tokens}"
485
+
486
+ self.console.print(f"[bold yellow]Session completed![/bold yellow]")
487
+ self.console.print(
488
+ f"[dim]Session time: {duration_str} | Total tokens: {token_str}[/dim]"
489
+ )
490
+ self.console.print("[bold yellow]Goodbye![/bold yellow]")
491
+
434
492
  if hasattr(self, "agent") and hasattr(self.agent, "join_driver"):
435
493
  if (
436
494
  hasattr(self.agent, "input_queue")
@@ -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()