janito 1.5.2__py3-none-any.whl → 1.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 (85) hide show
  1. janito/__init__.py +1 -1
  2. janito/__main__.py +0 -1
  3. janito/agent/config.py +11 -10
  4. janito/agent/config_defaults.py +3 -2
  5. janito/agent/conversation.py +93 -119
  6. janito/agent/conversation_api.py +98 -0
  7. janito/agent/conversation_exceptions.py +12 -0
  8. janito/agent/conversation_tool_calls.py +22 -0
  9. janito/agent/conversation_ui.py +17 -0
  10. janito/agent/message_handler.py +8 -9
  11. janito/agent/{agent.py → openai_client.py} +48 -16
  12. janito/agent/openai_schema_generator.py +53 -37
  13. janito/agent/profile_manager.py +172 -0
  14. janito/agent/queued_message_handler.py +13 -14
  15. janito/agent/rich_live.py +32 -0
  16. janito/agent/rich_message_handler.py +64 -0
  17. janito/agent/runtime_config.py +6 -1
  18. janito/agent/{tools/tool_base.py → tool_base.py} +15 -8
  19. janito/agent/tool_registry.py +118 -132
  20. janito/agent/tools/__init__.py +41 -2
  21. janito/agent/tools/ask_user.py +43 -33
  22. janito/agent/tools/create_directory.py +18 -16
  23. janito/agent/tools/create_file.py +31 -36
  24. janito/agent/tools/fetch_url.py +23 -19
  25. janito/agent/tools/find_files.py +40 -36
  26. janito/agent/tools/get_file_outline.py +100 -22
  27. janito/agent/tools/get_lines.py +40 -32
  28. janito/agent/tools/gitignore_utils.py +9 -6
  29. janito/agent/tools/move_file.py +22 -13
  30. janito/agent/tools/py_compile_file.py +40 -0
  31. janito/agent/tools/remove_directory.py +34 -24
  32. janito/agent/tools/remove_file.py +22 -20
  33. janito/agent/tools/replace_file.py +51 -0
  34. janito/agent/tools/replace_text_in_file.py +69 -42
  35. janito/agent/tools/rich_live.py +9 -2
  36. janito/agent/tools/run_bash_command.py +155 -107
  37. janito/agent/tools/run_python_command.py +139 -0
  38. janito/agent/tools/search_files.py +51 -34
  39. janito/agent/tools/tools_utils.py +4 -2
  40. janito/agent/tools/utils.py +6 -2
  41. janito/cli/_print_config.py +42 -16
  42. janito/cli/_utils.py +1 -0
  43. janito/cli/arg_parser.py +182 -29
  44. janito/cli/config_commands.py +54 -22
  45. janito/cli/logging_setup.py +9 -3
  46. janito/cli/main.py +11 -10
  47. janito/cli/runner/__init__.py +2 -0
  48. janito/cli/runner/cli_main.py +148 -0
  49. janito/cli/runner/config.py +33 -0
  50. janito/cli/runner/formatting.py +12 -0
  51. janito/cli/runner/scan.py +44 -0
  52. janito/cli_chat_shell/__init__.py +0 -1
  53. janito/cli_chat_shell/chat_loop.py +71 -92
  54. janito/cli_chat_shell/chat_state.py +38 -0
  55. janito/cli_chat_shell/chat_ui.py +43 -0
  56. janito/cli_chat_shell/commands/__init__.py +45 -0
  57. janito/cli_chat_shell/commands/config.py +22 -0
  58. janito/cli_chat_shell/commands/history_reset.py +29 -0
  59. janito/cli_chat_shell/commands/session.py +48 -0
  60. janito/cli_chat_shell/commands/session_control.py +12 -0
  61. janito/cli_chat_shell/commands/system.py +73 -0
  62. janito/cli_chat_shell/commands/utility.py +29 -0
  63. janito/cli_chat_shell/config_shell.py +39 -10
  64. janito/cli_chat_shell/load_prompt.py +5 -2
  65. janito/cli_chat_shell/session_manager.py +24 -27
  66. janito/cli_chat_shell/ui.py +75 -40
  67. janito/rich_utils.py +15 -2
  68. janito/web/__main__.py +10 -2
  69. janito/web/app.py +88 -52
  70. {janito-1.5.2.dist-info → janito-1.6.0.dist-info}/METADATA +76 -11
  71. janito-1.6.0.dist-info/RECORD +81 -0
  72. {janito-1.5.2.dist-info → janito-1.6.0.dist-info}/WHEEL +1 -1
  73. janito/agent/rich_tool_handler.py +0 -43
  74. janito/agent/templates/system_instructions.j2 +0 -38
  75. janito/agent/tool_auto_imports.py +0 -5
  76. janito/agent/tools/append_text_to_file.py +0 -41
  77. janito/agent/tools/py_compile.py +0 -39
  78. janito/agent/tools/python_exec.py +0 -83
  79. janito/cli/runner.py +0 -137
  80. janito/cli_chat_shell/commands.py +0 -204
  81. janito/render_prompt.py +0 -13
  82. janito-1.5.2.dist-info/RECORD +0 -66
  83. {janito-1.5.2.dist-info → janito-1.6.0.dist-info}/entry_points.txt +0 -0
  84. {janito-1.5.2.dist-info → janito-1.6.0.dist-info}/licenses/LICENSE +0 -0
  85. {janito-1.5.2.dist-info → janito-1.6.0.dist-info}/top_level.txt +0 -0
