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.
- janito/README.md +3 -3
- janito/agent/setup_agent.py +21 -35
- janito/agent/templates/profiles/system_prompt_template_Developer_with_Python_Tools.txt.j2 +6 -0
- janito/agent/templates/profiles/system_prompt_template_developer.txt.j2 +6 -0
- janito/agent/templates/profiles/system_prompt_template_market_analyst.txt.j2 +7 -1
- janito/agent/templates/profiles/system_prompt_template_model_conversation_without_tools_or_context.txt.j2 +7 -1
- janito/cli/chat_mode/session.py +154 -96
- janito/cli/cli_commands/list_plugins.py +99 -75
- janito/cli/cli_commands/show_system_prompt.py +8 -3
- janito/cli/core/runner.py +2 -2
- janito/cli/main_cli.py +9 -15
- janito/cli/prompt_core.py +0 -2
- janito/cli/rich_terminal_reporter.py +2 -1
- janito/cli/single_shot_mode/handler.py +0 -2
- janito/llm/agent.py +6 -1
- janito/provider_registry.py +1 -1
- janito/providers/openai/provider.py +1 -1
- janito/tools/adapters/local/ask_user.py +3 -1
- janito/tools/adapters/local/fetch_url.py +20 -28
- janito/tools/adapters/local/replace_text_in_file.py +9 -3
- janito/tools/adapters/local/search_text/core.py +2 -2
- janito/tools/loop_protection_decorator.py +12 -16
- janito/tools/tools_adapter.py +18 -4
- janito-2.30.0.dist-info/METADATA +83 -0
- {janito-2.28.0.dist-info → janito-2.30.0.dist-info}/RECORD +29 -30
- janito-2.30.0.dist-info/licenses/LICENSE +201 -0
- janito/cli/chat_mode/session_profile_select.py +0 -182
- janito-2.28.0.dist-info/METADATA +0 -431
- janito-2.28.0.dist-info/licenses/LICENSE +0 -21
- {janito-2.28.0.dist-info → janito-2.30.0.dist-info}/WHEEL +0 -0
- {janito-2.28.0.dist-info → janito-2.30.0.dist-info}/entry_points.txt +0 -0
- {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
|
119
|
+
### Profiles
|
120
120
|
Use predefined system prompts:
|
121
121
|
```bash
|
122
|
-
janito --
|
123
|
-
janito --
|
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
|
janito/agent/setup_agent.py
CHANGED
@@ -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
|
-
|
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(
|
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 %}
|
janito/cli/chat_mode/session.py
CHANGED
@@ -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
|
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
|
-
|
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
|
-
|
141
|
-
|
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
|
-
|
318
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
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()
|