janito 0.15.0__py3-none-any.whl → 1.0.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 (109) hide show
  1. janito/__init__.py +1 -5
  2. janito/__main__.py +3 -5
  3. janito/agent/__init__.py +1 -0
  4. janito/agent/agent.py +96 -0
  5. janito/agent/config.py +113 -0
  6. janito/agent/config_defaults.py +10 -0
  7. janito/agent/conversation.py +107 -0
  8. janito/agent/queued_tool_handler.py +16 -0
  9. janito/agent/runtime_config.py +30 -0
  10. janito/agent/tool_handler.py +124 -0
  11. janito/agent/tools/__init__.py +11 -0
  12. janito/agent/tools/ask_user.py +63 -0
  13. janito/agent/tools/bash_exec.py +58 -0
  14. janito/agent/tools/create_directory.py +19 -0
  15. janito/agent/tools/create_file.py +43 -0
  16. janito/agent/tools/fetch_url.py +48 -0
  17. janito/agent/tools/file_str_replace.py +48 -0
  18. janito/agent/tools/find_files.py +37 -0
  19. janito/agent/tools/gitignore_utils.py +40 -0
  20. janito/agent/tools/move_file.py +37 -0
  21. janito/agent/tools/remove_file.py +19 -0
  22. janito/agent/tools/rich_live.py +37 -0
  23. janito/agent/tools/rich_utils.py +31 -0
  24. janito/agent/tools/search_text.py +41 -0
  25. janito/agent/tools/view_file.py +34 -0
  26. janito/cli/__init__.py +0 -6
  27. janito/cli/_print_config.py +68 -0
  28. janito/cli/_utils.py +8 -0
  29. janito/cli/arg_parser.py +26 -0
  30. janito/cli/config_commands.py +131 -0
  31. janito/cli/logging_setup.py +27 -0
  32. janito/cli/main.py +39 -0
  33. janito/cli/runner.py +135 -0
  34. janito/cli_chat_shell/__init__.py +1 -0
  35. janito/cli_chat_shell/chat_loop.py +147 -0
  36. janito/cli_chat_shell/commands.py +202 -0
  37. janito/cli_chat_shell/config_shell.py +75 -0
  38. janito/cli_chat_shell/load_prompt.py +15 -0
  39. janito/cli_chat_shell/session_manager.py +60 -0
  40. janito/cli_chat_shell/ui.py +136 -0
  41. janito/render_prompt.py +12 -0
  42. janito/templates/system_instructions.j2 +36 -0
  43. janito/web/__init__.py +0 -0
  44. janito/web/__main__.py +17 -0
  45. janito/web/app.py +132 -0
  46. janito-1.0.0.dist-info/METADATA +144 -0
  47. janito-1.0.0.dist-info/RECORD +51 -0
  48. {janito-0.15.0.dist-info → janito-1.0.0.dist-info}/WHEEL +2 -1
  49. janito-1.0.0.dist-info/entry_points.txt +2 -0
  50. {janito-0.15.0.dist-info → janito-1.0.0.dist-info}/licenses/LICENSE +2 -2
  51. janito-1.0.0.dist-info/top_level.txt +1 -0
  52. janito/callbacks.py +0 -34
  53. janito/cli/agent/__init__.py +0 -7
  54. janito/cli/agent/conversation.py +0 -149
  55. janito/cli/agent/initialization.py +0 -168
  56. janito/cli/agent/query.py +0 -112
  57. janito/cli/agent.py +0 -12
  58. janito/cli/app.py +0 -178
  59. janito/cli/commands/__init__.py +0 -12
  60. janito/cli/commands/config.py +0 -30
  61. janito/cli/commands/history.py +0 -119
  62. janito/cli/commands/profile.py +0 -93
  63. janito/cli/commands/validation.py +0 -24
  64. janito/cli/commands/workspace.py +0 -31
  65. janito/cli/commands.py +0 -12
  66. janito/cli/output.py +0 -29
  67. janito/cli/utils.py +0 -22
  68. janito/config/README.md +0 -104
  69. janito/config/__init__.py +0 -16
  70. janito/config/cli/__init__.py +0 -28
  71. janito/config/cli/commands.py +0 -397
  72. janito/config/cli/validators.py +0 -77
  73. janito/config/core/__init__.py +0 -23
  74. janito/config/core/file_operations.py +0 -90
  75. janito/config/core/properties.py +0 -316
  76. janito/config/core/singleton.py +0 -282
  77. janito/config/profiles/__init__.py +0 -8
  78. janito/config/profiles/definitions.py +0 -38
  79. janito/config/profiles/manager.py +0 -80
  80. janito/data/instructions_template.txt +0 -34
  81. janito/token_report.py +0 -154
  82. janito/tools/__init__.py +0 -44
  83. janito/tools/bash/bash.py +0 -157
  84. janito/tools/bash/unix_persistent_bash.py +0 -215
  85. janito/tools/bash/win_persistent_bash.py +0 -341
  86. janito/tools/decorators.py +0 -90
  87. janito/tools/delete_file.py +0 -65
  88. janito/tools/fetch_webpage/__init__.py +0 -23
  89. janito/tools/fetch_webpage/core.py +0 -182
  90. janito/tools/find_files.py +0 -220
  91. janito/tools/move_file.py +0 -72
  92. janito/tools/prompt_user.py +0 -57
  93. janito/tools/replace_file.py +0 -63
  94. janito/tools/rich_console.py +0 -176
  95. janito/tools/search_text.py +0 -226
  96. janito/tools/str_replace_editor/__init__.py +0 -6
  97. janito/tools/str_replace_editor/editor.py +0 -55
  98. janito/tools/str_replace_editor/handlers/__init__.py +0 -16
  99. janito/tools/str_replace_editor/handlers/create.py +0 -60
  100. janito/tools/str_replace_editor/handlers/insert.py +0 -100
  101. janito/tools/str_replace_editor/handlers/str_replace.py +0 -94
  102. janito/tools/str_replace_editor/handlers/undo.py +0 -64
  103. janito/tools/str_replace_editor/handlers/view.py +0 -165
  104. janito/tools/str_replace_editor/utils.py +0 -33
  105. janito/tools/think.py +0 -37
  106. janito/tools/usage_tracker.py +0 -137
  107. janito-0.15.0.dist-info/METADATA +0 -481
  108. janito-0.15.0.dist-info/RECORD +0 -64
  109. janito-0.15.0.dist-info/entry_points.txt +0 -2