@@ -1,54 +1,71 @@
1
- from janito.agent.tools.tool_base import ToolBase
1
+ from janito.agent.tool_base import ToolBase
2
2
  from janito.agent.tool_registry import register_tool
3
+ from janito.agent.tools.tools_utils import pluralize
3
4
 
4
5
  import os
5
6
  from janito.agent.tools.gitignore_utils import filter_ignored
6
7
 
8
+
7
9
  @register_tool(name="search_files")
8
10
  class SearchFilesTool(ToolBase):
9
- """Search for a text pattern in all files within a directory and return matching lines. Respects .gitignore."""
10
- def call(self, directories: list[str], pattern: str, max_results: int=100) -> str:
11
- """
12
- Search for a text pattern in all files within one or more directories and return matching lines.
11
+ """
12
+ Search for a text pattern in all files within a directory and return matching lines. Respects .gitignore.
13
13
 
14
- Args:
15
- directories (list[str]): List of directories to search in.
16
- pattern (str): Plain text substring to search for in files. (Not a regular expression or glob pattern.)
17
- max_results (int): Maximum number of results to return. Defaults to 100.
14
+ Args:
15
+ directories (list[str]): List of directories to search in.
16
+ pattern (str): Plain text substring to search for in files. (Not a regular expression or glob pattern.)
17
+ recursive (bool): Whether to search recursively in subdirectories. Defaults to True.
18
+ max_depth (int, optional): Maximum directory depth to search (0 = only top-level). If None, unlimited. Defaults to None.
19
+ Returns:
20
+ str: Matching lines from files as a newline-separated string, each formatted as 'filepath:lineno: line'. Example:
21
+ - "/path/to/file.py:10: def my_function():"
22
+ - "Warning: Empty search pattern provided. Operation skipped."
23
+ """
18
24
 
19
- Returns:
20
- str: Matching lines from files as a newline-separated string, each formatted as 'filepath:lineno: line'. Example:
21
- - "/path/to/file.py:10: def my_function():"
22
- - "Warning: Empty search pattern provided. Operation skipped."
23
- """
25
+ def call(
26
+ self,
27
+ directories: list[str],
28
+ pattern: str,
29
+ recursive: bool = True,
30
+ max_depth: int = None,
31
+ ) -> str:
24
32
  if not pattern:
25
- self.report_warning("⚠️ Warning: Empty search pattern provided. Operation skipped.")
33
+ self.report_warning(
34
+ "⚠️ Warning: Empty search pattern provided. Operation skipped."
35
+ )
26
36
  return "Warning: Empty search pattern provided. Operation skipped."
27
- matches = []
37
+ output = []
28
38
  for directory in directories:
