janito 2.3.0__py3-none-any.whl → 2.4.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/__init__.py +6 -6
- janito/_version.py +57 -0
- janito/agent/setup_agent.py +92 -18
- janito/agent/templates/profiles/system_prompt_template_developer.txt.j2 +44 -0
- janito/cli/chat_mode/bindings.py +21 -2
- janito/cli/chat_mode/chat_entry.py +2 -3
- janito/cli/chat_mode/prompt_style.py +5 -0
- janito/cli/chat_mode/session.py +80 -94
- janito/cli/chat_mode/session_profile_select.py +80 -0
- janito/cli/chat_mode/shell/autocomplete.py +21 -21
- janito/cli/chat_mode/shell/commands/__init__.py +13 -7
- janito/cli/chat_mode/shell/commands/_priv_check.py +5 -0
- janito/cli/chat_mode/shell/commands/clear.py +12 -12
- janito/cli/chat_mode/shell/commands/conversation_restart.py +30 -0
- janito/cli/chat_mode/shell/commands/execute.py +42 -0
- janito/cli/chat_mode/shell/commands/help.py +6 -3
- janito/cli/chat_mode/shell/commands/model.py +28 -0
- janito/cli/chat_mode/shell/commands/multi.py +51 -51
- janito/cli/chat_mode/shell/commands/read.py +37 -0
- janito/cli/chat_mode/shell/commands/tools.py +45 -18
- janito/cli/chat_mode/shell/commands/write.py +37 -0
- janito/cli/chat_mode/shell/commands.bak.zip +0 -0
- janito/cli/chat_mode/shell/input_history.py +62 -62
- janito/cli/chat_mode/shell/session.bak.zip +0 -0
- janito/cli/chat_mode/toolbar.py +44 -27
- janito/cli/cli_commands/list_models.py +35 -35
- janito/cli/cli_commands/list_providers.py +9 -9
- janito/cli/cli_commands/list_tools.py +86 -53
- janito/cli/cli_commands/model_selection.py +50 -50
- janito/cli/cli_commands/set_api_key.py +19 -19
- janito/cli/cli_commands/show_config.py +51 -51
- janito/cli/cli_commands/show_system_prompt.py +105 -62
- janito/cli/config.py +5 -6
- janito/cli/core/__init__.py +4 -4
- janito/cli/core/event_logger.py +59 -59
- janito/cli/core/runner.py +25 -18
- janito/cli/core/setters.py +10 -1
- janito/cli/core/unsetters.py +54 -54
- janito/cli/main_cli.py +28 -5
- janito/cli/prompt_core.py +18 -2
- janito/cli/prompt_setup.py +56 -0
- janito/cli/single_shot_mode/__init__.py +6 -6
- janito/cli/single_shot_mode/handler.py +14 -73
- janito/cli/verbose_output.py +1 -1
- janito/config.py +5 -5
- janito/config_manager.py +13 -0
- janito/drivers/anthropic/driver.py +113 -113
- janito/drivers/dashscope.bak.zip +0 -0
- janito/drivers/openai/README.md +20 -0
- janito/drivers/openai_responses.bak.zip +0 -0
- janito/event_bus/event.py +2 -2
- janito/formatting_token.py +54 -54
- janito/i18n/__init__.py +35 -35
- janito/i18n/messages.py +23 -23
- janito/i18n/pt.py +46 -47
- janito/llm/README.md +23 -0
- janito/llm/__init__.py +5 -5
- janito/llm/agent.py +507 -443
- janito/llm/driver.py +8 -0
- janito/llm/driver_config_builder.py +34 -34
- janito/llm/driver_input.py +12 -12
- janito/llm/message_parts.py +60 -60
- janito/llm/model.py +38 -38
- janito/llm/provider.py +196 -196
- janito/provider_registry.py +8 -6
- janito/providers/anthropic/model_info.py +22 -22
- janito/providers/anthropic/provider.py +2 -0
- janito/providers/azure_openai/provider.py +3 -0
- janito/providers/dashscope.bak.zip +0 -0
- janito/providers/deepseek/__init__.py +1 -1
- janito/providers/deepseek/model_info.py +16 -16
- janito/providers/deepseek/provider.py +94 -91
- janito/providers/google/provider.py +3 -0
- janito/providers/mistralai/provider.py +3 -0
- janito/providers/openai/provider.py +4 -0
- janito/providers/registry.py +26 -26
- janito/shell.bak.zip +0 -0
- janito/tools/DOCSTRING_STANDARD.txt +33 -0
- janito/tools/README.md +3 -0
- janito/tools/__init__.py +20 -6
- janito/tools/adapters/__init__.py +1 -1
- janito/tools/adapters/local/__init__.py +65 -62
- janito/tools/adapters/local/adapter.py +18 -35
- janito/tools/adapters/local/ask_user.py +101 -102
- janito/tools/adapters/local/copy_file.py +84 -84
- janito/tools/adapters/local/create_directory.py +69 -69
- janito/tools/adapters/local/create_file.py +82 -82
- janito/tools/adapters/local/delete_text_in_file.py +2 -2
- janito/tools/adapters/local/fetch_url.py +97 -97
- janito/tools/adapters/local/find_files.py +139 -138
- janito/tools/adapters/local/get_file_outline/__init__.py +1 -1
- janito/tools/adapters/local/get_file_outline/core.py +117 -117
- janito/tools/adapters/local/get_file_outline/java_outline.py +40 -40
- janito/tools/adapters/local/get_file_outline/markdown_outline.py +14 -14
- janito/tools/adapters/local/get_file_outline/python_outline.py +303 -303
- janito/tools/adapters/local/get_file_outline/python_outline_v2.py +156 -156
- janito/tools/adapters/local/get_file_outline/search_outline.py +33 -33
- janito/tools/adapters/local/move_file.py +2 -2
- janito/tools/adapters/local/open_html_in_browser.py +2 -1
- janito/tools/adapters/local/open_url.py +2 -2
- janito/tools/adapters/local/python_code_run.py +166 -166
- janito/tools/adapters/local/python_command_run.py +164 -164
- janito/tools/adapters/local/python_file_run.py +163 -163
- janito/tools/adapters/local/remove_directory.py +2 -2
- janito/tools/adapters/local/remove_file.py +2 -2
- janito/tools/adapters/local/replace_text_in_file.py +2 -2
- janito/tools/adapters/local/run_bash_command.py +176 -176
- janito/tools/adapters/local/run_powershell_command.py +219 -219
- janito/tools/adapters/local/search_text/__init__.py +1 -1
- janito/tools/adapters/local/search_text/core.py +201 -201
- janito/tools/adapters/local/search_text/pattern_utils.py +73 -73
- janito/tools/adapters/local/search_text/traverse_directory.py +145 -145
- janito/tools/adapters/local/validate_file_syntax/__init__.py +1 -1
- janito/tools/adapters/local/validate_file_syntax/core.py +106 -106
- janito/tools/adapters/local/validate_file_syntax/css_validator.py +35 -35
- janito/tools/adapters/local/validate_file_syntax/html_validator.py +93 -93
- janito/tools/adapters/local/validate_file_syntax/js_validator.py +27 -27
- janito/tools/adapters/local/validate_file_syntax/json_validator.py +6 -6
- janito/tools/adapters/local/validate_file_syntax/markdown_validator.py +109 -109
- janito/tools/adapters/local/validate_file_syntax/ps1_validator.py +32 -32
- janito/tools/adapters/local/validate_file_syntax/python_validator.py +5 -5
- janito/tools/adapters/local/validate_file_syntax/xml_validator.py +11 -11
- janito/tools/adapters/local/validate_file_syntax/yaml_validator.py +6 -6
- janito/tools/adapters/local/view_file.py +168 -167
- janito/tools/inspect_registry.py +17 -17
- janito/tools/outline_file.bak.zip +0 -0
- janito/tools/permissions.py +45 -0
- janito/tools/permissions_parse.py +12 -0
- janito/tools/tool_base.py +118 -105
- janito/tools/tool_events.py +58 -58
- janito/tools/tool_run_exception.py +12 -12
- janito/tools/tool_use_tracker.py +81 -81
- janito/tools/tool_utils.py +43 -45
- janito/tools/tools_adapter.py +25 -20
- janito/tools/tools_schema.py +104 -104
- {janito-2.3.0.dist-info → janito-2.4.0.dist-info}/METADATA +425 -388
- janito-2.4.0.dist-info/RECORD +195 -0
- janito/agent/templates/profiles/system_prompt_template_base_pt.txt.j2 +0 -13
- janito/agent/templates/profiles/system_prompt_template_main.txt.j2 +0 -37
- janito/cli/chat_mode/shell/commands/edit.py +0 -25
- janito/cli/chat_mode/shell/commands/exec.py +0 -27
- janito/cli/chat_mode/shell/commands/termweb_log.py +0 -92
- janito/cli/termweb_starter.py +0 -122
- janito/termweb/app.py +0 -95
- janito/version.py +0 -4
- janito-2.3.0.dist-info/RECORD +0 -181
- {janito-2.3.0.dist-info → janito-2.4.0.dist-info}/WHEEL +0 -0
- {janito-2.3.0.dist-info → janito-2.4.0.dist-info}/entry_points.txt +0 -0
- {janito-2.3.0.dist-info → janito-2.4.0.dist-info}/licenses/LICENSE +0 -0
- {janito-2.3.0.dist-info → janito-2.4.0.dist-info}/top_level.txt +0 -0
janito/__init__.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
"""
|
2
|
-
janito: A Python package for managing and interacting with Large Language Model (LLM) providers and their APIs.
|
3
|
-
Provides a CLI for credential management and an extensible driver system for LLMs.
|
4
|
-
"""
|
5
|
-
|
6
|
-
|
1
|
+
"""
|
2
|
+
janito: A Python package for managing and interacting with Large Language Model (LLM) providers and their APIs.
|
3
|
+
Provides a CLI for credential management and an extensible driver system for LLMs.
|
4
|
+
"""
|
5
|
+
|
6
|
+
from ._version import __version__
|
janito/_version.py
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
"""Version handling for Janito.
|
2
|
+
Attempts to obtain the package version in the following order:
|
3
|
+
1. If a janito.version module exists (generated when the package is built with
|
4
|
+
setuptools-scm), use the version attribute from that module.
|
5
|
+
2. Ask importlib.metadata for the installed distribution version – works for
|
6
|
+
both regular and editable installs handled by pip.
|
7
|
+
3. Fall back to calling setuptools_scm.get_version() directly, using the git
|
8
|
+
repository when running from source without an installed distribution.
|
9
|
+
4. If everything else fails, return the literal string ``"unknown"`` so that
|
10
|
+
the application continues to work even when the version cannot be
|
11
|
+
determined.
|
12
|
+
|
13
|
+
This layered approach guarantees that a meaningful version string is returned
|
14
|
+
in most development and production scenarios while keeping Janito free from
|
15
|
+
hard-coded version numbers.
|
16
|
+
"""
|
17
|
+
|
18
|
+
from __future__ import annotations
|
19
|
+
|
20
|
+
import pathlib
|
21
|
+
from importlib import metadata as importlib_metadata
|
22
|
+
|
23
|
+
__all__ = ["__version__"]
|
24
|
+
|
25
|
+
|
26
|
+
# 1. "janito.version" (generated at build time by setuptools-scm)
|
27
|
+
try:
|
28
|
+
from . import version as _generated_version # type: ignore
|
29
|
+
|
30
|
+
__version__: str = _generated_version.version # pytype: disable=module-attr
|
31
|
+
except ImportError: # pragma: no cover – not available in editable installs
|
32
|
+
|
33
|
+
def _resolve_version() -> str:
|
34
|
+
"""Resolve the version string using several fallbacks."""
|
35
|
+
|
36
|
+
# 2. importlib.metadata – works for both regular and `pip install -e`.
|
37
|
+
try:
|
38
|
+
return importlib_metadata.version("janito")
|
39
|
+
except importlib_metadata.PackageNotFoundError:
|
40
|
+
pass # Not installed – probably running from a source checkout.
|
41
|
+
|
42
|
+
# 3. setuptools_scm – query the VCS metadata directly.
|
43
|
+
try:
|
44
|
+
from setuptools_scm import get_version # Imported lazily.
|
45
|
+
|
46
|
+
package_root = pathlib.Path(__file__).resolve().parent.parent
|
47
|
+
return get_version(root=str(package_root), relative_to=__file__)
|
48
|
+
except Exception: # pragma: no cover – any failure here falls through
|
49
|
+
# Either setuptools_scm is not available or this is not a git repo.
|
50
|
+
pass
|
51
|
+
|
52
|
+
# 4. Ultimate fallback – return a placeholder.
|
53
|
+
return "unknown"
|
54
|
+
|
55
|
+
__version__ = _resolve_version()
|
56
|
+
|
57
|
+
|
janito/agent/setup_agent.py
CHANGED
@@ -21,61 +21,122 @@ def setup_agent(
|
|
21
21
|
verbose_tools=False,
|
22
22
|
verbose_agent=False,
|
23
23
|
exec_enabled=False,
|
24
|
+
allowed_permissions=None,
|
25
|
+
profile=None,
|
26
|
+
profile_system_prompt=None,
|
24
27
|
):
|
25
28
|
"""
|
26
|
-
Creates an agent
|
29
|
+
Creates an agent. A system prompt is rendered from a template only when a profile is specified.
|
27
30
|
"""
|
28
31
|
tools_provider = get_local_tools_adapter()
|
29
32
|
tools_provider.set_verbose_tools(verbose_tools)
|
30
33
|
|
31
|
-
|
34
|
+
# If zero_mode is enabled or no profile is given we skip the system prompt.
|
35
|
+
if zero_mode or (profile is None and profile_system_prompt is None):
|
32
36
|
# Pass provider to agent, let agent create driver
|
33
37
|
agent = LLMAgent(
|
34
38
|
provider_instance,
|
35
39
|
tools_provider,
|
36
|
-
agent_name=role or "
|
40
|
+
agent_name=role or "developer",
|
37
41
|
system_prompt=None,
|
42
|
+
input_queue=input_queue,
|
43
|
+
output_queue=output_queue,
|
38
44
|
verbose_agent=verbose_agent,
|
39
45
|
)
|
46
|
+
if role:
|
47
|
+
agent.template_vars["role"] = role
|
40
48
|
return agent
|
41
|
-
#
|
49
|
+
# If profile_system_prompt is set, use it directly
|
50
|
+
if profile_system_prompt is not None:
|
51
|
+
agent = LLMAgent(
|
52
|
+
provider_instance,
|
53
|
+
tools_provider,
|
54
|
+
agent_name=role or "developer",
|
55
|
+
system_prompt=profile_system_prompt,
|
56
|
+
input_queue=input_queue,
|
57
|
+
output_queue=output_queue,
|
58
|
+
verbose_agent=verbose_agent,
|
59
|
+
)
|
60
|
+
agent.template_vars["role"] = role or "developer"
|
61
|
+
agent.template_vars["profile"] = None
|
62
|
+
agent.template_vars["profile_system_prompt"] = profile_system_prompt
|
63
|
+
return agent
|
64
|
+
# Normal flow (profile-specific system prompt)
|
42
65
|
if templates_dir is None:
|
43
66
|
# Set default template directory
|
44
67
|
templates_dir = Path(__file__).parent / "templates" / "profiles"
|
45
|
-
|
68
|
+
template_filename = f"system_prompt_template_{profile}.txt.j2"
|
69
|
+
template_path = templates_dir / template_filename
|
46
70
|
|
47
71
|
template_content = None
|
48
72
|
if template_path.exists():
|
49
73
|
with open(template_path, "r", encoding="utf-8") as file:
|
50
74
|
template_content = file.read()
|
51
75
|
else:
|
52
|
-
# Try package import fallback: janito.agent.templates.profiles.
|
76
|
+
# Try package import fallback: janito.agent.templates.profiles.system_prompt_template_<profile>.txt.j2
|
53
77
|
try:
|
54
78
|
with importlib.resources.files("janito.agent.templates.profiles").joinpath(
|
55
|
-
|
79
|
+
template_filename
|
56
80
|
).open("r", encoding="utf-8") as file:
|
57
81
|
template_content = file.read()
|
58
82
|
except (FileNotFoundError, ModuleNotFoundError, AttributeError):
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
83
|
+
if profile:
|
84
|
+
raise FileNotFoundError(
|
85
|
+
f"[janito] Could not find profile-specific template '{template_filename}' in {template_path} nor in janito.agent.templates.profiles package."
|
86
|
+
)
|
87
|
+
else:
|
88
|
+
warnings.warn(
|
89
|
+
f"[janito] Could not find {template_filename} in {template_path} nor in janito.agent.templates.profiles package."
|
90
|
+
)
|
91
|
+
raise FileNotFoundError(
|
92
|
+
f"Template file not found in either {template_path} or package resource."
|
93
|
+
)
|
65
94
|
|
66
95
|
import time
|
67
96
|
template = Template(template_content)
|
68
97
|
# Prepare context for Jinja2 rendering from llm_driver_config
|
69
98
|
# Compose context for Jinja2 rendering without using to_dict or temperature
|
70
99
|
context = {}
|
71
|
-
context["role"] = role or "
|
72
|
-
|
73
|
-
|
74
|
-
|
100
|
+
context["role"] = role or "developer"
|
101
|
+
context["profile"] = profile
|
102
|
+
# Normalize and inject allowed tool permissions
|
103
|
+
from janito.tools.tool_base import ToolPermissions
|
104
|
+
from janito.tools.permissions import get_global_allowed_permissions
|
105
|
+
|
106
|
+
if allowed_permissions is None:
|
107
|
+
# Fallback to globally configured permissions if not explicitly provided
|
108
|
+
allowed_permissions = get_global_allowed_permissions()
|
109
|
+
|
110
|
+
# Convert ToolPermissions -> string like "rwx" so the Jinja template can use
|
111
|
+
# membership checks such as `'r' in allowed_permissions`.
|
112
|
+
if isinstance(allowed_permissions, ToolPermissions):
|
113
|
+
perm_str = ""
|
114
|
+
if allowed_permissions.read:
|
115
|
+
perm_str += "r"
|
116
|
+
if allowed_permissions.write:
|
117
|
+
perm_str += "w"
|
118
|
+
if allowed_permissions.execute:
|
119
|
+
perm_str += "x"
|
120
|
+
allowed_permissions = perm_str or None # None if empty
|
121
|
+
|
122
|
+
context["allowed_permissions"] = allowed_permissions
|
123
|
+
|
124
|
+
# Inject platform information only when execute permission is granted
|
125
|
+
if allowed_permissions and 'x' in allowed_permissions:
|
75
126
|
pd = PlatformDiscovery()
|
76
127
|
context["platform"] = pd.get_platform_name()
|
77
128
|
context["python_version"] = pd.get_python_version()
|
78
129
|
context["shell_info"] = pd.detect_shell()
|
130
|
+
# DEBUG: Show permissions passed to template
|
131
|
+
from rich import print as rich_print
|
132
|
+
debug_flag = False
|
133
|
+
try:
|
134
|
+
debug_flag = (hasattr(sys, 'argv') and ('--debug' in sys.argv or '--verbose' in sys.argv or '-v' in sys.argv))
|
135
|
+
except Exception:
|
136
|
+
pass
|
137
|
+
if debug_flag:
|
138
|
+
rich_print(f"[bold magenta][DEBUG][/bold magenta] Rendering system prompt template '[cyan]{template_filename}[/cyan]' with allowed_permissions: [yellow]{allowed_permissions}[/yellow]")
|
139
|
+
rich_print(f"[bold magenta][DEBUG][/bold magenta] Template context: [green]{context}[/green]")
|
79
140
|
start_render = time.time()
|
80
141
|
rendered_prompt = template.render(**context)
|
81
142
|
end_render = time.time()
|
@@ -85,13 +146,18 @@ def setup_agent(
|
|
85
146
|
agent = LLMAgent(
|
86
147
|
provider_instance,
|
87
148
|
tools_provider,
|
88
|
-
agent_name=role or "
|
149
|
+
agent_name=role or "developer",
|
89
150
|
system_prompt=rendered_prompt,
|
90
151
|
input_queue=input_queue,
|
91
152
|
output_queue=output_queue,
|
92
153
|
verbose_agent=verbose_agent,
|
93
154
|
)
|
94
155
|
agent.template_vars["role"] = context["role"]
|
156
|
+
agent.template_vars["profile"] = profile
|
157
|
+
# Store template path and context for dynamic prompt refresh
|
158
|
+
agent.system_prompt_template = str(template_path)
|
159
|
+
agent._template_vars = context.copy()
|
160
|
+
agent._original_template_vars = context.copy()
|
95
161
|
return agent
|
96
162
|
|
97
163
|
|
@@ -105,6 +171,9 @@ def create_configured_agent(
|
|
105
171
|
templates_dir=None,
|
106
172
|
zero_mode=False,
|
107
173
|
exec_enabled=False,
|
174
|
+
allowed_permissions=None,
|
175
|
+
profile=None,
|
176
|
+
profile_system_prompt=None,
|
108
177
|
):
|
109
178
|
"""
|
110
179
|
Normalizes agent setup for all CLI modes.
|
@@ -131,6 +200,8 @@ def create_configured_agent(
|
|
131
200
|
input_queue = getattr(driver, "input_queue", None)
|
132
201
|
output_queue = getattr(driver, "output_queue", None)
|
133
202
|
|
203
|
+
# Automatically enable system prompt when a profile is specified
|
204
|
+
|
134
205
|
agent = setup_agent(
|
135
206
|
provider_instance=provider_instance,
|
136
207
|
llm_driver_config=llm_driver_config,
|
@@ -142,6 +213,9 @@ def create_configured_agent(
|
|
142
213
|
verbose_tools=verbose_tools,
|
143
214
|
verbose_agent=verbose_agent,
|
144
215
|
exec_enabled=exec_enabled,
|
216
|
+
allowed_permissions=allowed_permissions,
|
217
|
+
profile=profile,
|
218
|
+
profile_system_prompt=profile_system_prompt,
|
145
219
|
)
|
146
220
|
if driver is not None:
|
147
221
|
agent.driver = driver # Attach driver to agent for thread management
|
@@ -0,0 +1,44 @@
|
|
1
|
+
{# General role setup
|
2
|
+
ex. "Search in code" -> Python Developer -> find(*.py) | Java Developer -> find(*.java)
|
3
|
+
#}
|
4
|
+
You are: {{ role }}
|
5
|
+
|
6
|
+
{# Improves tool selection and platform specific constrains, eg, path format, C:\ vs /path #}
|
7
|
+
{% if allowed_permissions and 'x' in allowed_permissions %}
|
8
|
+
You will be using the following environment:
|
9
|
+
Platform: {{ platform }}
|
10
|
+
Python version: {{ python_version }}
|
11
|
+
Shell/Environment: {{ shell_info }}
|
12
|
+
{% endif %}
|
13
|
+
|
14
|
+
|
15
|
+
|
16
|
+
{% if allowed_permissions and 'r' in allowed_permissions %}
|
17
|
+
Before answering map the questions to artifacts found in the current directory - the current project.
|
18
|
+
{% endif %}
|
19
|
+
|
20
|
+
Respond according to the following guidelines:
|
21
|
+
{% if allowed_permissions %}
|
22
|
+
- Before using the namespace tools provide a short reason
|
23
|
+
{% endif %}
|
24
|
+
{% if allowed_permissions and 'r' in allowed_permissions %}
|
25
|
+
{# Exploratory hint #}
|
26
|
+
- Before answering to the user, explore the content related to the question
|
27
|
+
{# Reduces chunking roundtip #}
|
28
|
+
- When exploring full files content, provide empty range to read the entire files instead of chunked reads
|
29
|
+
{% endif %}
|
30
|
+
{% if allowed_permissions and 'w' in allowed_permissions %}
|
31
|
+
{# Reduce unrequest code verbosity overhead #}
|
32
|
+
- Use the namespace functions to deliver the code changes instead of showing the code.
|
33
|
+
{# Drive edit mode, place holders critical as shown to be crucial to avoid corruption with code placeholders #}
|
34
|
+
- Prefer making localized edits using string replacements. If the required change is extensive, replace the entire file instead, provide full content without placeholders.
|
35
|
+
{# Without this, the LLM choses to create files from a literal interpretation of the purpose and intention #}
|
36
|
+
- Before creating files search the code for the location related to the file purpose
|
37
|
+
{# This will trigger a search for the old names/locations to be updates #}
|
38
|
+
- After moving, removing or renaming functions or classes to different modules, update all imports, references, tests, and documentation to reflect the new locations, then verify functionality.
|
39
|
+
{# Keeping docstrings update is key to have semanatic match between prompts and code #}
|
40
|
+
- Once development or updates are finished, ensure that new or updated packages, modules, functions are properly documented.
|
41
|
+
{# Trying to prevent surrogates generation, found this frequently in gpt4.1/windows #}
|
42
|
+
- 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.
|
43
|
+
{% endif %}
|
44
|
+
|
janito/cli/chat_mode/bindings.py
CHANGED
@@ -3,7 +3,7 @@ Key bindings for Janito Chat CLI.
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
from prompt_toolkit.key_binding import KeyBindings
|
6
|
-
|
6
|
+
from janito.tools.permissions import get_global_allowed_permissions
|
7
7
|
|
8
8
|
class KeyBindingsFactory:
|
9
9
|
@staticmethod
|
@@ -31,7 +31,26 @@ class KeyBindingsFactory:
|
|
31
31
|
@bindings.add("f2")
|
32
32
|
def _(event):
|
33
33
|
buf = event.app.current_buffer
|
34
|
-
|
34
|
+
# Toggle read permission based on current state
|
35
|
+
current = get_global_allowed_permissions()
|
36
|
+
next_state = "off" if getattr(current, "read", False) else "on"
|
37
|
+
buf.text = f"/read {next_state}"
|
38
|
+
buf.validate_and_handle()
|
39
|
+
|
40
|
+
@bindings.add("f3")
|
41
|
+
def _(event):
|
42
|
+
buf = event.app.current_buffer
|
43
|
+
current = get_global_allowed_permissions()
|
44
|
+
next_state = "off" if getattr(current, "write", False) else "on"
|
45
|
+
buf.text = f"/write {next_state}"
|
46
|
+
buf.validate_and_handle()
|
47
|
+
|
48
|
+
@bindings.add("f4")
|
49
|
+
def _(event):
|
50
|
+
buf = event.app.current_buffer
|
51
|
+
current = get_global_allowed_permissions()
|
52
|
+
next_state = "off" if getattr(current, "execute", False) else "on"
|
53
|
+
buf.text = f"/execute {next_state}"
|
35
54
|
buf.validate_and_handle()
|
36
55
|
|
37
56
|
@bindings.add("f12")
|
@@ -10,11 +10,10 @@ from janito.cli.chat_mode.session import ChatSession
|
|
10
10
|
|
11
11
|
def main(args=None):
|
12
12
|
console = Console()
|
13
|
+
console.clear()
|
13
14
|
from janito.version import __version__
|
14
15
|
|
15
|
-
|
16
|
-
f"[bold green]Welcome to the Janito Chat Mode (v{__version__})! Type /exit or press Ctrl+C to quit.[/bold green]"
|
17
|
-
)
|
16
|
+
|
18
17
|
session = ChatSession(console, args=args)
|
19
18
|
session.run()
|
20
19
|
|
@@ -15,5 +15,10 @@ chat_shell_style = Style.from_dict(
|
|
15
15
|
"tokens_in": "fg:#00af5f",
|
16
16
|
"tokens_out": "fg:#01814a",
|
17
17
|
"max-tokens": "fg:#888888",
|
18
|
+
|
19
|
+
|
20
|
+
"key-toggle-on": "bg:#ffd700 fg:#232323 bold",
|
21
|
+
"key-toggle-off": "bg:#444444 fg:#ffffff bold",
|
22
|
+
"cmd-label": "bg:#ff9500 fg:#232323 bold",
|
18
23
|
}
|
19
24
|
)
|
janito/cli/chat_mode/session.py
CHANGED
@@ -3,6 +3,8 @@ Session management for Janito Chat CLI.
|
|
3
3
|
Defines ChatSession and ChatShellState classes.
|
4
4
|
"""
|
5
5
|
|
6
|
+
from __future__ import annotations
|
7
|
+
|
6
8
|
import types
|
7
9
|
from rich.console import Console
|
8
10
|
from rich.rule import Rule
|
@@ -17,23 +19,22 @@ from janito.cli.chat_mode.bindings import KeyBindingsFactory
|
|
17
19
|
from janito.cli.chat_mode.shell.commands import handle_command
|
18
20
|
from janito.cli.chat_mode.shell.autocomplete import ShellCommandCompleter
|
19
21
|
|
22
|
+
# Shared prompt/agent factory
|
23
|
+
from janito.cli.prompt_setup import setup_agent_and_prompt_handler
|
24
|
+
|
20
25
|
|
21
26
|
class ChatShellState:
|
22
27
|
def __init__(self, mem_history, conversation_history):
|
23
|
-
self.allow_execution = False # Controls whether execution tools are enabled
|
24
28
|
self.mem_history = mem_history
|
25
29
|
self.conversation_history = conversation_history
|
26
30
|
self.paste_mode = False
|
27
|
-
self.
|
28
|
-
self.
|
29
|
-
self.
|
30
|
-
self.
|
31
|
+
self._port = None
|
32
|
+
self._pid = None
|
33
|
+
self._stdout_path = None
|
34
|
+
self._stderr_path = None
|
31
35
|
self.livereload_stderr_path = None
|
32
|
-
self.
|
33
|
-
|
34
|
-
None # 'online', 'offline', updated by background checker
|
35
|
-
)
|
36
|
-
self.termweb_live_checked_time = None # datetime.datetime of last status check
|
36
|
+
self._status = "starting" # Tracks the current status (updated by background thread/UI)
|
37
|
+
|
37
38
|
self.last_usage_info = {}
|
38
39
|
self.last_elapsed = None
|
39
40
|
self.main_agent = {}
|
@@ -53,25 +54,10 @@ class ChatSession:
|
|
53
54
|
args=None,
|
54
55
|
verbose_tools=False,
|
55
56
|
verbose_agent=False,
|
56
|
-
exec_enabled=False
|
57
|
+
exec_enabled=False,
|
58
|
+
allowed_permissions=None,
|
57
59
|
):
|
58
|
-
|
59
|
-
if args is not None and hasattr(args, "exec"):
|
60
|
-
allow_execution = bool(getattr(args, "exec", False))
|
61
|
-
else:
|
62
|
-
allow_execution = exec_enabled
|
63
|
-
from janito.cli.prompt_core import PromptHandler as GenericPromptHandler
|
64
|
-
|
65
|
-
self._prompt_handler = GenericPromptHandler(
|
66
|
-
args=None,
|
67
|
-
conversation_history=(
|
68
|
-
None
|
69
|
-
if not hasattr(self, "shell_state")
|
70
|
-
else self.shell_state.conversation_history
|
71
|
-
),
|
72
|
-
provider_instance=provider_instance,
|
73
|
-
)
|
74
|
-
self._prompt_handler.agent = None # Will be set below if agent exists
|
60
|
+
|
75
61
|
self.console = console
|
76
62
|
self.user_input_history = UserInputHistory()
|
77
63
|
self.input_dicts = self.user_input_history.load()
|
@@ -81,27 +67,57 @@ class ChatSession:
|
|
81
67
|
self.mem_history.append_string(item["input"])
|
82
68
|
self.provider_instance = provider_instance
|
83
69
|
self.llm_driver_config = llm_driver_config
|
84
|
-
from janito.agent.setup_agent import create_configured_agent
|
85
70
|
|
86
|
-
|
71
|
+
# --- Profile selection (interactive) ---------------------------------
|
72
|
+
profile = getattr(args, "profile", None) if args is not None else None
|
73
|
+
profile_system_prompt = None
|
74
|
+
if profile is None:
|
75
|
+
try:
|
76
|
+
from janito.cli.chat_mode.session_profile_select import select_profile
|
77
|
+
|
78
|
+
result = select_profile()
|
79
|
+
if (
|
80
|
+
isinstance(result, dict)
|
81
|
+
and result.get("profile") is None
|
82
|
+
and result.get("profile_system_prompt")
|
83
|
+
):
|
84
|
+
profile_system_prompt = result["profile_system_prompt"]
|
85
|
+
elif isinstance(result, str) and result.startswith("role:"):
|
86
|
+
role = result[len("role:") :].strip()
|
87
|
+
profile = "developer"
|
88
|
+
else:
|
89
|
+
profile = (
|
90
|
+
"developer" if result == "software developer" else result
|
91
|
+
)
|
92
|
+
except ImportError:
|
93
|
+
profile = "helpful assistant"
|
94
|
+
|
95
|
+
# ---------------------------------------------------------------------
|
96
|
+
from janito.conversation_history import LLMConversationHistory
|
97
|
+
|
98
|
+
conversation_history = LLMConversationHistory()
|
99
|
+
|
100
|
+
# Build agent and core prompt handler via the shared helper
|
101
|
+
self.agent, self._prompt_handler = setup_agent_and_prompt_handler(
|
102
|
+
args=args,
|
87
103
|
provider_instance=provider_instance,
|
88
104
|
llm_driver_config=llm_driver_config,
|
89
105
|
role=role,
|
90
106
|
verbose_tools=verbose_tools,
|
91
107
|
verbose_agent=verbose_agent,
|
92
|
-
exec_enabled=
|
108
|
+
exec_enabled=exec_enabled,
|
109
|
+
allowed_permissions=allowed_permissions,
|
110
|
+
profile=profile,
|
111
|
+
profile_system_prompt=profile_system_prompt,
|
112
|
+
conversation_history=conversation_history,
|
93
113
|
)
|
94
|
-
from janito.conversation_history import LLMConversationHistory
|
95
114
|
|
96
|
-
self.shell_state = ChatShellState(self.mem_history,
|
97
|
-
self.shell_state.agent = agent
|
98
|
-
self.shell_state.allow_execution = allow_execution
|
99
|
-
self.agent = agent
|
115
|
+
self.shell_state = ChatShellState(self.mem_history, conversation_history)
|
116
|
+
self.shell_state.agent = self.agent
|
100
117
|
# Filter execution tools at startup
|
101
118
|
try:
|
102
|
-
registry
|
103
|
-
|
104
|
-
registry.set_execution_tools_enabled(allow_execution)
|
119
|
+
# Permissions are now managed globally; registry filtering is automatic
|
120
|
+
getattr(__import__('janito.tools', fromlist=['get_local_tools_adapter']), 'get_local_tools_adapter')()
|
105
121
|
except Exception as e:
|
106
122
|
self.console.print(f"[yellow]Warning: Could not filter execution tools at startup: {e}[/yellow]")
|
107
123
|
from janito.perf_singleton import performance_collector
|
@@ -114,76 +130,46 @@ class ChatSession:
|
|
114
130
|
self.shell_state.conversation_history
|
115
131
|
)
|
116
132
|
|
117
|
-
|
118
|
-
self.termweb_support = False
|
133
|
+
self._support = False
|
119
134
|
if args and getattr(args, "web", False):
|
120
|
-
self.
|
121
|
-
self.shell_state.
|
122
|
-
from janito.cli.
|
123
|
-
from janito.cli.config import
|
135
|
+
self._support = True
|
136
|
+
self.shell_state._support = self._support
|
137
|
+
from janito.cli._starter import _start_and_watch
|
138
|
+
from janito.cli.config import get__port
|
124
139
|
import threading
|
125
140
|
from rich.console import Console
|
126
141
|
|
127
|
-
Console().print("[yellow]Starting
|
128
|
-
self.
|
129
|
-
|
130
|
-
self.shell_state, self.
|
142
|
+
Console().print("[yellow]Starting in background...[/yellow]")
|
143
|
+
self._lock = threading.Lock()
|
144
|
+
_thread = _start_and_watch(
|
145
|
+
self.shell_state, self._lock, get__port()
|
131
146
|
)
|
132
147
|
# Initial status is set to 'starting' by constructor; the watcher will update
|
133
|
-
self.
|
148
|
+
self._thread = _thread
|
134
149
|
|
135
|
-
# Start a background timer to update live
|
150
|
+
# Start a background timer to update live status (for UI responsiveness)
|
136
151
|
import threading, datetime
|
137
152
|
|
138
|
-
|
139
|
-
while True:
|
140
|
-
with self.termweb_lock:
|
141
|
-
port = getattr(self.shell_state, "termweb_port", None)
|
142
|
-
if port:
|
143
|
-
try:
|
144
|
-
# is_termweb_running is removed; inline health check here:
|
145
|
-
try:
|
146
|
-
import http.client
|
147
|
-
|
148
|
-
conn = http.client.HTTPConnection(
|
149
|
-
"localhost", port, timeout=0.5
|
150
|
-
)
|
151
|
-
conn.request("GET", "/")
|
152
|
-
resp = conn.getresponse()
|
153
|
-
running = resp.status == 200
|
154
|
-
except Exception:
|
155
|
-
running = False
|
156
|
-
self.shell_state.termweb_live_status = (
|
157
|
-
"online" if running else "offline"
|
158
|
-
)
|
159
|
-
except Exception:
|
160
|
-
self.shell_state.termweb_live_status = "offline"
|
161
|
-
self.shell_state.termweb_live_checked_time = (
|
162
|
-
datetime.datetime.now()
|
163
|
-
)
|
164
|
-
else:
|
165
|
-
self.shell_state.termweb_live_status = None
|
166
|
-
self.shell_state.termweb_live_checked_time = (
|
167
|
-
datetime.datetime.now()
|
168
|
-
)
|
169
|
-
# sleep outside lock
|
170
|
-
threading.Event().wait(1.0)
|
171
|
-
|
172
|
-
self._termweb_liveness_thread = threading.Thread(
|
173
|
-
target=update_termweb_liveness, daemon=True
|
174
|
-
)
|
175
|
-
self._termweb_liveness_thread.start()
|
176
|
-
# No queue or blocking checks; UI (and timer) will observe self.shell_state fields
|
153
|
+
# Health check and liveness thread removed as per refactor to eliminate localhost references.
|
177
154
|
|
178
155
|
else:
|
179
|
-
self.shell_state.
|
180
|
-
self.shell_state.
|
156
|
+
self.shell_state._support = False
|
157
|
+
self.shell_state._status = "offline"
|
181
158
|
|
182
159
|
def run(self):
|
183
|
-
|
160
|
+
self.console.clear()
|
161
|
+
from janito import __version__
|
184
162
|
self.console.print(
|
185
|
-
"[bold green]
|
163
|
+
f"[bold green]Janito Chat Mode v{__version__}[/bold green]"
|
186
164
|
)
|
165
|
+
self.console.print("[green]/help for commands /exit or Ctrl+C to quit[/green]")
|
166
|
+
|
167
|
+
# Inform user if no privileges are enabled
|
168
|
+
from janito.cli.chat_mode.shell.commands._priv_check import user_has_any_privileges
|
169
|
+
if not user_has_any_privileges():
|
170
|
+
self.console.print("[yellow]Note: You currently have no privileges enabled. If you need to interact with files or the system, enable permissions using /read on, /write on, or /execute on.[/yellow]")
|
171
|
+
|
172
|
+
session = self._create_prompt_session()
|
187
173
|
self._chat_loop(session)
|
188
174
|
|
189
175
|
def _chat_loop(self, session):
|