janito 2.5.0__py3-none-any.whl → 2.6.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 (67) hide show
  1. janito/agent/setup_agent.py +231 -222
  2. janito/agent/templates/profiles/system_prompt_template_software_developer.txt.j2 +39 -0
  3. janito/cli/chat_mode/bindings.py +1 -26
  4. janito/cli/chat_mode/session.py +282 -294
  5. janito/cli/chat_mode/session_profile_select.py +125 -55
  6. janito/cli/chat_mode/shell/commands/conversation_restart.py +3 -4
  7. janito/cli/chat_mode/shell/commands/execute.py +1 -2
  8. janito/cli/chat_mode/shell/commands/prompt.py +1 -8
  9. janito/cli/chat_mode/shell/commands/read.py +1 -2
  10. janito/cli/chat_mode/shell/commands/role.py +2 -9
  11. janito/cli/chat_mode/shell/commands/tools.py +51 -48
  12. janito/cli/chat_mode/shell/commands/write.py +1 -2
  13. janito/cli/chat_mode/toolbar.py +42 -68
  14. janito/cli/cli_commands/list_tools.py +41 -56
  15. janito/cli/cli_commands/show_system_prompt.py +70 -49
  16. janito/cli/core/runner.py +6 -1
  17. janito/cli/core/setters.py +43 -34
  18. janito/cli/main_cli.py +25 -1
  19. janito/cli/prompt_core.py +76 -69
  20. janito/cli/rich_terminal_reporter.py +22 -1
  21. janito/cli/single_shot_mode/handler.py +95 -94
  22. janito/drivers/driver_registry.py +27 -29
  23. janito/drivers/openai/driver.py +436 -494
  24. janito/llm/agent.py +55 -69
  25. janito/provider_registry.py +178 -178
  26. janito/providers/anthropic/model_info.py +41 -22
  27. janito/providers/anthropic/provider.py +80 -67
  28. janito/providers/provider_static_info.py +18 -17
  29. janito/tools/adapters/local/__init__.py +66 -65
  30. janito/tools/adapters/local/adapter.py +79 -18
  31. janito/tools/adapters/local/create_directory.py +9 -9
  32. janito/tools/adapters/local/create_file.py +12 -12
  33. janito/tools/adapters/local/delete_text_in_file.py +16 -16
  34. janito/tools/adapters/local/find_files.py +2 -2
  35. janito/tools/adapters/local/get_file_outline/core.py +5 -5
  36. janito/tools/adapters/local/get_file_outline/search_outline.py +4 -4
  37. janito/tools/adapters/local/open_html_in_browser.py +15 -15
  38. janito/tools/adapters/local/python_file_run.py +4 -4
  39. janito/tools/adapters/local/read_files.py +40 -0
  40. janito/tools/adapters/local/remove_directory.py +5 -5
  41. janito/tools/adapters/local/remove_file.py +4 -4
  42. janito/tools/adapters/local/replace_text_in_file.py +21 -21
  43. janito/tools/adapters/local/run_bash_command.py +1 -1
  44. janito/tools/adapters/local/search_text/pattern_utils.py +2 -2
  45. janito/tools/adapters/local/search_text/traverse_directory.py +10 -10
  46. janito/tools/adapters/local/validate_file_syntax/core.py +7 -7
  47. janito/tools/adapters/local/validate_file_syntax/css_validator.py +2 -2
  48. janito/tools/adapters/local/validate_file_syntax/html_validator.py +7 -7
  49. janito/tools/adapters/local/validate_file_syntax/js_validator.py +2 -2
  50. janito/tools/adapters/local/validate_file_syntax/json_validator.py +2 -2
  51. janito/tools/adapters/local/validate_file_syntax/markdown_validator.py +2 -2
  52. janito/tools/adapters/local/validate_file_syntax/ps1_validator.py +2 -2
  53. janito/tools/adapters/local/validate_file_syntax/python_validator.py +2 -2
  54. janito/tools/adapters/local/validate_file_syntax/xml_validator.py +2 -2
  55. janito/tools/adapters/local/validate_file_syntax/yaml_validator.py +2 -2
  56. janito/tools/adapters/local/view_file.py +12 -12
  57. janito/tools/path_security.py +204 -0
  58. janito/tools/tool_use_tracker.py +12 -12
  59. janito/tools/tools_adapter.py +66 -34
  60. {janito-2.5.0.dist-info → janito-2.6.0.dist-info}/METADATA +412 -412
  61. {janito-2.5.0.dist-info → janito-2.6.0.dist-info}/RECORD +65 -64
  62. janito/drivers/anthropic/driver.py +0 -113
  63. janito/tools/adapters/local/get_file_outline/python_outline_v2.py +0 -156
  64. {janito-2.5.0.dist-info → janito-2.6.0.dist-info}/WHEEL +0 -0
  65. {janito-2.5.0.dist-info → janito-2.6.0.dist-info}/entry_points.txt +0 -0
  66. {janito-2.5.0.dist-info → janito-2.6.0.dist-info}/licenses/LICENSE +0 -0
  67. {janito-2.5.0.dist-info → janito-2.6.0.dist-info}/top_level.txt +0 -0