29
- self.report_info(f"🔎 Searching for text '{pattern}' in '{directory}'")
30
- for root, dirs, files in os.walk(directory):
39
+ info_str = f"🔎 Searching for text '{pattern}' in '{directory}'"
40
+ if recursive is False:
41
+ info_str += f" (recursive={recursive})"
42
+ self.report_info(info_str)
43
+ if recursive:
44
+ walker = os.walk(directory)
45
+ else:
46
+ # Only the top directory, not recursive
47
+ dirs, files = filter_ignored(
48
+ directory, *os.walk(directory).__next__()[1:]
49
+ )
50
+ walker = [(directory, dirs, files)]
51
+ for root, dirs, files in walker:
52
+ # Calculate depth
53
+ rel_path = os.path.relpath(root, directory)
54
+ depth = 0 if rel_path == "." else rel_path.count(os.sep) + 1
55
+ if max_depth is not None and depth > max_depth:
56
+ dirs[:] = []
57
+ continue
58
+ if not recursive and depth > 0:
59
+ break
31
60
  dirs, files = filter_ignored(root, dirs, files)
32
61
  for filename in files:
33
62
  path = os.path.join(root, filename)
34
63
  try:
35
- with open(path, 'r', encoding='utf-8', errors='ignore') as f:
64
+ with open(path, "r", encoding="utf-8", errors="ignore") as f:
36
65
  for lineno, line in enumerate(f, 1):
37
66
  if pattern in line:
38
- matches.append(f"{path}:{lineno}: {line.strip()}")
39
- if len(matches) >= max_results:
40
- break
67
+ output.append(f"{path}:{lineno}: {line.strip()}")
41
68
  except Exception:
42
69
  continue
43
-
44
- warning = ""
45
- if len(matches) >= max_results:
46
- warning = "\n⚠️ Warning: Maximum result limit reached. Some matches may not be shown."
47
- suffix = " (Max Reached)"
48
- else:
49
- suffix = ""
50
- self.report_success(f" ✅ {len(matches)} {pluralize('line', len(matches))}{suffix}")
51
- return '\n'.join(matches) + warning
52
-
53
-
54
- from janito.agent.tools.tools_utils import pluralize
70
+ self.report_success(f" ✅ {len(output)} {pluralize('line', len(output))} found")
71
+ return "\n".join(output)
@@ -1,11 +1,13 @@
1
1
  def display_path(path):
2
2
  import os
3
+
3
4
  if os.path.isabs(path):
4
5
  return path
5
6
  return os.path.relpath(path)
6
7
 
8
+
7
9
  def pluralize(word: str, count: int) -> str:
8
10
  """Return the pluralized form of word if count != 1, unless word already ends with 's'."""
9
- if count == 1 or word.endswith('s'):
11
+ if count == 1 or word.endswith("s"):
10
12
  return word
11
- return word + 's'
13
+ return word + "s"
@@ -20,11 +20,15 @@ def display_path(original_path: str, expanded_path: str) -> str:
20
20
  - Else, show the expanded path.
21
21
  """
22
22
  # Detect relative path (POSIX or Windows)
23
- if not (original_path.startswith("/") or original_path.startswith("~") or (os.name == "nt" and len(original_path) > 1 and original_path[1] == ":")):
23
+ if not (
24
+ original_path.startswith("/")
25
+ or original_path.startswith("~")
26
+ or (os.name == "nt" and len(original_path) > 1 and original_path[1] == ":")
27
+ ):
24
28
  return original_path
25
29
  home = os.path.expanduser("~")
26
30
  if original_path.startswith("~"):
27
31
  return original_path
28
32
  if expanded_path.startswith(home):
29
- return "~" + expanded_path[len(home):]
33
+ return "~" + expanded_path[len(home) :]
30
34
  return expanded_path
@@ -2,6 +2,7 @@ import os
2
2
  from janito.rich_utils import print_info, print_warning, print_magenta
3
3
  from ._utils import home_shorten
4
4
 
5
+
5
6
  def print_config_items(items, color_label=None):
6
7
  if not items:
7
8
  return
@@ -9,7 +10,7 @@ def print_config_items(items, color_label=None):
9
10
  print_info(color_label)
10
11
  home = os.path.expanduser("~")
11
12
  for key, value in items.items():
12
- if key == "system_prompt" and isinstance(value, str):
13
+ if key == "system_prompt_template" and isinstance(value, str):
13
14
  if value.startswith(home):
14
15
  print(f"{key} = {home_shorten(value)}")
15
16
  else:
@@ -18,51 +19,76 @@ def print_config_items(items, color_label=None):
18
19
  print_info(f"{key} = {value}")
19
20
  print_info("")
20
21
 
21
- def print_full_config(local_config, global_config, unified_config, config_defaults, console=None):
22
+
23
+ def print_full_config(
24
+ local_config, global_config, unified_config, config_defaults, console=None
25
+ ):
22
26
  """
