janito 2.3.1__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 (96) hide show
  1. janito/__init__.py +1 -1
  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/commands/__init__.py +13 -7
  11. janito/cli/chat_mode/shell/commands/_priv_check.py +5 -0
  12. janito/cli/chat_mode/shell/commands/conversation_restart.py +30 -0
  13. janito/cli/chat_mode/shell/commands/execute.py +42 -0
  14. janito/cli/chat_mode/shell/commands/help.py +6 -3
  15. janito/cli/chat_mode/shell/commands/model.py +28 -0
  16. janito/cli/chat_mode/shell/commands/read.py +37 -0
  17. janito/cli/chat_mode/shell/commands/tools.py +45 -18
  18. janito/cli/chat_mode/shell/commands/write.py +37 -0
  19. janito/cli/chat_mode/shell/commands.bak.zip +0 -0
  20. janito/cli/chat_mode/shell/session.bak.zip +0 -0
  21. janito/cli/chat_mode/toolbar.py +44 -27
  22. janito/cli/cli_commands/list_tools.py +44 -11
  23. janito/cli/cli_commands/model_utils.py +95 -95
  24. janito/cli/cli_commands/show_system_prompt.py +57 -14
  25. janito/cli/config.py +5 -6
  26. janito/cli/core/getters.py +33 -33
  27. janito/cli/core/runner.py +25 -18
  28. janito/cli/core/setters.py +10 -1
  29. janito/cli/main_cli.py +28 -5
  30. janito/cli/prompt_core.py +18 -2
  31. janito/cli/prompt_setup.py +56 -0
  32. janito/cli/single_shot_mode/handler.py +14 -73
  33. janito/cli/verbose_output.py +1 -1
  34. janito/config_manager.py +125 -112
  35. janito/drivers/dashscope.bak.zip +0 -0
  36. janito/drivers/openai/README.md +20 -0
  37. janito/drivers/openai_responses.bak.zip +0 -0
  38. janito/event_bus/event.py +2 -2
  39. janito/i18n/pt.py +0 -1
  40. janito/llm/README.md +23 -0
  41. janito/llm/agent.py +80 -16
  42. janito/llm/auth.py +63 -63
  43. janito/llm/driver.py +8 -0
  44. janito/provider_registry.py +178 -176
  45. janito/providers/azure_openai/model_info.py +16 -16
  46. janito/providers/dashscope.bak.zip +0 -0
  47. janito/providers/registry.py +26 -26
  48. janito/shell.bak.zip +0 -0
  49. janito/tools/DOCSTRING_STANDARD.txt +33 -0
  50. janito/tools/README.md +3 -0
  51. janito/tools/__init__.py +20 -6
  52. janito/tools/adapters/local/__init__.py +65 -62
  53. janito/tools/adapters/local/adapter.py +18 -35
  54. janito/tools/adapters/local/ask_user.py +3 -4
  55. janito/tools/adapters/local/copy_file.py +2 -2
  56. janito/tools/adapters/local/create_directory.py +2 -2
  57. janito/tools/adapters/local/create_file.py +2 -2
  58. janito/tools/adapters/local/delete_text_in_file.py +2 -2
  59. janito/tools/adapters/local/fetch_url.py +2 -2
  60. janito/tools/adapters/local/find_files.py +2 -1
  61. janito/tools/adapters/local/get_file_outline/core.py +2 -2
  62. janito/tools/adapters/local/get_file_outline/search_outline.py +2 -2
  63. janito/tools/adapters/local/move_file.py +2 -2
  64. janito/tools/adapters/local/open_html_in_browser.py +2 -1
  65. janito/tools/adapters/local/open_url.py +2 -2
  66. janito/tools/adapters/local/python_code_run.py +3 -3
  67. janito/tools/adapters/local/python_command_run.py +3 -3
  68. janito/tools/adapters/local/python_file_run.py +3 -3
  69. janito/tools/adapters/local/remove_directory.py +2 -2
  70. janito/tools/adapters/local/remove_file.py +2 -2
  71. janito/tools/adapters/local/replace_text_in_file.py +2 -2
  72. janito/tools/adapters/local/run_bash_command.py +3 -3
  73. janito/tools/adapters/local/run_powershell_command.py +3 -3
  74. janito/tools/adapters/local/search_text/core.py +2 -2
  75. janito/tools/adapters/local/validate_file_syntax/core.py +2 -2
  76. janito/tools/adapters/local/view_file.py +2 -1
  77. janito/tools/outline_file.bak.zip +0 -0
  78. janito/tools/permissions.py +45 -0
  79. janito/tools/permissions_parse.py +12 -0
  80. janito/tools/tool_base.py +14 -1
  81. janito/tools/tool_utils.py +4 -6
  82. janito/tools/tools_adapter.py +25 -20
  83. {janito-2.3.1.dist-info → janito-2.4.0.dist-info}/METADATA +51 -16
  84. {janito-2.3.1.dist-info → janito-2.4.0.dist-info}/RECORD +88 -74
  85. janito/agent/templates/profiles/system_prompt_template_base_pt.txt.j2 +0 -13
  86. janito/agent/templates/profiles/system_prompt_template_main.txt.j2 +0 -37
  87. janito/cli/chat_mode/shell/commands/edit.py +0 -25
  88. janito/cli/chat_mode/shell/commands/exec.py +0 -27
  89. janito/cli/chat_mode/shell/commands/termweb_log.py +0 -92
  90. janito/cli/termweb_starter.py +0 -122
  91. janito/termweb/app.py +0 -95
  92. janito/version.py +0 -4
  93. {janito-2.3.1.dist-info → janito-2.4.0.dist-info}/WHEEL +0 -0
  94. {janito-2.3.1.dist-info → janito-2.4.0.dist-info}/entry_points.txt +0 -0
  95. {janito-2.3.1.dist-info → janito-2.4.0.dist-info}/licenses/LICENSE +0 -0
  96. {janito-2.3.1.dist-info → janito-2.4.0.dist-info}/top_level.txt +0 -0