@@ -0,0 +1,131 @@
1
+ import sys
2
+ from janito.agent.config import local_config, global_config
3
+ from janito.agent.runtime_config import unified_config, runtime_config
4
+ from rich import print
5
+ from ._utils import home_shorten
6
+
7
+
8
+ def handle_config_commands(args):
9
+ """Handle --set-local-config, --set-global-config, --show-config. Exit if any are used."""
10
+ did_something = False
11
+
12
+ if args.set_local_config:
13
+ from janito.agent.config import CONFIG_OPTIONS
14
+ try:
15
+ key, val = args.set_local_config.split("=", 1)
16
+ except ValueError:
17
+ print("Invalid format for --set-local-config, expected key=val")
18
+ sys.exit(1)
19
+ key = key.strip()
20
+ if key not in CONFIG_OPTIONS:
21
+ print(f"Invalid config key: '{key}'. Supported keys are: {', '.join(CONFIG_OPTIONS.keys())}")
22
+ sys.exit(1)
23
+ local_config.set(key, val.strip())
24
+ local_config.save()
25
+ runtime_config.set(key, val.strip())
26
+ print(f"Local config updated: {key} = {val.strip()}")
27
+ did_something = True
28
+
29
+ if args.set_global_config:
30
+ from janito.agent.config import CONFIG_OPTIONS
31
+ try:
32
+ key, val = args.set_global_config.split("=", 1)
33
+ except ValueError:
34
+ print("Invalid format for --set-global-config, expected key=val")
35
+ sys.exit(1)
36
+ key = key.strip()
37
+ if key not in CONFIG_OPTIONS:
38
+ print(f"Invalid config key: '{key}'. Supported keys are: {', '.join(CONFIG_OPTIONS.keys())}")
39
+ sys.exit(1)
40
+ global_config.set(key, val.strip())
41
+ global_config.save()
42
+ runtime_config.set(key, val.strip())
43
+ print(f"Global config updated: {key} = {val.strip()}")
44
+ did_something = True
45
+
46
+ if args.set_api_key:
47
+ global_config.set("api_key", args.set_api_key.strip())
48
+ global_config.save()
49
+ runtime_config.set("api_key", args.set_api_key.strip())
50
+ print("Global API key saved.")
51
+ did_something = True
52
+
53
+ if args.show_config:
54
+ local_items = {}
55
+ global_items = {}
56
+
57
+ # Collect and group keys
58
+ from janito.agent.config_defaults import CONFIG_DEFAULTS
59
+ local_keys = set(local_config.all().keys())
60
+ global_keys = set(global_config.all().keys())
61
+ all_keys = set(CONFIG_DEFAULTS.keys()) | global_keys | local_keys
62
+ if not (local_keys or global_keys):
63
+ print("No configuration found.")
64
+ else:
65
+ from janito.agent.config import get_api_key
66
+ from janito.agent.runtime_config import unified_config
67
+ for key in sorted(local_keys):
68
+ if key == "api_key":
69
+ value = local_config.get("api_key")
70
+ value = value[:4] + '...' + value[-4:] if value and len(value) > 8 else ('***' if value else None)
71
+ else:
72
+ value = unified_config.get(key)
73
+ local_items[key] = value
74
+ for key in sorted(global_keys - local_keys):
75
+ if key == "api_key":
76
+ value = global_config.get("api_key")
77
+ value = value[:4] + '...' + value[-4:] if value and len(value) > 8 else ('***' if value else None)
78
+ else:
79
+ value = unified_config.get(key)
80
+ global_items[key] = value
81
+
82
+ # Mask API key
83
+ for cfg in (local_items, global_items):
84
+ if 'api_key' in cfg and cfg['api_key']:
85
+ val = cfg['api_key']
86
+ cfg['api_key'] = val[:4] + '...' + val[-4:] if len(val) > 8 else '***'
87
+
88
+ # Print local config
89
+ from ._print_config import print_config_items
90
+ print_config_items(local_items, color_label="[cyan]🏠 Local Configuration[/cyan]")
91
+
92
+ # Print global config
93
+ print_config_items(global_items, color_label="[yellow]🌐 Global Configuration[/yellow]")
94
+
95
+
96
+ # Show defaults for unset keys
97
+ shown_keys = set(local_items.keys()) | set(global_items.keys())
98
+ default_items = {k: v for k, v in CONFIG_DEFAULTS.items() if k not in shown_keys and k != 'api_key'}
99
+ if default_items:
100
+ print("[green]🟢 Defaults (not set in config files)[/green]")
101
+ for key, value in default_items.items():
102
+ # Special case for system_prompt: show template file if None
103
+ if key == "system_prompt" and value is None:
104
+ from pathlib import Path
105
+ template_path = Path(__file__).parent.parent / "templates" / "system_instructions.j2"
106
+ print(f"{key} = file: {home_shorten(str(template_path))}")
107
+ else:
108
+ print(f"{key} = {value}")
109
+ print()
110
+ did_something = True
111
+
112
+ import os
113
+ from pathlib import Path
114
+ if getattr(args, "config_reset_local", False):
115
+ local_path = Path('.janito/config.json')
116
+ if local_path.exists():
117
+ os.remove(local_path)
118
+ print(f"Removed local config file: {local_path}")
119
+ else:
120
+ print(f"Local config file does not exist: {local_path}")
121
+ sys.exit(0)
122
+ if getattr(args, "config_reset_global", False):
123
+ global_path = Path.home() / '.janito/config.json'
124
+ if global_path.exists():
125
+ os.remove(global_path)
126
+ print(f"Removed global config file: {global_path}")
127
+ else:
128
+ print(f"Global config file does not exist: {global_path}")
129
+ sys.exit(0)
130
+ if did_something:
131
+ sys.exit(0)
@@ -0,0 +1,27 @@
1
+ import os
2
+ import logging
3
+
4
+
5
+ def setup_verbose_logging(args):
6
+ if args.verbose_http or args.verbose_http_raw:
7
+ httpx_logger = logging.getLogger("httpx")
8
+ httpx_logger.setLevel(logging.DEBUG)
9
+ handler = logging.StreamHandler()
10
+ handler.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s"))
11
+ httpx_logger.addHandler(handler)
12
+
13
+ if args.verbose_http_raw:
14
+ os.environ["HTTPX_LOG_LEVEL"] = "trace"
15
+
16
+ httpcore_logger = logging.getLogger("httpcore")
17
+ httpcore_logger.setLevel(logging.DEBUG)
18
+ handler_core = logging.StreamHandler()
19
+ handler_core.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s"))
20
+ httpcore_logger.addHandler(handler_core)
21
+
22
+ # Re-add handler to httpx logger in case
23
+ httpx_logger = logging.getLogger("httpx")
24
+ httpx_logger.setLevel(logging.DEBUG)
25
+ handler = logging.StreamHandler()
26
+ handler.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s"))
27
+ httpx_logger.addHandler(handler)
janito/cli/main.py ADDED
@@ -0,0 +1,39 @@
1
+ """Main CLI entry point for Janito."""
2
+
3
+ from janito.cli.arg_parser import create_parser
4
+ from janito.cli.config_commands import handle_config_commands
5
+ from janito.cli.logging_setup import setup_verbose_logging
6
+ from janito.cli.runner import run_cli
7
+
8
+
9
+ def main():
10
+ """Entry point for the Janito CLI.
11
+
12
+ Parses command-line arguments, handles config commands, sets up logging,
13
+ and launches either the CLI chat shell or the web server.
14
+ """
15
+ # Ensure configs are loaded once at CLI startup
16
+ from janito.agent.config import local_config, global_config
17
+ local_config.load()
18
+ global_config.load()
19
+
20
+ parser = create_parser()
21
+ args = parser.parse_args()
22
+
23
+ from janito.agent.config import CONFIG_OPTIONS
24
+ from janito.agent.config_defaults import CONFIG_DEFAULTS
25
+ import sys
26
+ if getattr(args, "help_config", False):
27
+ print("Available configuration options:\n")
28
+ for key, desc in CONFIG_OPTIONS.items():
29
+ default = CONFIG_DEFAULTS.get(key, None)
30
+ print(f"{key:15} {desc} (default: {default})")
31
+ sys.exit(0)
32
+
33
+ handle_config_commands(args)
34
+ setup_verbose_logging(args)
35
+ if getattr(args, 'web', False):
36
+ import subprocess
37
+ subprocess.run(['python', '-m', 'janito.web'])
38
+ else:
39
+ run_cli(args)
janito/cli/runner.py ADDED
@@ -0,0 +1,135 @@
1
+ import sys
2
+ import os
3
+ from rich.console import Console
4
+ from rich.markdown import Markdown
5
+ from janito.render_prompt import render_system_prompt
6
+ from janito.agent.agent import Agent
7
+ from janito.agent.conversation import MaxRoundsExceededError, EmptyResponseError, ProviderError
8
+ from janito.agent.runtime_config import unified_config, runtime_config
9
+ from janito.agent.config import get_api_key
10
+ from janito import __version__
11
+ from rich.rule import Rule
12
+
13
+
14
+ def format_tokens(n):
15
+ if n is None:
16
+ return "?"
17
+ try:
18
+ n = int(n)
19
+ except (TypeError, ValueError):
20
+ return str(n)
21
+ if n >= 1_000_000:
22
+ return f"{n/1_000_000:.1f}m"
23
+ if n >= 1_000:
24
+ return f"{n/1_000:.1f}k"
25
+ return str(n)
26
+
27
+
28
+ def run_cli(args):
29
+ if args.version:
30
+ print(f"janito version {__version__}")
31
+ sys.exit(0)
32
+
33
+ role = args.role or unified_config.get("role", "software engineer")
34
+ # if args.role:
35
+ # runtime_config.set('role', args.role)
36
+ system_prompt = args.system_prompt or unified_config.get("system_prompt")
37
+ if system_prompt is None:
38
+ system_prompt = render_system_prompt(role)
39
+
40
+ if args.show_system:
41
+ api_key = get_api_key()
42
+ model = unified_config.get('model')
43
+ agent = Agent(api_key=api_key, model=model)
44
+ print("Model:", agent.model)
45
+ print("Parameters: {}")
46
+ import json
47
+ print("System Prompt:", system_prompt or "(default system prompt not provided)")
48
+ sys.exit(0)
49
+
50
+ api_key = get_api_key()
51
+
52
+ model = unified_config.get('model')
53
+ base_url = unified_config.get('base_url', 'https://openrouter.ai/api/v1')
54
+ agent = Agent(api_key=api_key, model=model, system_prompt=system_prompt, verbose_tools=args.verbose_tools, base_url=base_url)
55
+
56
+ # Save runtime max_tokens override if provided
57
+ if args.max_tokens is not None:
58
+ runtime_config.set('max_tokens', args.max_tokens)
59
+ if not args.prompt:
60
+ console = Console()
61
+
62
+ if not getattr(args, 'continue_session', False):
63
+ save_path = os.path.join('.janito', 'last_conversation.json')
64
+ if os.path.exists(save_path):
65
+ try:
66
+ with open(save_path, 'r', encoding='utf-8') as f:
67
+ data = json.load(f)
68
+ messages = data.get('messages', [])
69
+ num_messages = len(messages)
70
+ console.print(f"[bold yellow]A previous conversation with {num_messages} messages was found.[/bold yellow]")
71
+
72
+ last_usage_info = data.get('last_usage_info')
73
+ if last_usage_info:
74
+ prompt_tokens = last_usage_info.get('prompt_tokens', 0)
75
+ completion_tokens = last_usage_info.get('completion_tokens', 0)
76
+ total_tokens = prompt_tokens + completion_tokens
77
+ console.print(Rule(f"Token usage - Prompt: {format_tokens(prompt_tokens)}, Completion: {format_tokens(completion_tokens)}, Total: {format_tokens(total_tokens)}"))
78
+
79
+ console.print("You can resume it anytime by typing [bold]/continue[/bold].")
80
+ except Exception:
81
+ pass # Fail silently if file is corrupt or unreadable
82
+
83
+ from janito.cli_chat_shell.chat_loop import start_chat_shell
84
+ start_chat_shell(agent, continue_session=getattr(args, 'continue_session', False))
85
+ sys.exit(0)
86
+
87
+ prompt = args.prompt
88
+
89
+ console = Console()
90
+
91
+ waiting_displayed = [True]
92
+
93
+ def on_content(data):
94
+ content = data.get("content", "")
95
+ if waiting_displayed[0]:
96
+ # Clear the waiting message
97
+ sys.stdout.flush()
98
+ waiting_displayed[0] = False
99
+ console.print(Markdown(content))
100
+
101
+ messages = []
102
+ if agent.system_prompt:
103
+ messages.append({"role": "system", "content": agent.system_prompt})
104
+
105
+ messages.append({"role": "user", "content": prompt})
106
+
107
+ try:
108
+ try:
109
+ response = agent.chat(
110
+ messages,
111
+ on_content=on_content,
112
+ spinner=True,
113
+ )
114
+ if args.verbose_response:
115
+ import json
116
+ console.print_json(json.dumps(response))
117
+
118
+ usage = response.get('usage')
119
+ if usage:
120
+ prompt_tokens = usage.get('prompt_tokens')
121
+ completion_tokens = usage.get('completion_tokens')
122
+ total_tokens = usage.get('total_tokens')
123
+ console.print(Rule(f"Token usage - Prompt: {format_tokens(prompt_tokens)}, Completion: {format_tokens(completion_tokens)}, Total: {format_tokens(total_tokens)}"))
124
+ except MaxRoundsExceededError:
125
+ print("[Error] Conversation exceeded maximum rounds.")
126
+ sys.exit(1)
127
+ except ProviderError as e:
128
+ print(f"[Error] Provider error: {e}")
129
+ sys.exit(1)
130
+ except EmptyResponseError as e:
131
+ print(f"[Error] {e}")
132
+ sys.exit(1)
133
+ except KeyboardInterrupt:
134
+ print("\n[Interrupted by user]")
135
+ sys.exit(1)
@@ -0,0 +1 @@
1
+ from .chat_loop import start_chat_shell
@@ -0,0 +1,147 @@
1
+ from rich.console import Console
2
+ from rich.markdown import Markdown
3
+ from prompt_toolkit.history import InMemoryHistory
4
+ from .session_manager import load_last_conversation, load_input_history
5
+ from .ui import print_welcome, get_toolbar_func, get_prompt_session
6
+ from janito import __version__
7
+ from .commands import handle_command
8
+ from janito.agent.config import effective_config
9
+ from janito.agent.conversation import EmptyResponseError, ProviderError
10
+
11
+
12
+ def start_chat_shell(agent, continue_session=False):
13
+ console = Console()
14
+
15
+ # Load input history
16
+ history_list = load_input_history()
17
+ mem_history = InMemoryHistory()
18
+ for item in history_list:
19
+ mem_history.append_string(item)
20
+
21
+ # Initialize chat state variables
22
+ messages = []
23
+ last_usage_info = None
24
+ last_elapsed = None
25
+
26
+ state = {
27
+ 'messages': messages,
28
+ 'mem_history': mem_history,
29
+ 'history_list': history_list,
30
+ 'last_usage_info': last_usage_info,
31
+ 'last_elapsed': last_elapsed,
32
+ }
33
+
34
+ # Restore conversation if requested
35
+ if continue_session:
36
+ msgs, prompts, usage = load_last_conversation()
37
+ messages = msgs
38
+ last_usage_info = usage
39
+ mem_history = InMemoryHistory()
40
+ for item in prompts:
41
+ mem_history.append_string(item)
42
+ # update state dict with restored data
43
+
44
+ state['messages'] = messages
45
+ state['last_usage_info'] = last_usage_info
46
+ state['mem_history'] = mem_history
47
+ console.print('[bold green]Restored last saved conversation.[/bold green]')
48
+
49
+ # Add system prompt if needed
50
+ if agent.system_prompt and not any(m.get('role') == 'system' for m in messages):
51
+ messages.insert(0, {"role": "system", "content": agent.system_prompt})
52
+
53
+ print_welcome(console, version=__version__)
54
+
55
+ # Toolbar references
56
+ def get_messages():
57
+ return messages
58
+
59
+ def get_usage():
60
+ return last_usage_info
61
+
62
+ def get_elapsed():
63
+ return last_elapsed
64
+
65
+ # Try to get model name from agent
66
+ model_name = getattr(agent, 'model', None)
67
+
68
+ session = get_prompt_session(
69
+ get_toolbar_func(
70
+ get_messages, get_usage, get_elapsed, model_name=model_name,
71
+ role_ref=lambda: effective_config.get('role')
72
+ ),
73
+ mem_history
74
+ )
75
+
76
+
77
+ # Main chat loop
78
+ while True:
79
+ try:
80
+ if state.get('paste_mode'):
81
+ console.print('')
82
+ user_input = session.prompt('Multiline> ', multiline=True)
83
+ was_paste_mode = True
84
+ state['paste_mode'] = False
85
+ else:
86
+ from prompt_toolkit.formatted_text import HTML
87
+ user_input = session.prompt(HTML('<prompt>💬 </prompt>'), multiline=False)
88
+ was_paste_mode = False
89
+ except EOFError:
90
+ console.print("\n[bold red]Exiting...[/bold red]")
91
+ break
92
+ except KeyboardInterrupt:
93
+ console.print() # Move to next line
94
+ try:
95
+ confirm = input("Do you really want to exit? (y/n): ").strip().lower()
96
+ except KeyboardInterrupt:
97
+ console.print("\n[bold red]Exiting...[/bold red]")
98
+ break
99
+ if confirm == 'y':
100
+ console.print("[bold red]Exiting...[/bold red]")
101
+ break
102
+ else:
103
+ continue
104
+
105
+ if not was_paste_mode and user_input.strip().startswith('/'):
106
+ result = handle_command(user_input.strip(), console, agent=agent, messages=messages, mem_history=mem_history, state=state)
107
+ if result == 'exit':
108
+ break
109
+ continue
110
+
111
+ if not user_input.strip():
112
+ continue
113
+
114
+ mem_history.append_string(user_input)
115
+ messages.append({"role": "user", "content": user_input})
116
+
117
+ start_time = None
118
+ import time
119
+ start_time = time.time()
120
+
121
+ # Define streaming content handler
122
+ def on_content(chunk):
123
+ content_piece = chunk.get('content')
124
+ if content_piece:
125
+ console.print(Markdown(content_piece))
126
+
127
+ try:
128
+ response = agent.chat(messages, on_content=on_content, spinner=True)
129
+ except KeyboardInterrupt:
130
+ console.print("[bold yellow]Request interrupted. Returning to prompt.[/bold yellow]")
131
+ continue
132
+ except ProviderError as e:
133
+ console.print(f"[bold red]Provider error:[/bold red] {e}")
134
+ continue
135
+ except EmptyResponseError as e:
136
+ console.print(f"[bold red]Error:[/bold red] {e}")
137
+ continue
138
+ last_elapsed = time.time() - start_time
139
+
140
+ usage = response.get('usage')
141
+ last_usage_info = usage
142
+
143
+ # Save conversation and input history
144
+ from .session_manager import save_conversation, save_input_history
145
+ prompts = [h for h in mem_history.get_strings()]
146
+ save_conversation(messages, prompts, last_usage_info)
147
+ save_input_history(prompts)
@@ -0,0 +1,202 @@
1
+ import os
2
+ import sys
3
+ import json
4
+ from prompt_toolkit.history import InMemoryHistory
5
+ from janito.render_prompt import render_system_prompt
6
+ from .load_prompt import load_prompt
7
+ from janito.agent.config import effective_config
8
+
9
+
10
+ def handle_exit(console, **kwargs):
11
+ console.print("[bold red]Exiting chat mode.[/bold red]")
12
+ sys.exit(0)
13
+
14
+
15
+ def handle_restart(console, **kwargs):
16
+ console.print("[bold yellow]Restarting CLI...[/bold yellow]")
17
+ os.execv(sys.executable, [sys.executable, "-m", "janito"] + sys.argv[1:])
18
+
19
+
20
+ def handle_continue(console, state, **kwargs):
21
+ save_path = os.path.join('.janito', 'last_conversation.json')
22
+ if not os.path.exists(save_path):
23
+ console.print('[bold red]No saved conversation found.[/bold red]')
24
+ return
25
+ try:
26
+ with open(save_path, 'r', encoding='utf-8') as f:
27
+ data = json.load(f)
28
+ state['messages'].clear()
29
+ state['messages'].extend(data.get('messages', []))
30
+ state['history_list'].clear()
31
+ state['history_list'].extend(data.get('prompts', []))
32
+ state['mem_history'] = InMemoryHistory()
33
+ for item in state['history_list']:
34
+ state['mem_history'].append_string(item)
35
+ state['last_usage_info'] = data.get('last_usage_info')
36
+ console.print('[bold green]Conversation restored from last session.[/bold green]')
37
+ except Exception as e:
38
+ console.print(f'[bold red]Failed to load conversation:[/bold red] {e}')
39
+
40
+ def handle_history(console, state, *args, **kwargs):
41
+ messages = state.get('messages', [])
42
+ try:
43
+ if not args:
44
+ # Default: last 5 messages
45
+ start = max(0, len(messages) - 5)
46
+ end = len(messages)
47
+ elif len(args) == 1:
48
+ count = int(args[0])
49
+ start = max(0, len(messages) - count)
50
+ end = len(messages)
51
+ elif len(args) >= 2:
52
+ start = int(args[0])
53
+ end = int(args[1]) + 1 # inclusive
54
+ else:
55
+ start = 0
56
+ end = len(messages)
57
+
58
+ console.print(f"[bold cyan]Showing messages {start} to {end - 1} (total {len(messages)}):[/bold cyan]")
59
+ for idx, msg in enumerate(messages[start:end], start=start):
60
+ role = msg.get('role', 'unknown')
61
+ content = msg.get('content', '')
62
+ console.print(f"[bold]{idx} [{role}]:[/bold] {content}")
63
+ except Exception as e:
64
+ console.print(f"[bold red]Error parsing arguments or displaying history:[/bold red] {e}")
65
+
66
+ def handle_help(console, **kwargs):
67
+ console.print("""
68
+ [bold green]Available commands:[/bold green]
69
+ /exit - Exit chat mode
70
+ /restart - Restart the CLI
71
+ /help - Show this help message
72
+ /continue - Restore last saved conversation
73
+ /reset - Reset conversation history
74
+ /system - Show the system prompt
75
+ /role - Change the system role
76
+ /clear - Clear the terminal screen
77
+ /multi - Provide multiline input as next message
78
+ /config - Show or set configuration (see: /config show, /config set local|global key=value)
79
+ """)
80
+
81
+
82
+ def handle_reload(console, *args, **kwargs):
83
+ """
84
+ /reload [filename] - Reload the system prompt from the default or specified file
85
+ """
86
+ agent = kwargs.get('agent')
87
+ state = kwargs.get('state')
88
+ filename = args[0] if args else None
89
+ try:
90
+ prompt_text = load_prompt(filename)
91
+ if hasattr(agent, 'system_prompt'):
92
+ agent.system_prompt = prompt_text
93
+ # Update the first system message in the conversation if present
94
+ messages = state.get('messages') if state else None
95
+ if messages:
96
+ for msg in messages:
97
+ if msg.get('role') == 'system':
98
+ msg['content'] = prompt_text
99
+ break
100
+ console.print(f"[bold green]System prompt reloaded from {'default file' if not filename else filename}![/bold green]")
101
+ except Exception as e:
102
+ console.print(f"[bold red]Failed to reload system prompt:[/bold red] {e}")
103
+
104
+
105
+ def handle_system(console, **kwargs):
106
+ prompt = getattr(kwargs.get('agent'), 'system_prompt', None)
107
+ if not prompt:
108
+ prompt = render_system_prompt("software engineer")
109
+ console.print(f"[bold magenta]System Prompt:[/bold magenta]\n{prompt}")
110
+
111
+
112
+ def handle_clear(console, **kwargs):
113
+ os.system('cls' if os.name == 'nt' else 'clear')
114
+
115
+
116
+ def handle_reset(console, state, **kwargs):
117
+ import os
118
+ save_path = os.path.join('.janito', 'last_conversation.json')
119
+
120
+ # Clear in-memory conversation and prompt history
121
+ state['messages'].clear()
122
+ state['history_list'].clear()
123
+ state['mem_history'] = InMemoryHistory()
124
+ state['last_usage_info'] = None
125
+ state['last_elapsed'] = None
126
+
127
+ # Delete saved conversation file if exists
128
+ if os.path.exists(save_path):
129
+ try:
130
+ os.remove(save_path)
131
+ console.print('[bold yellow]Deleted saved conversation history.[/bold yellow]')
132
+ except Exception as e:
133
+ console.print(f'[bold red]Failed to delete saved conversation:[/bold red] {e}')
134
+ else:
135
+ console.print('[bold yellow]No saved conversation to delete.[/bold yellow]')
136
+
137
+ console.print('[bold green]Conversation history has been reset.[/bold green]')
138
+
139
+
140
+ def handle_multi(console, state, **kwargs):
141
+ console.print("[bold yellow]Multiline mode activated. Provide or write your text and press Esc + Enter to submit.[/bold yellow]")
142
+ state['paste_mode'] = True
143
+
144
+ from janito.agent.runtime_config import runtime_config
145
+ from .config_shell import handle_config_shell
146
+
147
+ def handle_role(console, *args, **kwargs):
148
+ state = kwargs.get('state')
149
+ agent = kwargs.get('agent')
150
+ if not args:
151
+ console.print('[bold red]Usage: /role <new role description>[/bold red]')
152
+ return
153
+ new_role = ' '.join(args)
154
+ # Update system message in conversation
155
+ found = False
156
+ for msg in state['messages']:
157
+ if msg.get('role') == 'system':
158
+ msg['content'] = render_system_prompt(new_role)
159
+ found = True
160
+ break
161
+ if not found:
162
+ # Insert new system message at the beginning
163
+ state['messages'].insert(0, {'role': 'system', 'content': new_role})
164
+ # Update agent's system prompt attribute if exists
165
+ if hasattr(agent, 'system_prompt'):
166
+ agent.system_prompt = render_system_prompt(new_role)
167
+ # Also store the raw role string
168
+ if hasattr(agent, 'role_name'):
169
+ agent.role_name = new_role
170
+ else:
171
+ setattr(agent, 'role_name', new_role)
172
+ # Update runtime config so all lookups see the new role
173
+ runtime_config.set('role', new_role)
174
+ console.print(f"[bold green]System role updated to:[/bold green] {new_role}")
175
+
176
+
177
+ COMMAND_HANDLERS = {
178
+ "/history": handle_history,
179
+ "/continue": handle_continue,
180
+ "/exit": handle_exit,
181
+ "/restart": handle_restart,
182
+
183
+ "/help": handle_help,
184
+ "/multi": handle_multi,
185
+ "/system": handle_system,
186
+ "/role": handle_role,
187
+ "/clear": handle_clear,
188
+ "/reset": handle_reset,
189
+ "/config": handle_config_shell,
190
+ "/reload": handle_reload,
191
+ }
192
+
193
+
194
+ def handle_command(command, console, **kwargs):
195
+ parts = command.strip().split()
196
+ cmd = parts[0]
197
+ args = parts[1:]
198
+ handler = COMMAND_HANDLERS.get(cmd)
199
+ if handler:
200
+ return handler(console, *args, **kwargs)
201
+ console.print(f"[bold red]Invalid command: {cmd}. Type /help for a list of commands.[/bold red]")
202
+ return None