23
27
  Print local, global, and default config values in a unified way.
24
- Handles masking API keys and showing the template file for system_prompt if not set.
28
+ Handles masking API keys and showing the template file for system_prompt_template if not set.
25
29
  """
26
30
  local_items = {}
27
31
  global_items = {}
28
32
  local_keys = set(local_config.all().keys())
29
33
  global_keys = set(global_config.all().keys())
30
- all_keys = set(config_defaults.keys()) | global_keys | local_keys
31
- out = print_info if console is None else console.print
32
34
  if not (local_keys or global_keys):
33
35
  print_warning("No configuration found.")
34
36
  else:
35
37
  for key in sorted(local_keys):
36
38
  if key == "api_key":
37
39
  value = local_config.get("api_key")
38
- value = value[:4] + '...' + value[-4:] if value and len(value) > 8 else ('***' if value else None)
40
+ value = (
41
+ value[:4] + "..." + value[-4:]
42
+ if value and len(value) > 8
43
+ else ("***" if value else None)
44
+ )
39
45
  else:
40
46
  value = unified_config.get(key)
41
47
  local_items[key] = value
42
48
  for key in sorted(global_keys - local_keys):
43
49
  if key == "api_key":
44
50
  value = global_config.get("api_key")
45
- value = value[:4] + '...' + value[-4:] if value and len(value) > 8 else ('***' if value else None)
51
+ value = (
52
+ value[:4] + "..." + value[-4:]
53
+ if value and len(value) > 8
54
+ else ("***" if value else None)
55
+ )
46
56
  else:
47
57
  value = unified_config.get(key)
48
58
  global_items[key] = value
49
59
  # Mask API key
50
60
  for cfg in (local_items, global_items):
51
- if 'api_key' in cfg and cfg['api_key']:
52
- val = cfg['api_key']
53
- cfg['api_key'] = val[:4] + '...' + val[-4:] if len(val) > 8 else '***'
54
- print_config_items(local_items, color_label="[cyan]🏠 Local Configuration[/cyan]")
55
- print_config_items(global_items, color_label="[yellow]🌐 Global Configuration[/yellow]")
61
+ if "api_key" in cfg and cfg["api_key"]:
62
+ val = cfg["api_key"]
63
+ cfg["api_key"] = val[:4] + "..." + val[-4:] if len(val) > 8 else "***"
64
+ print_config_items(
65
+ local_items, color_label="[cyan]🏠 Local Configuration[/cyan]"
66
+ )
67
+ print_config_items(
68
+ global_items, color_label="[yellow]🌐 Global Configuration[/yellow]"
69
+ )
56
70
  # Show defaults for unset keys
57
71
  shown_keys = set(local_items.keys()) | set(global_items.keys())
58
- default_items = {k: v for k, v in config_defaults.items() if k not in shown_keys and k != 'api_key'}
72
+ default_items = {
73
+ k: v
74
+ for k, v in config_defaults.items()
75
+ if k not in shown_keys and k != "api_key"
76
+ }
59
77
  if default_items:
60
78
  print_magenta("[green]🟢 Defaults (not set in config files)[/green]")
61
79
  from pathlib import Path
62
- template_path = Path(__file__).parent / "agent" / "templates" / "system_instructions.j2"
80
+
81
+ template_path = (
82
+ Path(__file__).parent
83
+ / "agent"
84
+ / "templates"
85
+ / "system_prompt_template.j2"
86
+ )
63
87
  for key, value in default_items.items():
64
- if key == "system_prompt" and value is None:
65
- print_info(f"{key} = (default template path: {home_shorten(str(template_path))})")
88
+ if key == "system_prompt_template" and value is None:
89
+ print_info(
90
+ f"{key} = (default template path: {home_shorten(str(template_path))})"
91
+ )
66
92
  else:
67
93
  print_info(f"{key} = {value}")
68
94
  print_info("")
janito/cli/_utils.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import os
2
2
 
3
+
3
4
  def home_shorten(path: str) -> str:
4
5
  """If path starts with the user's home directory, replace it with ~."""
5
6
  home = os.path.expanduser("~")
janito/cli/arg_parser.py CHANGED
@@ -2,38 +2,191 @@ import argparse
2
2
 