@@ -1,95 +1,95 @@
1
- """
2
- Utilities for model-related CLI output
3
- """
4
-
5
-
6
- def _print_models_table(models, provider_name):
7
- from rich.table import Table
8
- from rich.console import Console
9
-
10
- headers = [
11
- "name",
12
- "open",
13
- "context",
14
- "max_input",
15
- "max_cot",
16
- "max_response",
17
- "thinking_supported",
18
- "driver",
19
- ]
20
- display_headers = [
21
- "Model Name",
22
- "Vendor",
23
- "context",
24
- "max_input",
25
- "max_cot",
26
- "max_response",
27
- "Thinking",
28
- "Driver",
29
- ]
30
- table = Table(title=f"Supported models for provider '{provider_name}'")
31
- _add_table_columns(table, display_headers)
32
- num_fields = {"context", "max_input", "max_cot", "max_response"}
33
- for m in models:
34
- row = [str(m.get("name", ""))]
35
- row.extend(_build_model_row(m, headers, num_fields))
36
- table.add_row(*row)
37
- import sys
38
- if sys.stdout.isatty():
39
- from rich.console import Console
40
- Console().print(table)
41
- else:
42
- # ASCII-friendly fallback table when output is redirected
43
- print(f"Supported models for provider '{provider_name}'")
44
- headers_fallback = [h for h in display_headers]
45
- print(' | '.join(headers_fallback))
46
- for m in models:
47
- row = [str(m.get('name', ''))]
48
- row.extend(_build_model_row(m, headers, num_fields))
49
- print(' | '.join(row))
50
-
51
-
52
- def _add_table_columns(table, display_headers):
53
- for i, h in enumerate(display_headers):
54
- justify = "right" if i == 0 else "center"
55
- table.add_column(h, style="bold", justify=justify)
56
-
57
-
58
- def _format_k(val):
59
- try:
60
- n = int(val)
61
- if n >= 1000:
62
- return f"{n // 1000}k"
63
- return str(n)
64
- except Exception:
65
- return str(val)
66
-
67
-
68
- def _build_model_row(m, headers, num_fields):
69
- def format_driver(val):
70
- if isinstance(val, (list, tuple)):
71
- return ", ".join(val)
72
- val_str = str(val)
73
- return val_str.removesuffix("ModelDriver").strip()
74
-
75
- row = []
76
- for h in headers[1:]:
77
- v = m.get(h, "")
78
- if h in num_fields and v not in ("", "N/A"):
79
- if (
80
- h in ("context", "max_input")
81
- and isinstance(v, (list, tuple))
82
- and len(v) == 2
83
- ):
84
- row.append(f"{_format_k(v[0])} / {_format_k(v[1])}")
85
- else:
86
- row.append(_format_k(v))
87
- elif h == "open":
88
- row.append("Open" if v is True or v == "Open" else "Locked")
89
- elif h == "thinking_supported":
90
- row.append("📖" if v is True or v == "True" else "")
91
- elif h == "driver":
92
- row.append(format_driver(v))
93
- else:
94
- row.append(str(v))
95
- return row
1
+ """
2
+ Utilities for model-related CLI output
3
+ """
4
+
5
+
6
+ def _print_models_table(models, provider_name):
7
+ from rich.table import Table
8
+ from rich.console import Console
9
+
10
+ headers = [
11
+ "name",
12
+ "open",
13
+ "context",
14
+ "max_input",
15
+ "max_cot",
16
+ "max_response",
17
+ "thinking_supported",
18
+ "driver",
19
+ ]
20
+ display_headers = [
21
+ "Model Name",
22
+ "Vendor",
23
+ "context",
24
+ "max_input",
25
+ "max_cot",
26
+ "max_response",
27
+ "Thinking",
28
+ "Driver",
29
+ ]
30
+ table = Table(title=f"Supported models for provider '{provider_name}'")
31
+ _add_table_columns(table, display_headers)
32
+ num_fields = {"context", "max_input", "max_cot", "max_response"}
33
+ for m in models:
34
+ row = [str(m.get("name", ""))]
35
+ row.extend(_build_model_row(m, headers, num_fields))
36
+ table.add_row(*row)
37
+ import sys
38
+ if sys.stdout.isatty():
39
+ from rich.console import Console
40
+ Console().print(table)
41
+ else:
42
+ # ASCII-friendly fallback table when output is redirected
43
+ print(f"Supported models for provider '{provider_name}'")
44
+ headers_fallback = [h for h in display_headers]
45
+ print(' | '.join(headers_fallback))
46
+ for m in models:
47
+ row = [str(m.get('name', ''))]
48
+ row.extend(_build_model_row(m, headers, num_fields))
49
+ print(' | '.join(row))
50
+
51
+
52
+ def _add_table_columns(table, display_headers):
53
+ for i, h in enumerate(display_headers):
54
+ justify = "right" if i == 0 else "center"
55
+ table.add_column(h, style="bold", justify=justify)
56
+
57
+
58
+ def _format_k(val):
59
+ try:
60
+ n = int(val)
61
+ if n >= 1000:
62
+ return f"{n // 1000}k"
63
+ return str(n)
64
+ except Exception:
65
+ return str(val)
66
+
67
+
68
+ def _build_model_row(m, headers, num_fields):
69
+ def format_driver(val):
70
+ if isinstance(val, (list, tuple)):
71
+ return ", ".join(val)
72
+ val_str = str(val)
73
+ return val_str.removesuffix("ModelDriver").strip()
74
+
75
+ row = []
76
+ for h in headers[1:]:
77
+ v = m.get(h, "")
78
+ if h in num_fields and v not in ("", "N/A"):
79
+ if (
80
+ h in ("context", "max_input")
81
+ and isinstance(v, (list, tuple))
82
+ and len(v) == 2
83
+ ):
84
+ row.append(f"{_format_k(v[0])} / {_format_k(v[1])}")
85
+ else:
86
+ row.append(_format_k(v))
87
+ elif h == "open":
88
+ row.append("Open" if v is True or v == "Open" else "Locked")
89
+ elif h == "thinking_supported":
90
+ row.append("📖" if v is True or v == "True" else "")
91
+ elif h == "driver":
92
+ row.append(format_driver(v))
93
+ else:
94
+ row.append(str(v))
95
+ return row
@@ -1,5 +1,7 @@
1
1
  """
2
2
  CLI Command: Show the resolved system prompt for the main agent (single-shot mode)
3
+
4
+ Supports --profile to select a profile-specific system prompt template.
3
5
  """