@@ -1,222 +1,231 @@
1
- from pathlib import Path
2
- from jinja2 import Template
3
- import importlib.resources
4
- import sys
5
- import warnings
6
- from janito.tools import get_local_tools_adapter
7
- from janito.llm.agent import LLMAgent
8
- from janito.drivers.driver_registry import get_driver_class
9
- from queue import Queue
10
- from janito.platform_discovery import PlatformDiscovery
11
-
12
-
13
- def setup_agent(
14
- provider_instance,
15
- llm_driver_config,
16
- role=None,
17
- templates_dir=None,
18
- zero_mode=False,
19
- input_queue=None,
20
- output_queue=None,
21
- verbose_tools=False,
22
- verbose_agent=False,
23
-
24
- allowed_permissions=None,
25
- profile=None,
26
- profile_system_prompt=None,
27
- ):
28
- """
29
- Creates an agent. A system prompt is rendered from a template only when a profile is specified.
30
- """
31
- tools_provider = get_local_tools_adapter()
32
- tools_provider.set_verbose_tools(verbose_tools)
33
-
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):
36
- # Pass provider to agent, let agent create driver
37
- agent = LLMAgent(
38
- provider_instance,
39
- tools_provider,
40
- agent_name=role or "developer",
41
- system_prompt=None,
42
- input_queue=input_queue,
43
- output_queue=output_queue,
44
- verbose_agent=verbose_agent,
45
- )
46
- if role:
47
- agent.template_vars["role"] = role
48
- return agent
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)
65
- if templates_dir is None:
66
- # Set default template directory
67
- templates_dir = Path(__file__).parent / "templates" / "profiles"
68
- template_filename = f"system_prompt_template_{profile}.txt.j2"
69
- template_path = templates_dir / template_filename
70
-
71
- template_content = None
72
- if template_path.exists():
73
- with open(template_path, "r", encoding="utf-8") as file:
74
- template_content = file.read()
75
- else:
76
- # Try package import fallback: janito.agent.templates.profiles.system_prompt_template_<profile>.txt.j2
77
- try:
78
- with importlib.resources.files("janito.agent.templates.profiles").joinpath(
79
- template_filename
80
- ).open("r", encoding="utf-8") as file:
81
- template_content = file.read()
82
- except (FileNotFoundError, ModuleNotFoundError, AttributeError):
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
- )
94
-
95
- import time
96
- template = Template(template_content)
97
- # Prepare context for Jinja2 rendering from llm_driver_config
98
- # Compose context for Jinja2 rendering without using to_dict or temperature
99
- context = {}
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:
126
- pd = PlatformDiscovery()
127
- context["platform"] = pd.get_platform_name()
128
- context["python_version"] = pd.get_python_version()
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]")
140
- start_render = time.time()
141
- rendered_prompt = template.render(**context)
142
- end_render = time.time()
143
-
144
- # Create the agent as before, now passing the explicit role
145
- # Do NOT pass temperature; do not depend on to_dict
146
- agent = LLMAgent(
147
- provider_instance,
148
- tools_provider,
149
- agent_name=role or "developer",
150
- system_prompt=rendered_prompt,
151
- input_queue=input_queue,
152
- output_queue=output_queue,
153
- verbose_agent=verbose_agent,
154
- )
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()
161
- return agent
162
-
163
-
164
- def create_configured_agent(
165
- *,
166
- provider_instance=None,
167
- llm_driver_config=None,
168
- role=None,
169
- verbose_tools=False,
170
- verbose_agent=False,
171
- templates_dir=None,
172
- zero_mode=False,
173
-
174
- allowed_permissions=None,
175
- profile=None,
176
- profile_system_prompt=None,
177
- ):
178
- """
179
- Normalizes agent setup for all CLI modes.
180
-
181
- Args:
182
- provider_instance: Provider instance for the agent
183
- llm_driver_config: LLM driver configuration
184
- role: Optional role string
185
- verbose_tools: Optional, default False
186
- verbose_agent: Optional, default False
187
- templates_dir: Optional
188
- zero_mode: Optional, default False
189
-
190
- Returns:
191
- Configured agent instance
192
- """
193
- # If provider_instance has create_driver, wire queues (single-shot mode)
194
- input_queue = None
195
- output_queue = None
196
- driver = None
197
- if hasattr(provider_instance, "create_driver"):
198
- driver = provider_instance.create_driver()
199
- driver.start() # Ensure the driver background thread is started
200
- input_queue = getattr(driver, "input_queue", None)
201
- output_queue = getattr(driver, "output_queue", None)
202
-
203
- # Automatically enable system prompt when a profile is specified
204
-
205
- agent = setup_agent(
206
- provider_instance=provider_instance,
207
- llm_driver_config=llm_driver_config,
208
- role=role,
209
- templates_dir=templates_dir,
210
- zero_mode=zero_mode,
211
- input_queue=input_queue,
212
- output_queue=output_queue,
213
- verbose_tools=verbose_tools,
214
- verbose_agent=verbose_agent,
215
-
216
- allowed_permissions=allowed_permissions,
217
- profile=profile,
218
- profile_system_prompt=profile_system_prompt,
219
- )
220
- if driver is not None:
221
- agent.driver = driver # Attach driver to agent for thread management
222
- return agent
1
+ import importlib.resources
2
+ import re
3
+ import sys
4
+ import time
5
+ import warnings
6
+ import threading
7
+ from pathlib import Path
8
+ from jinja2 import Template
9
+ from rich import print as rich_print
10
+ from queue import Queue
11
+ from janito.tools import get_local_tools_adapter
12
+ from janito.llm.agent import LLMAgent
13
+ from janito.drivers.driver_registry import get_driver_class
14
+ from janito.platform_discovery import PlatformDiscovery
15
+ from janito.tools.tool_base import ToolPermissions
16
+ from janito.tools.permissions import get_global_allowed_permissions
17
+
18
+
19
+ def _load_template_content(profile, templates_dir):
20
+ """
21
+ Loads the template content for the given profile from the specified directory or package resources.
22
+ """
23
+ template_filename = f"system_prompt_template_{profile}.txt.j2"
24
+ template_path = templates_dir / template_filename
25
+ if template_path.exists():
26
+ with open(template_path, "r", encoding="utf-8") as file:
27
+ return file.read(), template_path
28
+ # Try package import fallback
29
+ try:
30
+ with importlib.resources.files("janito.agent.templates.profiles").joinpath(
31
+ template_filename
32
+ ).open("r", encoding="utf-8") as file:
33
+ return file.read(), template_path
34
+ except (FileNotFoundError, ModuleNotFoundError, AttributeError):
35
+ raise FileNotFoundError(
36
+ f"[janito] Could not find profile-specific template '{template_filename}' in {template_path} nor in janito.agent.templates.profiles package."
37
+ )
38
+
39
+
40
+ def _prepare_template_context(role, profile, allowed_permissions):
41
+ """
42
+ Prepares the context dictionary for Jinja2 template rendering.
43
+ """
44
+ context = {}
45
+ context["role"] = role or "developer"
46
+ context["profile"] = profile
47
+ if allowed_permissions is None:
48
+ allowed_permissions = get_global_allowed_permissions()
49
+ # Convert ToolPermissions -> string like "rwx"
50
+ if isinstance(allowed_permissions, ToolPermissions):
51
+ perm_str = ""
52
+ if allowed_permissions.read:
53
+ perm_str += "r"
54
+ if allowed_permissions.write:
55
+ perm_str += "w"
56
+ if allowed_permissions.execute:
57
+ perm_str += "x"
58
+ allowed_permissions = perm_str or None
59
+ context["allowed_permissions"] = allowed_permissions
60
+ # Inject platform info if execute permission is present
61
+ if allowed_permissions and 'x' in allowed_permissions:
62
+ pd = PlatformDiscovery()
63
+ context["platform"] = pd.get_platform_name()
64
+ context["python_version"] = pd.get_python_version()
65
+ context["shell_info"] = pd.detect_shell()
66
+ return context
67
+
68
+
69
+ def _create_agent(provider_instance, tools_provider, role, system_prompt, input_queue, output_queue, verbose_agent, context, template_path, profile):
70
+ """
71
+ Creates and returns an LLMAgent instance with the provided parameters.
72
+ """
73
+ agent = LLMAgent(
74
+ provider_instance,
75
+ tools_provider,
76
+ agent_name=role or "developer",
77
+ system_prompt=system_prompt,
78
+ input_queue=input_queue,
79
+ output_queue=output_queue,
80
+ verbose_agent=verbose_agent,
81
+ )
82
+ agent.template_vars["role"] = context["role"]
83
+ agent.template_vars["profile"] = profile
84
+ agent.system_prompt_template = str(template_path)
85
+ agent._template_vars = context.copy()
86
+ agent._original_template_vars = context.copy()
87
+ return agent
88
+
89
+
90
+ def setup_agent(
91
+ provider_instance,
92
+ llm_driver_config,
93
+ role=None,
94
+ templates_dir=None,
95
+ zero_mode=False,
96
+ input_queue=None,
97
+ output_queue=None,
98
+ verbose_tools=False,
99
+ verbose_agent=False,
100
+ allowed_permissions=None,
101
+ profile=None,
102
+ profile_system_prompt=None,
103
+ ):
104
+ """
105
+ Creates an agent. A system prompt is rendered from a template only when a profile is specified.
106
+ """
107
+ tools_provider = get_local_tools_adapter()
108
+ tools_provider.set_verbose_tools(verbose_tools)
109
+
110
+ # If zero_mode is enabled or no profile is given we skip the system prompt.
111
+ if zero_mode or (profile is None and profile_system_prompt is None):
112
+ agent = LLMAgent(
113
+ provider_instance,
114
+ tools_provider,
115
+ agent_name=role or "developer",
116
+ system_prompt=None,
117
+ input_queue=input_queue,
118
+ output_queue=output_queue,
119
+ verbose_agent=verbose_agent,
120
+ )
121
+ if role:
122
+ agent.template_vars["role"] = role
123
+ return agent
124
+
125
+ # If profile_system_prompt is set, use it directly
126
+ if profile_system_prompt is not None:
127
+ agent = LLMAgent(
128
+ provider_instance,
129
+ tools_provider,
130
+ agent_name=role or "developer",
131
+ system_prompt=profile_system_prompt,
132
+ input_queue=input_queue,
133
+ output_queue=output_queue,
134
+ verbose_agent=verbose_agent,
135
+ )
136
+ agent.template_vars["role"] = role or "developer"
137
+ agent.template_vars["profile"] = None
138
+ agent.template_vars["profile_system_prompt"] = profile_system_prompt
139
+ return agent
140
+
141
+ # Normal flow (profile-specific system prompt)
142
+ if templates_dir is None:
143
+ templates_dir = Path(__file__).parent / "templates" / "profiles"
144
+ template_content, template_path = _load_template_content(profile, templates_dir)
145
+
146
+ template = Template(template_content)
147
+ context = _prepare_template_context(role, profile, allowed_permissions)
148
+
149
+ # Debug output if requested
150
+ debug_flag = False
151
+ try:
152
+ debug_flag = (hasattr(sys, 'argv') and ('--debug' in sys.argv or '--verbose' in sys.argv or '-v' in sys.argv))
153
+ except Exception:
154
+ pass
155
+ if debug_flag:
156
+ rich_print(f"[bold magenta][DEBUG][/bold magenta] Rendering system prompt template '[cyan]{template_path.name}[/cyan]' with allowed_permissions: [yellow]{context.get('allowed_permissions')}[/yellow]")
157
+ rich_print(f"[bold magenta][DEBUG][/bold magenta] Template context: [green]{context}[/green]")
158
+ start_render = time.time()
159
+ rendered_prompt = template.render(**context)
160
+ end_render = time.time()
161
+ # Merge multiple empty lines into a single empty line
162
+ rendered_prompt = re.sub(r'\n{3,}', '\n\n', rendered_prompt)
163
+
164
+ return _create_agent(
165
+ provider_instance,
166
+ tools_provider,
167
+ role,
168
+ rendered_prompt,
169
+ input_queue,
170
+ output_queue,
171
+ verbose_agent,
172
+ context,
173
+ template_path,
174
+ profile,
175
+ )
176
+
177
+
178
+ def create_configured_agent(
179
+ *,
180
+ provider_instance=None,
181
+ llm_driver_config=None,
182
+ role=None,
183
+ verbose_tools=False,
184
+ verbose_agent=False,
185
+ templates_dir=None,
186
+ zero_mode=False,
187
+ allowed_permissions=None,
188
+ profile=None,
189
+ profile_system_prompt=None,
190
+ ):
191
+ """
192
+ Normalizes agent setup for all CLI modes.
193
+
194
+ Args:
195
+ provider_instance: Provider instance for the agent
196
+ llm_driver_config: LLM driver configuration
197
+ role: Optional role string
198
+ verbose_tools: Optional, default False
199
+ verbose_agent: Optional, default False
200
+ templates_dir: Optional
201
+ zero_mode: Optional, default False
202
+
203
+ Returns:
204
+ Configured agent instance
205
+ """
206
+ input_queue = None
207
+ output_queue = None
208
+ driver = None
209
+ if hasattr(provider_instance, "create_driver"):
210
+ driver = provider_instance.create_driver()
211
+ driver.start() # Ensure the driver background thread is started
212
+ input_queue = getattr(driver, "input_queue", None)
213
+ output_queue = getattr(driver, "output_queue", None)
214
+
215
+ agent = setup_agent(
216
+ provider_instance=provider_instance,
217
+ llm_driver_config=llm_driver_config,
218
+ role=role,
219
+ templates_dir=templates_dir,
220
+ zero_mode=zero_mode,
221
+ input_queue=input_queue,
222
+ output_queue=output_queue,
223
+ verbose_tools=verbose_tools,
224
+ verbose_agent=verbose_agent,
225
+ allowed_permissions=allowed_permissions,
226
+ profile=profile,
227
+ profile_system_prompt=profile_system_prompt,
228
+ )
229
+ if driver is not None:
230
+ agent.driver = driver # Attach driver to agent for thread management
231
+ return agent
@@ -0,0 +1,39 @@
1
+ You are: software developer
2
+
3
+ {# Improves tool selection and platform specific constrains, eg, path format, C:\ vs /path #}
4
+ {% if allowed_permissions and 'x' in allowed_permissions %}
5
+ You will be using the following environment:
6
+ Platform: {{ platform }}
7
+ Shell/Environment: {{ shell_info }}
8
+ {% endif %}
9
+
10
+
11
+ {% if allowed_permissions and 'r' in allowed_permissions %}
12
+ Before answering map the questions to artifacts found in the current directory - the current project.
13
+ {% endif %}
14
+
15
+ Respond according to the following guidelines:
16
+ {% if allowed_permissions %}
17
+ - Before using the namespace tools provide a short reason
18
+ {% endif %}
19
+ {% if allowed_permissions and 'r' in allowed_permissions %}
20
+ {# Exploratory hint #}
21
+ - Before answering to the user, explore the content related to the question
22
+ {# Reduces chunking roundtip #}
23
+ - When exploring full files content, provide empty range to read the entire files instead of chunked reads
24
+ {% endif %}
25
+ {% if allowed_permissions and 'w' in allowed_permissions %}
26
+ {# Reduce unrequest code verbosity overhead #}
27
+ - Use the namespace functions to deliver the code changes instead of showing the code.
28
+ {# Drive edit mode, place holders critical as shown to be crucial to avoid corruption with code placeholders #}
29
+ - Prefer making localized edits using string replacements. If the required change is extensive, replace the entire file instead, provide full content without placeholders.
30
+ {# Without this, the LLM choses to create files from a literal interpretation of the purpose and intention #}
31
+ - Before creating files search the code for the location related to the file purpose
32
+ {# This will trigger a search for the old names/locations to be updates #}
33
+ - 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.
34
+ {# Keeping docstrings update is key to have semanatic match between prompts and code #}
35
+ - Once development or updates are finished, ensure that new or updated packages, modules, functions are properly documented.
36
+ {# Trying to prevent surrogates generation, found this frequently in gpt4.1/windows #}
37
+ - 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.
38
+ {% endif %}
39
+
@@ -22,35 +22,10 @@ class KeyBindingsFactory:
22
22
  buf.text = "No"
23
23
  buf.validate_and_handle()
24
24
 
25
- @bindings.add("f1")
26
- def _(event):
27
- buf = event.app.current_buffer
28
- buf.text = "/restart"
29
- buf.validate_and_handle()
30
-
31
25
  @bindings.add("f2")
32
26
  def _(event):
33
27
  buf = event.app.current_buffer
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}"
28
+ buf.text = "/restart"
54
29
  buf.validate_and_handle()
55
30
 
56
31
  @bindings.add("f12")