3
3
 
4
4
  def create_parser():
5
- parser = argparse.ArgumentParser(description="OpenRouter API call using OpenAI Python SDK")
6
- parser.add_argument("prompt", type=str, nargs="?", help="Prompt to send to the model")
5
+ parser = argparse.ArgumentParser(
6
+ description="OpenRouter API call using OpenAI Python SDK"
7
+ )
8
+ parser.add_argument(
9
+ "prompt", type=str, nargs="?", help="Prompt to send to the model"
10
+ )
7
11
 
8
- parser.add_argument("--max-tokens", type=int, default=None, help="Maximum tokens for model response (overrides config, default: 200000)")
9
- parser.add_argument("--max-tools", type=int, default=None, help="Maximum number of tool calls allowed within a chat session (default: unlimited)")
10
- parser.add_argument("--model", type=str, default=None, help="Model name to use for this session (overrides config, does not persist)")
11
- parser.add_argument("--max-rounds", type=int, default=None, help="Maximum number of agent rounds per prompt (overrides config, default: 50)")
12
+ parser.add_argument(
13
+ "--max-tokens",
14
+ type=int,
15
+ default=None,
16
+ help="Maximum tokens for model response (overrides config, default: 200000)",
17
+ )
18
+ parser.add_argument(
19
+ "--max-tools",
20
+ type=int,
21
+ default=None,
22
+ help="Maximum number of tool calls allowed within a chat session (default: unlimited)",
23
+ )
24
+ parser.add_argument(
25
+ "--model",
26
+ type=str,
27
+ default=None,
28
+ help="Model name to use for this session (overrides config, does not persist)",
29
+ )
30
+ parser.add_argument(
31
+ "--max-rounds",
32
+ type=int,
33
+ default=None,
34
+ help="Maximum number of agent rounds per prompt (overrides config, default: 50)",
35
+ )
12
36
 
13
37
  # Mutually exclusive group for system prompt options
14
38
  group = parser.add_mutually_exclusive_group()
15
- group.add_argument("-s", "--system", type=str, default=None, help="Optional system prompt as a raw string.")
16
- group.add_argument("--system-file", type=str, default=None, help="Path to a plain text file to use as the system prompt (no template rendering, takes precedence over --system-prompt)")
39
+ group.add_argument(
40
+ "-s",
41
+ "--system",
42
+ type=str,
43
+ default=None,
44
+ help="Optional system prompt as a raw string.",
45
+ )
46
+ group.add_argument(
47
+ "--system-file",
48
+ type=str,
49
+ default=None,
50
+ help="Path to a plain text file to use as the system prompt (no template rendering, takes precedence over --system-prompt)",
51
+ )
17
52
 