4
6
 
5
7
  from janito.cli.core.runner import prepare_llm_driver_config
@@ -23,39 +25,80 @@ def handle_show_system_prompt(args):
23
25
 
24
26
  # Prepare context for Jinja2 rendering
25
27
  context = {}
26
- context["role"] = agent_role or "software developer"
27
- pd = PlatformDiscovery()
28
- context["platform"] = pd.get_platform_name()
29
- context["python_version"] = pd.get_python_version()
30
- context["shell_info"] = pd.detect_shell()
28
+ context["role"] = agent_role or "developer"
29
+ context["profile"] = getattr(args, "profile", None)
30
+ # Compute allowed_permissions from CLI args (as in agent setup)
31
+ from janito.tools.tool_base import ToolPermissions
32
+ read = getattr(args, "read", False)
33
+ write = getattr(args, "write", False)
34
+ execute = getattr(args, "exec", False)
35
+ allowed = ToolPermissions(read=read, write=write, execute=execute)
36
+ perm_str = ""
37
+ if allowed.read:
38
+ perm_str += "r"
39
+ if allowed.write:
40
+ perm_str += "w"
41
+ if allowed.execute:
42
+ perm_str += "x"
43
+ allowed_permissions = perm_str or None
44
+ context["allowed_permissions"] = allowed_permissions
45
+ # DEBUG: Show permissions/context before rendering
46
+ from rich import print as rich_print
47
+ debug_flag = False
48
+ import sys
49
+ try:
50
+ debug_flag = (hasattr(sys, 'argv') and ('--debug' in sys.argv or '--verbose' in sys.argv or '-v' in sys.argv))
51
+ except Exception:
52
+ pass
53
+ if debug_flag:
54
+ rich_print(f"[bold magenta][DEBUG][/bold magenta] Rendering system prompt template '[cyan]{template_filename}[/cyan]' with allowed_permissions: [yellow]{allowed_permissions}[/yellow]")
55
+ rich_print(f"[bold magenta][DEBUG][/bold magenta] Template context: [green]{context}[/green]")
56
+ if allowed_permissions and 'x' in allowed_permissions:
57
+ pd = PlatformDiscovery()
58
+ context["platform"] = pd.get_platform_name()
59
+ context["python_version"] = pd.get_python_version()
60
+ context["shell_info"] = pd.detect_shell()
31
61
 
