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.
Files changed (150) hide show
  1. janito/__init__.py +6 -6
  2. janito/_version.py +57 -0
  3. janito/agent/setup_agent.py +92 -18
  4. janito/agent/templates/profiles/system_prompt_template_developer.txt.j2 +44 -0
  5. janito/cli/chat_mode/bindings.py +21 -2
  6. janito/cli/chat_mode/chat_entry.py +2 -3
  7. janito/cli/chat_mode/prompt_style.py +5 -0
  8. janito/cli/chat_mode/session.py +80 -94
  9. janito/cli/chat_mode/session_profile_select.py +80 -0
  10. janito/cli/chat_mode/shell/autocomplete.py +21 -21
  11. janito/cli/chat_mode/shell/commands/__init__.py +13 -7
  12. janito/cli/chat_mode/shell/commands/_priv_check.py +5 -0
  13. janito/cli/chat_mode/shell/commands/clear.py +12 -12
  14. janito/cli/chat_mode/shell/commands/conversation_restart.py +30 -0
  15. janito/cli/chat_mode/shell/commands/execute.py +42 -0
  16. janito/cli/chat_mode/shell/commands/help.py +6 -3
  17. janito/cli/chat_mode/shell/commands/model.py +28 -0
  18. janito/cli/chat_mode/shell/commands/multi.py +51 -51
  19. janito/cli/chat_mode/shell/commands/read.py +37 -0
  20. janito/cli/chat_mode/shell/commands/tools.py +45 -18
  21. janito/cli/chat_mode/shell/commands/write.py +37 -0
  22. janito/cli/chat_mode/shell/commands.bak.zip +0 -0
  23. janito/cli/chat_mode/shell/input_history.py +62 -62
  24. janito/cli/chat_mode/shell/session.bak.zip +0 -0
  25. janito/cli/chat_mode/toolbar.py +44 -27
  26. janito/cli/cli_commands/list_models.py +35 -35
  27. janito/cli/cli_commands/list_providers.py +9 -9
  28. janito/cli/cli_commands/list_tools.py +86 -53
  29. janito/cli/cli_commands/model_selection.py +50 -50
  30. janito/cli/cli_commands/set_api_key.py +19 -19
  31. janito/cli/cli_commands/show_config.py +51 -51
  32. janito/cli/cli_commands/show_system_prompt.py +105 -62
  33. janito/cli/config.py +5 -6
  34. janito/cli/core/__init__.py +4 -4
  35. janito/cli/core/event_logger.py +59 -59
  36. janito/cli/core/runner.py +25 -18
  37. janito/cli/core/setters.py +10 -1
  38. janito/cli/core/unsetters.py +54 -54
  39. janito/cli/main_cli.py +28 -5
  40. janito/cli/prompt_core.py +18 -2
  41. janito/cli/prompt_setup.py +56 -0
  42. janito/cli/single_shot_mode/__init__.py +6 -6
  43. janito/cli/single_shot_mode/handler.py +14 -73
  44. janito/cli/verbose_output.py +1 -1
  45. janito/config.py +5 -5
  46. janito/config_manager.py +13 -0
  47. janito/drivers/anthropic/driver.py +113 -113
  48. janito/drivers/dashscope.bak.zip +0 -0
  49. janito/drivers/openai/README.md +20 -0
  50. janito/drivers/openai_responses.bak.zip +0 -0
  51. janito/event_bus/event.py +2 -2
  52. janito/formatting_token.py +54 -54
  53. janito/i18n/__init__.py +35 -35
  54. janito/i18n/messages.py +23 -23
  55. janito/i18n/pt.py +46 -47
  56. janito/llm/README.md +23 -0
  57. janito/llm/__init__.py +5 -5
  58. janito/llm/agent.py +507 -443
  59. janito/llm/driver.py +8 -0
  60. janito/llm/driver_config_builder.py +34 -34
  61. janito/llm/driver_input.py +12 -12
  62. janito/llm/message_parts.py +60 -60
  63. janito/llm/model.py +38 -38
  64. janito/llm/provider.py +196 -196
  65. janito/provider_registry.py +8 -6
  66. janito/providers/anthropic/model_info.py +22 -22
  67. janito/providers/anthropic/provider.py +2 -0
  68. janito/providers/azure_openai/provider.py +3 -0
  69. janito/providers/dashscope.bak.zip +0 -0
  70. janito/providers/deepseek/__init__.py +1 -1
  71. janito/providers/deepseek/model_info.py +16 -16
  72. janito/providers/deepseek/provider.py +94 -91
  73. janito/providers/google/provider.py +3 -0
  74. janito/providers/mistralai/provider.py +3 -0
  75. janito/providers/openai/provider.py +4 -0
  76. janito/providers/registry.py +26 -26
  77. janito/shell.bak.zip +0 -0
  78. janito/tools/DOCSTRING_STANDARD.txt +33 -0
  79. janito/tools/README.md +3 -0
  80. janito/tools/__init__.py +20 -6
  81. janito/tools/adapters/__init__.py +1 -1
  82. janito/tools/adapters/local/__init__.py +65 -62
  83. janito/tools/adapters/local/adapter.py +18 -35
  84. janito/tools/adapters/local/ask_user.py +101 -102
  85. janito/tools/adapters/local/copy_file.py +84 -84
  86. janito/tools/adapters/local/create_directory.py +69 -69
  87. janito/tools/adapters/local/create_file.py +82 -82
  88. janito/tools/adapters/local/delete_text_in_file.py +2 -2
  89. janito/tools/adapters/local/fetch_url.py +97 -97
  90. janito/tools/adapters/local/find_files.py +139 -138
  91. janito/tools/adapters/local/get_file_outline/__init__.py +1 -1
  92. janito/tools/adapters/local/get_file_outline/core.py +117 -117
  93. janito/tools/adapters/local/get_file_outline/java_outline.py +40 -40
  94. janito/tools/adapters/local/get_file_outline/markdown_outline.py +14 -14
  95. janito/tools/adapters/local/get_file_outline/python_outline.py +303 -303
  96. janito/tools/adapters/local/get_file_outline/python_outline_v2.py +156 -156
  97. janito/tools/adapters/local/get_file_outline/search_outline.py +33 -33
  98. janito/tools/adapters/local/move_file.py +2 -2
  99. janito/tools/adapters/local/open_html_in_browser.py +2 -1
  100. janito/tools/adapters/local/open_url.py +2 -2
  101. janito/tools/adapters/local/python_code_run.py +166 -166
  102. janito/tools/adapters/local/python_command_run.py +164 -164
  103. janito/tools/adapters/local/python_file_run.py +163 -163
  104. janito/tools/adapters/local/remove_directory.py +2 -2
  105. janito/tools/adapters/local/remove_file.py +2 -2
  106. janito/tools/adapters/local/replace_text_in_file.py +2 -2
  107. janito/tools/adapters/local/run_bash_command.py +176 -176
  108. janito/tools/adapters/local/run_powershell_command.py +219 -219
  109. janito/tools/adapters/local/search_text/__init__.py +1 -1
  110. janito/tools/adapters/local/search_text/core.py +201 -201
  111. janito/tools/adapters/local/search_text/pattern_utils.py +73 -73
  112. janito/tools/adapters/local/search_text/traverse_directory.py +145 -145
  113. janito/tools/adapters/local/validate_file_syntax/__init__.py +1 -1
  114. janito/tools/adapters/local/validate_file_syntax/core.py +106 -106
  115. janito/tools/adapters/local/validate_file_syntax/css_validator.py +35 -35
  116. janito/tools/adapters/local/validate_file_syntax/html_validator.py +93 -93
  117. janito/tools/adapters/local/validate_file_syntax/js_validator.py +27 -27
  118. janito/tools/adapters/local/validate_file_syntax/json_validator.py +6 -6
  119. janito/tools/adapters/local/validate_file_syntax/markdown_validator.py +109 -109
  120. janito/tools/adapters/local/validate_file_syntax/ps1_validator.py +32 -32
  121. janito/tools/adapters/local/validate_file_syntax/python_validator.py +5 -5
  122. janito/tools/adapters/local/validate_file_syntax/xml_validator.py +11 -11
  123. janito/tools/adapters/local/validate_file_syntax/yaml_validator.py +6 -6
  124. janito/tools/adapters/local/view_file.py +168 -167
  125. janito/tools/inspect_registry.py +17 -17
  126. janito/tools/outline_file.bak.zip +0 -0
  127. janito/tools/permissions.py +45 -0
  128. janito/tools/permissions_parse.py +12 -0
  129. janito/tools/tool_base.py +118 -105
  130. janito/tools/tool_events.py +58 -58
  131. janito/tools/tool_run_exception.py +12 -12
  132. janito/tools/tool_use_tracker.py +81 -81
  133. janito/tools/tool_utils.py +43 -45
  134. janito/tools/tools_adapter.py +25 -20
  135. janito/tools/tools_schema.py +104 -104
  136. {janito-2.3.0.dist-info → janito-2.4.0.dist-info}/METADATA +425 -388
  137. janito-2.4.0.dist-info/RECORD +195 -0
  138. janito/agent/templates/profiles/system_prompt_template_base_pt.txt.j2 +0 -13
  139. janito/agent/templates/profiles/system_prompt_template_main.txt.j2 +0 -37
  140. janito/cli/chat_mode/shell/commands/edit.py +0 -25
  141. janito/cli/chat_mode/shell/commands/exec.py +0 -27
  142. janito/cli/chat_mode/shell/commands/termweb_log.py +0 -92
  143. janito/cli/termweb_starter.py +0 -122
  144. janito/termweb/app.py +0 -95
  145. janito/version.py +0 -4
  146. janito-2.3.0.dist-info/RECORD +0 -181
  147. {janito-2.3.0.dist-info → janito-2.4.0.dist-info}/WHEEL +0 -0
  148. {janito-2.3.0.dist-info → janito-2.4.0.dist-info}/entry_points.txt +0 -0
  149. {janito-2.3.0.dist-info → janito-2.4.0.dist-info}/licenses/LICENSE +0 -0
  150. {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
- __version__ = "2.3.0"
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
+
@@ -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 using a rendered system prompt template, passing an explicit role.
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
- if zero_mode:
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 "software developer",
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
- # Normal flow
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
- template_path = templates_dir / "system_prompt_template_main.txt.j2"
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.system_prompt_template_main.txt.j2
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
- "system_prompt_template_main.txt.j2"
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
- warnings.warn(
60
- f"[janito] Could not find system_prompt_template_main.txt.j2 in {template_path} nor in janito.agent.templates.profiles package."
61
- )
62
- raise FileNotFoundError(
63
- f"Template file not found in either {template_path} or package resource."
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 "software developer"
72
- # Inject current platform environment information only if exec_enabled
73
- context["exec_enabled"] = bool(exec_enabled)
74
- if exec_enabled:
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 "software developer",
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
+
@@ -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
- buf.text = "/exec on"
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
- console.print(
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
  )
@@ -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.termweb_port = None
28
- self.termweb_pid = None
29
- self.termweb_stdout_path = None
30
- self.termweb_stderr_path = None
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.termweb_status = "starting" # Tracks the current termweb status (updated by background thread/UI)
33
- self.termweb_live_status = (
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
- # Set allow_execution from exec_enabled or args
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
- agent = create_configured_agent(
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=allow_execution
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, LLMConversationHistory())
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 = getattr(__import__('janito.tools', fromlist=['get_local_tools_adapter']), 'get_local_tools_adapter')()
103
- if hasattr(registry, 'set_execution_tools_enabled'):
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
- # TERMWEB logic migrated from runner
118
- self.termweb_support = False
133
+ self._support = False
119
134
  if args and getattr(args, "web", False):
120
- self.termweb_support = True
121
- self.shell_state.termweb_support = self.termweb_support
122
- from janito.cli.termweb_starter import termweb_start_and_watch
123
- from janito.cli.config import get_termweb_port
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 termweb in background...[/yellow]")
128
- self.termweb_lock = threading.Lock()
129
- termweb_thread = termweb_start_and_watch(
130
- self.shell_state, self.termweb_lock, get_termweb_port()
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.termweb_thread = termweb_thread
148
+ self._thread = _thread
134
149
 
135
- # Start a background timer to update live termweb status (for UI responsiveness)
150
+ # Start a background timer to update live status (for UI responsiveness)
136
151
  import threading, datetime
137
152
 
138
- def update_termweb_liveness():
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.termweb_support = False
180
- self.shell_state.termweb_status = "offline"
156
+ self.shell_state._support = False
157
+ self.shell_state._status = "offline"
181
158
 
182
159
  def run(self):
183
- session = self._create_prompt_session()
160
+ self.console.clear()
161
+ from janito import __version__
184
162
  self.console.print(
185
- "[bold green]Type /help for commands. Type /exit or press Ctrl+C to quit.[/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):