18
- parser.add_argument("-r", "--role", type=str, default=None, help="Role description for the default system prompt")
19
- parser.add_argument("-t", "--temperature", type=float, default=None, help="Sampling temperature (e.g., 0.0 - 2.0)")
20
- parser.add_argument("--verbose-http", action="store_true", help="Enable verbose HTTP logging")
21
- parser.add_argument("--verbose-http-raw", action="store_true", help="Enable raw HTTP wire-level logging")
22
- parser.add_argument("--verbose-response", action="store_true", help="Pretty print the full response object")
23
- parser.add_argument("--show-system", action="store_true", help="Show model, parameters, system prompt, and tool definitions, then exit")
24
- parser.add_argument("--verbose-tools", action="store_true", help="Print tool call parameters and results")
25
- parser.add_argument("-n", "--no-tools", action="store_true", default=False, help="Disable tool use (default: enabled)")
26
- parser.add_argument("--set-local-config", type=str, default=None, help='Set a local config key-value pair, format "key=val"')
27
- parser.add_argument("--set-global-config", type=str, default=None, help='Set a global config key-value pair, format "key=val"')
28
- parser.add_argument("--run-config", type=str, action='append', default=None, help='Set a runtime (in-memory only) config key-value pair, format "key=val". Can be repeated.')
29
- parser.add_argument("--show-config", action="store_true", help="Show effective configuration and exit")
30
- parser.add_argument("--set-api-key", type=str, default=None, help="Set and save the API key globally")
31
- parser.add_argument("--version", action="store_true", help="Show program's version number and exit")
32
- parser.add_argument("--help-config", action="store_true", help="Show all configuration options and exit")
33
- parser.add_argument("--continue-session", action="store_true", help="Continue from the last saved conversation")
34
- parser.add_argument("--web", action="store_true", help="Launch the Janito web server instead of CLI")
35
- parser.add_argument("--config-reset-local", action="store_true", help="Remove the local config file (~/.janito/config.json)")
36
- parser.add_argument("--config-reset-global", action="store_true", help="Remove the global config file (~/.janito/config.json)")
37
- parser.add_argument("--trust", action="store_true", help="Enable trust mode: suppresses run_bash_command output, only shows output file locations.")
38
- parser.add_argument("-V", "--vanilla", action="store_true", default=False, help="Vanilla mode: disables tools, system prompt, and temperature (unless -t is set)")
53
+ parser.add_argument(
54
+ "-r",
55
+ "--role",
56
+ type=str,
57
+ default=None,
58
+ help="Role description for the default system prompt",
59
+ )
60
+ parser.add_argument(
61
+ "-t",
62
+ "--temperature",
63
+ type=float,
64
+ default=None,
65
+ help="Sampling temperature (e.g., 0.0 - 2.0)",
66
+ )
67
+ parser.add_argument(
68
+ "--verbose-http", action="store_true", help="Enable verbose HTTP logging"
69
+ )
70
+ parser.add_argument(
71
+ "--verbose-http-raw",
72
+ action="store_true",
73
+ help="Enable raw HTTP wire-level logging",
74
+ )
75
+ parser.add_argument(
76
+ "--verbose-response",
77
+ action="store_true",
78
+ help="Pretty print the full response object",
79
+ )
80
+ parser.add_argument(
81
+ "--show-system",
82
+ action="store_true",
83
+ help="Show model, parameters, system prompt, and tool definitions, then exit",
84
+ )
85
+ parser.add_argument(
86
+ "--verbose-tools",
87
+ action="store_true",
88
+ help="Print tool call parameters and results",
89
+ )
90
+ parser.add_argument(
91
+ "-n",
92
+ "--no-tools",
93
+ action="store_true",
94
+ default=False,
95
+ help="Disable tool use (default: enabled)",
96
+ )
97
+ parser.add_argument(
98
+ "--set-local-config",
99
+ type=str,
100
+ default=None,
101
+ help='Set a local config key-value pair, format "key=val"',
102
+ )
103
+ parser.add_argument(
104
+ "--set-global-config",
105
+ type=str,
106
+ default=None,
107
+ help='Set a global config key-value pair, format "key=val"',
108
+ )
109
+ parser.add_argument(
110
+ "--run-config",
111
+ type=str,
112
+ action="append",
113
+ default=None,
114
+ help='Set a runtime (in-memory only) config key-value pair, format "key=val". Can be repeated.',
115
+ )
116
+ parser.add_argument(
117
+ "--show-config",
118
+ action="store_true",
119
+ help="Show effective configuration and exit",
120
+ )
121
+ parser.add_argument(
122
+ "--set-api-key",
123
+ type=str,
124
+ default=None,
125
+ help="Set and save the API key globally",
126
+ )
127
+ parser.add_argument(
128
+ "--version", action="store_true", help="Show program's version number and exit"
129
+ )
130
+ parser.add_argument(
131
+ "--help-config",
132
+ action="store_true",
133
+ help="Show all configuration options and exit",
134
+ )
135
+ parser.add_argument(
136
+ "--continue-session",
137
+ action="store_true",
138
+ help="Continue from the last saved conversation",
139
+ )
140
+ parser.add_argument(
141
+ "--web", action="store_true", help="Launch the Janito web server instead of CLI"
142
+ )
143
+ parser.add_argument(
144
+ "--config-reset-local",
145
+ action="store_true",
146
+ help="Remove the local config file (~/.janito/config.json)",
147
+ )
148
+ parser.add_argument(
149
+ "--config-reset-global",
150
+ action="store_true",
151
+ help="Remove the global config file (~/.janito/config.json)",
152
+ )
153
+ parser.add_argument(
154
+ "--verbose-events",
155
+ action="store_true",
156
+ help="Print all agent events before dispatching to the message handler (for debugging)",
157
+ )
158
+ parser.add_argument(
159
+ "-V",
160
+ "--vanilla",
161
+ action="store_true",
162
+ default=False,
163
+ help="Vanilla mode: disables tools, system prompt, and temperature (unless -t is set)",
164
+ )
165
+ parser.add_argument(
166
+ "-T",
167
+ "--trust-tools",
168
+ action="store_true",
169
+ help="Suppress all tool output (trusted tools mode: only shows output file locations)",
170
+ )
171
+ parser.add_argument(
172
+ "--style",
173
+ type=str,
174
+ default=None,
175
+ help="Interaction style for system prompt template (e.g., default, technical)",
176
+ )
177
+ parser.add_argument(
178
+ "--stream",
179
+ action="store_true",
180
+ help="Enable OpenAI streaming mode (yields tokens as they arrive)",
181
+ )
182
+ parser.add_argument(
183
+ "--verbose-stream",
184
+ action="store_true",
185
+ help="Print raw chunks as they are fetched from OpenAI (for debugging)",
186
+ )
187
+ parser.add_argument(
188
+ "--scan",
189
+ action="store_true",
190
+ help="Scan the project to auto-detect relevant tech/skills and save to .janito/tech.txt (no chat)",
191
+ )
39
192
  return parser