32
62
  # Locate and load the system prompt template
33
63
  templates_dir = (
34
64
  Path(__file__).parent.parent.parent / "agent" / "templates" / "profiles"
35
65
  )
36
- template_path = templates_dir / "system_prompt_template_main.txt.j2"
66
+ profile = getattr(args, "profile", None)
67
+ if profile:
68
+ template_filename = f"system_prompt_template_{profile}.txt.j2"
69
+ template_path = templates_dir / template_filename
70
+ else:
71
+ # No profile specified means the main agent has no dedicated system prompt template.
72
+ print("[janito] No profile specified. The main agent runs without a system prompt template.\n"
73
+ "Use --profile PROFILE to view a profile-specific system prompt.")
74
+ return
37
75
  template_content = None
38
- if template_path.exists():
76
+ if template_path and template_path.exists():
39
77
  with open(template_path, "r", encoding="utf-8") as file:
40
78
  template_content = file.read()
41
79
  else:
42
80
  # Try package import fallback
43
81
  try:
44
82
  with importlib.resources.files("janito.agent.templates.profiles").joinpath(
45
- "system_prompt_template_main.txt.j2"
83
+ template_filename
46
84
  ).open("r", encoding="utf-8") as file:
47
85
  template_content = file.read()
48
86
  except (FileNotFoundError, ModuleNotFoundError, AttributeError):
49
- print(
50
- f"[janito] Could not find system_prompt_template_main.txt.j2 in {template_path} nor in janito.agent.templates.profiles package."
51
- )
52
- print("No system prompt is set or resolved for this configuration.")
53
- return
87
+ if profile:
88
+ raise FileNotFoundError(
89
+ f"[janito] Could not find profile-specific template '{template_filename}' in {template_path} nor in janito.agent.templates.profiles package."
90
+ )
91
+ else:
92
+ print(
93
+ f"[janito] Could not find {template_filename} in {template_path} nor in janito.agent.templates.profiles package."
94
+ )
95
+ print("No system prompt is set or resolved for this configuration.")
96
+ return
54
97
 