@@ -19,7 +19,9 @@ def handle_config_commands(args):
19
19
  sys.exit(1)
20
20
  key = key.strip()
21
21
  if key not in CONFIG_OPTIONS:
22
- print(f"Invalid config key: '{key}'. Supported keys are: {', '.join(CONFIG_OPTIONS.keys())}")
22
+ print(
23
+ f"Invalid config key: '{key}'. Supported keys are: {', '.join(CONFIG_OPTIONS.keys())}"
24
+ )
23
25
  sys.exit(1)
24
26
  runtime_config.set(key, val.strip())
25
27
  if args.set_local_config:
@@ -30,7 +32,9 @@ def handle_config_commands(args):
30
32
  sys.exit(1)
31
33
  key = key.strip()
32
34
  if key not in CONFIG_OPTIONS:
33
- print(f"Invalid config key: '{key}'. Supported keys are: {', '.join(CONFIG_OPTIONS.keys())}")
35
+ print(
36
+ f"Invalid config key: '{key}'. Supported keys are: {', '.join(CONFIG_OPTIONS.keys())}"
37
+ )
34
38
  sys.exit(1)
35
39
  local_config.set(key, val.strip())
36
40
  local_config.save()
@@ -46,18 +50,20 @@ def handle_config_commands(args):
46
50
  sys.exit(1)
47
51
  key = key.strip()
48
52
  if key not in CONFIG_OPTIONS and not key.startswith("template."):
49
- print(f"Invalid config key: '{key}'. Supported keys are: {', '.join(CONFIG_OPTIONS.keys())}")
53
+ print(
54
+ f"Invalid config key: '{key}'. Supported keys are: {', '.join(CONFIG_OPTIONS.keys())}"
55
+ )
50
56
  sys.exit(1)
51
- if key.startswith('template.'):
52
- subkey = key[len('template.'):]
53
- template_dict = global_config.get('template', {})
57
+ if key.startswith("template."):
58
+ subkey = key[len("template.") :]
59
+ template_dict = global_config.get("template", {})
54
60
  template_dict[subkey] = val.strip()
55
- global_config.set('template', template_dict)
61
+ global_config.set("template", template_dict)
56
62
  global_config.save()
57
63
  # Remove legacy flat key if present
58
64
  if key in global_config._data:
59
65
  del global_config._data[key]
60
- runtime_config.set('template', template_dict)
66
+ runtime_config.set("template", template_dict)
61
67
  print(f"Global config updated: template.{subkey} = {val.strip()}")
62
68
  did_something = True
63
69
  else:
@@ -84,7 +90,6 @@ def handle_config_commands(args):
84
90
  # Collect and group keys
85
91
  local_keys = set(local_config.all().keys())
86
92
  global_keys = set(global_config.all().keys())
87
- all_keys = set(CONFIG_DEFAULTS.keys()) | global_keys | local_keys
88
93
  if not (local_keys or global_keys):
89
94
  print("No configuration found.")
90
95
  else:
@@ -103,7 +108,11 @@ def handle_config_commands(args):
103
108
  continue
104
109
  if key == "api_key":
105
110
  value = local_config.get("api_key")
106
- value = value[:4] + '...' + value[-4:] if value and len(value) > 8 else ('***' if value else None)
111
+ value = (
112
+ value[:4] + "..." + value[-4:]
113
+ if value and len(value) > 8
114
+ else ("***" if value else None)
115
+ )
107
116
  else:
108
117
  value = unified_config.get(key)
109
118
  local_items[key] = value
@@ -119,36 +128,58 @@ def handle_config_commands(args):
119
128
  continue
120
129
  if key == "api_key":
121
130
  value = global_config.get("api_key")
122
- value = value[:4] + '...' + value[-4:] if value and len(value) > 8 else ('***' if value else None)
131
+ value = (
132
+ value[:4] + "..." + value[-4:]
133
+ if value and len(value) > 8
134
+ else ("***" if value else None)
135
+ )
123
136
  else:
124
137
  value = unified_config.get(key)
125
138
  global_items[key] = value
126
139
 
127
140
  # Mask API key
128
141
  for cfg in (local_items, global_items):
129
- if 'api_key' in cfg and cfg['api_key']:
130
- val = cfg['api_key']
131
- cfg['api_key'] = val[:4] + '...' + val[-4:] if len(val) > 8 else '***'
142
+ if "api_key" in cfg and cfg["api_key"]:
143
+ val = cfg["api_key"]
144
+ cfg["api_key"] = (
145
+ val[:4] + "..." + val[-4:] if len(val) > 8 else "***"
146
+ )
132
147
 
133
148
  # Print local config
134
149
  from ._print_config import print_config_items
135
- print_config_items(local_items, color_label="[cyan]🏠 Local Configuration[/cyan]")
136
150
 
137
- # Print global config
138
- print_config_items(global_items, color_label="[yellow]🌐 Global Configuration[/yellow]")
151
+ print_config_items(
152
+ local_items, color_label="[cyan]🏠 Local Configuration[/cyan]"
153
+ )
139
154
 
155
+ # Print global config
156
+ print_config_items(
157
+ global_items, color_label="[yellow]🌐 Global Configuration[/yellow]"
158
+ )
140
159
 
141
160
  # Show defaults for unset keys
142
161
  shown_keys = set(local_items.keys()) | set(global_items.keys())
143
- default_items = {k: v for k, v in CONFIG_DEFAULTS.items() if k not in shown_keys and k != 'api_key'}
162
+ default_items = {
163
+ k: v
164
+ for k, v in CONFIG_DEFAULTS.items()
165
+ if k not in shown_keys and k != "api_key"
166
+ }
144
167
  if default_items:
145
168
  print("[green]🟢 Defaults (not set in config files)[/green]")
146
169
  for key, value in default_items.items():
147
170
  # Special case for system_prompt: show template file if None
148
171
  if key == "system_prompt" and value is None:
149
172
  from pathlib import Path
150
- template_path = Path(__file__).parent / "agent" / "templates" / "system_instructions.j2"
151
- print(f"{key} = (default template path: {home_shorten(str(template_path))})")
173
+
174
+ template_path = (
175
+ Path(__file__).parent
176
+ / "agent"
177
+ / "templates"
178
+ / "system_prompt_template.j2"
179
+ )
180
+ print(
181
+ f"{key} = (default template path: {home_shorten(str(template_path))})"
182
+ )
152
183
  else:
153
184
  print(f"{key} = {value}")
154
185
  print()
@@ -156,8 +187,9 @@ def handle_config_commands(args):
156
187
 
157
188
  import os
158
189
  from pathlib import Path
190
+
159
191
  if getattr(args, "config_reset_local", False):
160
- local_path = Path('.janito/config.json')
192
+ local_path = Path(".janito/config.json")
161
193
  if local_path.exists():
162
194
  os.remove(local_path)
163
195
  print(f"Removed local config file: {local_path}")
@@ -165,7 +197,7 @@ def handle_config_commands(args):
165
197
  print(f"Local config file does not exist: {local_path}")
166
198
  sys.exit(0)
167
199
  if getattr(args, "config_reset_global", False):
168
- global_path = Path.home() / '.janito/config.json'
200
+ global_path = Path.home() / ".janito/config.json"
169
201
  if global_path.exists():
170
202
  os.remove(global_path)
171
203
  print(f"Removed global config file: {global_path}")