55
98
  template = Template(template_content)
56
99
  system_prompt = template.render(**context)
57
100
 
58
- print("\n--- System Prompt (resolved) ---\n")
101
+ print(f"\n--- System Prompt (resolved, profile: {getattr(args, 'profile', 'main')}) ---\n")
59
102
  print(system_prompt)
60
103
  print("\n-------------------------------\n")
61
104
  if agent_role:
janito/cli/config.py CHANGED
@@ -13,16 +13,15 @@ CONFIG_OPTIONS = {
13
13
  "profile": "Agent Profile name (only 'base' is supported)",
14
14
  }
15
15
 
16
- DEFAULT_TERMWEB_PORT = 8088
17
16
 
18
17
 
19
- def get_termweb_port():
20
- port = config.get("termweb_port")
18
+ def get__port():
19
+ port = config.get("_port")
21
20
  try:
22
21
  return int(port)
23
22
  except Exception:
24
- return DEFAULT_TERMWEB_PORT
23
+ pass
25
24
 
26
25
 
27
- def set_termweb_port(port):
28
- config.file_set("termweb_port", int(port))
26
+ def set__port(port):
27
+ config.file_set("_port", int(port))
@@ -1,33 +1,33 @@
1
- """Handlers for get-type CLI commands (show_config, list_providers, models, tools)."""
2
- import sys
3
-
4
- from janito.cli.cli_commands.list_providers import handle_list_providers
5
- from janito.cli.cli_commands.list_models import handle_list_models
6
- from janito.cli.cli_commands.list_tools import handle_list_tools
7
- from janito.cli.cli_commands.show_config import handle_show_config
8
- from functools import partial
9
- from janito.provider_registry import ProviderRegistry
10
-
11
- GETTER_KEYS = ["show_config", "list_providers", "list_models", "list_tools"]
12
-
13
-
14
- def handle_getter(args, config_mgr=None):
15
- provider_instance = None
16
- if getattr(args, "list_models", False):
17
- provider = getattr(args, "provider", None)
18
- if not provider:
19
- import sys
20
- print(
21
- "Error: No provider selected. Please set a provider using '-p PROVIDER', '--set provider=name', or configure a provider."
22
- )
23
- sys.exit(1)
24
- provider_instance = ProviderRegistry().get_instance(provider)
25
- GETTER_DISPATCH = {
26
- "list_providers": partial(handle_list_providers, args),
27
- "list_models": partial(handle_list_models, args, provider_instance),
28
- "list_tools": partial(handle_list_tools, args),
29
- "show_config": partial(handle_show_config, args),
30
- }
31
- for arg in GETTER_KEYS:
32
- if getattr(args, arg, False) and arg in GETTER_DISPATCH:
33
- return GETTER_DISPATCH[arg]()
1
+ """Handlers for get-type CLI commands (show_config, list_providers, models, tools)."""
2
+ import sys
3
+
4
+ from janito.cli.cli_commands.list_providers import handle_list_providers
5
+ from janito.cli.cli_commands.list_models import handle_list_models
6
+ from janito.cli.cli_commands.list_tools import handle_list_tools
7
+ from janito.cli.cli_commands.show_config import handle_show_config
8
+ from functools import partial
9
+ from janito.provider_registry import ProviderRegistry
10
+
11
+ GETTER_KEYS = ["show_config", "list_providers", "list_models", "list_tools"]
12
+
13
+
14
+ def handle_getter(args, config_mgr=None):
15
+ provider_instance = None
16
+ if getattr(args, "list_models", False):
17
+ provider = getattr(args, "provider", None)
18
+ if not provider:
19
+ import sys
20
+ print(
21
+ "Error: No provider selected. Please set a provider using '-p PROVIDER', '--set provider=name', or configure a provider."
22
+ )
23
+ sys.exit(1)
24
+ provider_instance = ProviderRegistry().get_instance(provider)
25
+ GETTER_DISPATCH = {
26
+ "list_providers": partial(handle_list_providers, args),
27
+ "list_models": partial(handle_list_models, args, provider_instance),
28
+ "list_tools": partial(handle_list_tools, args),
29
+ "show_config": partial(handle_show_config, args),
30
+ }
31
+ for arg in GETTER_KEYS:
32
+ if getattr(args, arg, False) and arg in GETTER_DISPATCH:
33
+ return GETTER_DISPATCH[arg]()
janito/cli/core/runner.py CHANGED
@@ -92,7 +92,7 @@ def prepare_llm_driver_config(args, modifiers):
92
92
  llm_driver_config = LLMDriverConfig(**driver_config_data)
93
93
  if getattr(llm_driver_config, "verbose_api", None):
94
94
  pass
95
- agent_role = modifiers.get("role", "software developer")
95
+ agent_role = modifiers.get("role", "developer")
96
96
  return provider, llm_driver_config, agent_role
97
97
 
98
98
 
@@ -105,23 +105,26 @@ def handle_runner(args, provider, llm_driver_config, agent_role, verbose_tools=F
105
105
 
106
106
  # Patch: disable execution/run tools if not enabled
107
107
  import janito.tools
108
+ from janito.tools.tool_base import ToolPermissions
109
+ read = getattr(args, "read", False)
110
+ write = getattr(args, "write", False)
111
+ execute = exec_enabled
112
+ from janito.tools.permissions import set_global_allowed_permissions
113
+ from janito.tools.tool_base import ToolPermissions
114
+ allowed_permissions = ToolPermissions(read=read, write=write, execute=execute)
115
+ set_global_allowed_permissions(allowed_permissions)
116
+ # Store the default permissions for later restoration (e.g., on /restart)
117
+ from janito.tools.permissions import set_default_allowed_permissions
118
+ set_default_allowed_permissions(allowed_permissions)
108
119
  adapter = janito.tools.get_local_tools_adapter(workdir=getattr(args, "workdir", None))
109
- if not exec_enabled:
110
- if hasattr(adapter, "disable_execution_tools"):
111
- adapter.disable_execution_tools()
112
- else:
113
- # Try to re-register execution tools if possible (print warning if not supported)
114
- if hasattr(adapter, "register_tool"):
115
- # This is a no-op if already registered, but we can attempt to re-register known execution tools
116
- try:
117
- from janito.tools.adapters.local import PythonCodeRunTool, PythonCommandRunTool, PythonFileRunTool, RunBashCommandTool, RunPowershellCommandTool
118
- for tool_cls in [PythonCodeRunTool, PythonCommandRunTool, PythonFileRunTool, RunBashCommandTool, RunPowershellCommandTool]:
119
- try:
120
- adapter.register_tool(tool_cls)
121
- except Exception:
122
- pass # Already registered or error
123
- except Exception:
124
- pass
120
+
121
+ # Print allowed permissions in verbose mode
122
+ if getattr(args, "verbose", False):
123
+ print_verbose_info(
124
+ "Allowed Tool Permissions",
125
+ f"read={read}, write={write}, execute={execute}",
126
+ style="yellow"
127
+ )
125
128
 
126
129
  provider_instance = ProviderRegistry().get_instance(provider, llm_driver_config)
127
130
  if provider_instance is None:
@@ -140,7 +143,10 @@ def handle_runner(args, provider, llm_driver_config, agent_role, verbose_tools=F
140
143
  # DEBUG: Print exec_enabled propagation at runner
141
144
 
142
145
  handler = SingleShotPromptHandler(
143
- args, provider_instance, llm_driver_config, role=agent_role, exec_enabled=exec_enabled
146
+ args, provider_instance, llm_driver_config,
147
+ role=agent_role,
148
+ exec_enabled=exec_enabled,
149
+ allowed_permissions=allowed_permissions,
144
150
  )
145
151
  handler.handle()
146
152
  else:
@@ -157,6 +163,7 @@ def handle_runner(args, provider, llm_driver_config, agent_role, verbose_tools=F
157
163
  verbose_tools=verbose_tools,
158
164
  verbose_agent=getattr(args, "verbose_agent", False),
159
165
  exec_enabled=exec_enabled,
166
+ allowed_permissions=allowed_permissions,
160
167
  )
161
168
  session.run()
162
169
 
@@ -43,8 +43,17 @@ def handle_set(args, config_mgr=None):
43
43
  return True
44
44
  if ".max_tokens" in key or ".base_url" in key:
45
45
  return _handle_set_provider_level_setting(key, value)
46
+ # Tool permissions support: janito set tool_permissions=rwx
47
+ if key == "tool_permissions":
48
+ from janito.tools.permissions_parse import parse_permissions_string
49
+ from janito.tools.permissions import set_global_allowed_permissions
50
+ perms = parse_permissions_string(value)
51
+ global_config.file_set("tool_permissions", value)
52
+ set_global_allowed_permissions(perms)
53
+ print(f"Tool permissions set to '{value}' (parsed: {perms})")
54
+ return True
46
55
  print(
47
- f"Error: Unknown config key '{key}'. Supported: provider, model, <provider>.model, max_tokens, base_url, azure_deployment_name, <provider>.max_tokens, <provider>.base_url, <provider>.<model>.max_tokens, <provider>.<model>.base_url"
56
+ f"Error: Unknown config key '{key}'. Supported: provider, model, <provider>.model, max_tokens, base_url, azure_deployment_name, <provider>.max_tokens, <provider>.base_url, <provider>.<model>.max_tokens, <provider>.<model>.base_url, tool_permissions"
48
57
  )
49
58
  return True
50
59
  except Exception as e:
janito/cli/main_cli.py CHANGED
@@ -14,6 +14,14 @@ from janito.cli.core.event_logger import (
14
14
  )
15
15
 
16
16
  definition = [
17
+ (
18
+ ["--profile"],
19
+ {
20
+ "metavar": "PROFILE",
21
+ "help": "Select the profile name for the system prompt (e.g. 'developer').",
22
+ "default": None,
23
+ },
24
+ ),
17
25
  (
18
26
  ["-W", "--workdir"],
19
27
  {
@@ -57,6 +65,20 @@ definition = [
57
65
  "help": "Enable execution/run tools (allows running code or shell tools from the CLI)",
58
66
  },
59
67
  ),
68
+ (
69
+ ["-r", "--read"],
70
+ {
71
+ "action": "store_true",
72
+ "help": "Enable tools that require read permissions",
73
+ },
74
+ ),
75
+ (
76
+ ["-w", "--write"],
77
+ {
78
+ "action": "store_true",
79
+ "help": "Enable tools that require write permissions",
80
+ },
81
+ ),
60
82
  (["--unset"], {"metavar": "KEY", "help": "Unset (remove) a config key"}),
61
83
  (["--version"], {"action": "version", "version": None}),
62
84
  (["--list-tools"], {"action": "store_true", "help": "List all registered tools"}),
@@ -85,7 +107,6 @@ definition = [
85
107
  "help": "Show the resolved system prompt for the main agent",
86
108
  },
87
109
  ),
88
- (["-r", "--role"], {"metavar": "ROLE", "help": "Set the role for the agent"}),
89
110
  (["-p", "--provider"], {"metavar": "PROVIDER", "help": "Select the provider"}),
90
111
  (["-m", "--model"], {"metavar": "MODEL", "help": "Select the model"}),
91
112
  (
@@ -104,7 +125,7 @@ definition = [
104
125
  },
105
126
  ),
106
127
  (
107
- ["-w", "--web"],
128
+ ["--web"],
108
129
  {
109
130
  "action": "store_true",
110
131
  "default": False,
@@ -112,11 +133,11 @@ definition = [
112
133
  },
113
134
  ),
114
135
  (
115
- ["--termweb-port"],
136
+ ["---port"],
116
137
  {
117
138
  "type": int,
118
139
  "default": 8088,
119
- "help": "Port for the termweb server (default: 8088)",
140
+ "help": "Port for the server (default: 8088)",
120
141
  },
121
142
  ),
122
143
  (["--effort"],
@@ -150,10 +171,12 @@ MODIFIER_KEYS = [
150
171
  "verbose",
151
172
  "raw",
152
173
  "web",
153
- "termweb_port",
174
+ "_port",
154
175
  "verbose_api",
155
176
  "verbose_tools",
156
177
  "exec",
178
+ "read",
179
+ "write",
157
180
  ]
158
181
  SETTER_KEYS = ["set", "set_provider", "set_api_key", "unset"]
159
182
  GETTER_KEYS = ["show_config", "list_providers", "list_models", "list_tools"]
janito/cli/prompt_core.py CHANGED
@@ -3,7 +3,7 @@ Core PromptHandler: Handles prompt submission and response formatting for janito
3
3
  """
4
4
 
5
5
  import time
6
- from janito.version import __version__ as VERSION
6
+ from janito import __version__ as VERSION
7
7
  from janito.performance_collector import PerformanceCollector
8
8
  from rich.status import Status
9
9
  from rich.console import Console
@@ -210,7 +210,23 @@ class PromptHandler:
210
210
  on_event(final_event)
211
211
  global_event_bus.publish(final_event)
212
212
  except KeyboardInterrupt:
213
- self.console.print("[red]Request interrupted.[red]")
213
+ # Capture user interrupt / cancellation
214
+ self.console.print("[red]Interrupted by the user.[/red]")
215
+ try:
216
+ from janito.driver_events import RequestFinished, RequestStatus
217
+ # Record a synthetic "cancelled" final event so that downstream
218
+ # handlers (e.g. single_shot_mode.handler._post_prompt_actions)
219
+ # can reliably detect that the prompt was interrupted by the
220
+ # user and avoid showing misleading messages such as
221
+ # "No output produced by the model.".
222
+ if hasattr(self, "agent") and self.agent is not None:
223
+ self.agent.last_event = RequestFinished(
224
+ status=RequestStatus.CANCELLED,
225
+ reason="Interrupted by the user",
226
+ )
227
+ except Exception:
228
+ # Do not fail on cleanup – this hook is best-effort only.
229
+ pass
214
230
 
215
231
  def _print_verbose_debug(self, message):
216
232
  if hasattr(self.args, "verbose_agent") and self.args.verbose_agent:
@@ -0,0 +1,56 @@
1
+ """
2
+ Shared utilities to set up an agent together with a GenericPromptHandler that
3
+ both single–shot and chat modes can reuse. Having one central place avoids the
4
+ code duplication that previously existed in `chat_mode.session.ChatSession` and
5
+ `single_shot_mode.handler.PromptHandler`.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from janito.agent.setup_agent import create_configured_agent
10
+ from janito.cli.prompt_core import (
11
+ PromptHandler as GenericPromptHandler,
12
+ )
13
+ from typing import Any, Optional
14
+
15
+
16
+ def setup_agent_and_prompt_handler(
17
+ *,
18
+ args: Any,
19
+ provider_instance: Any,
20
+ llm_driver_config: Any,
21
+ role: Optional[str] = None,
22
+ verbose_tools: bool = False,
23
+ verbose_agent: bool = False,
24
+ exec_enabled: bool = False,
25
+ allowed_permissions: Optional[list[str]] = None,
26
+ profile: Optional[str] = None,
27
+ profile_system_prompt: Optional[str] = None,
28
+ conversation_history: Any = None,
29
+ ):
30
+ """Create a configured *agent* as well as a *GenericPromptHandler* bound to
31
+ that agent and return them as a tuple.
32
+
33
+ This helper consolidates the repetitive boiler-plate that was scattered
34
+ across *single-shot* and *chat* modes – both of which need an agent plus a
35
+ prompt handler that points to that agent.
36
+ """
37
+ agent = create_configured_agent(
38
+ provider_instance=provider_instance,
39
+ llm_driver_config=llm_driver_config,
40
+ role=role,
41
+ verbose_tools=verbose_tools,
42
+ verbose_agent=verbose_agent,
43
+ exec_enabled=exec_enabled,
44
+ allowed_permissions=allowed_permissions,
45
+ profile=profile,
46
+ profile_system_prompt=profile_system_prompt,
47
+ )
48
+
49
+ prompt_handler = GenericPromptHandler(
50
+ args=args,
51
+ conversation_history=conversation_history,
52
+ provider_instance=provider_instance,
53
+ )
54
+ prompt_handler.agent = agent
55
+
56
+ return agent, prompt_handler