janito 1.4.0__py3-none-any.whl → 1.5.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 (58) hide show
  1. janito/__init__.py +1 -1
  2. janito/agent/__init__.py +0 -1
  3. janito/agent/agent.py +7 -25
  4. janito/agent/config.py +4 -6
  5. janito/agent/config_defaults.py +2 -2
  6. janito/agent/content_handler.py +0 -0
  7. janito/agent/conversation.py +63 -37
  8. janito/agent/message_handler.py +18 -0
  9. janito/agent/openai_schema_generator.py +116 -0
  10. janito/agent/queued_message_handler.py +32 -0
  11. janito/agent/rich_tool_handler.py +43 -0
  12. janito/agent/runtime_config.py +1 -1
  13. janito/agent/templates/system_instructions.j2 +10 -4
  14. janito/agent/tool_registry.py +92 -0
  15. janito/agent/tools/append_text_to_file.py +41 -0
  16. janito/agent/tools/ask_user.py +16 -3
  17. janito/agent/tools/create_directory.py +31 -0
  18. janito/agent/tools/create_file.py +52 -0
  19. janito/agent/tools/fetch_url.py +23 -8
  20. janito/agent/tools/find_files.py +40 -21
  21. janito/agent/tools/get_file_outline.py +26 -8
  22. janito/agent/tools/get_lines.py +53 -19
  23. janito/agent/tools/move_file.py +50 -0
  24. janito/agent/tools/py_compile.py +27 -11
  25. janito/agent/tools/python_exec.py +43 -14
  26. janito/agent/tools/remove_directory.py +23 -7
  27. janito/agent/tools/remove_file.py +38 -0
  28. janito/agent/tools/replace_text_in_file.py +40 -17
  29. janito/agent/tools/run_bash_command.py +107 -80
  30. janito/agent/tools/search_files.py +38 -19
  31. janito/agent/tools/tool_base.py +30 -3
  32. janito/agent/tools/tools_utils.py +11 -0
  33. janito/agent/tools/utils.py +0 -1
  34. janito/cli/_print_config.py +1 -1
  35. janito/cli/arg_parser.py +2 -1
  36. janito/cli/config_commands.py +3 -6
  37. janito/cli/main.py +2 -2
  38. janito/cli/runner.py +18 -14
  39. janito/cli_chat_shell/chat_loop.py +10 -15
  40. janito/cli_chat_shell/commands.py +8 -3
  41. janito/cli_chat_shell/config_shell.py +0 -3
  42. janito/cli_chat_shell/session_manager.py +11 -0
  43. janito/cli_chat_shell/ui.py +12 -113
  44. janito/render_prompt.py +0 -1
  45. janito/rich_utils.py +30 -0
  46. janito/web/app.py +10 -12
  47. janito-1.5.0.dist-info/METADATA +176 -0
  48. janito-1.5.0.dist-info/RECORD +64 -0
  49. janito/agent/queued_tool_handler.py +0 -16
  50. janito/agent/tool_handler.py +0 -196
  51. janito/agent/tools/file_ops.py +0 -114
  52. janito/agent/tools/rich_utils.py +0 -31
  53. janito-1.4.0.dist-info/METADATA +0 -142
  54. janito-1.4.0.dist-info/RECORD +0 -55
  55. {janito-1.4.0.dist-info → janito-1.5.0.dist-info}/WHEEL +0 -0
  56. {janito-1.4.0.dist-info → janito-1.5.0.dist-info}/entry_points.txt +0 -0
  57. {janito-1.4.0.dist-info → janito-1.5.0.dist-info}/licenses/LICENSE +0 -0
  58. {janito-1.4.0.dist-info → janito-1.5.0.dist-info}/top_level.txt +0 -0
janito/cli/runner.py CHANGED
@@ -1,14 +1,11 @@
1
1
  import sys
2
- import os
3
2
  from rich.console import Console
4
- from rich.markdown import Markdown
5
3
  from janito.render_prompt import render_system_prompt
6
4
  from janito.agent.agent import Agent
7
5
  from janito.agent.conversation import MaxRoundsExceededError, EmptyResponseError, ProviderError
8
6
  from janito.agent.runtime_config import unified_config, runtime_config
9
7
  from janito.agent.config import get_api_key
10
8
  from janito import __version__
11
- from rich.rule import Rule
12
9
 
13
10
 
14
11
  def format_tokens(n):
@@ -56,8 +53,8 @@ def run_cli(args):
56
53
  runtime_config.set('system_prompt_file', args.system_file)
57
54
 
58
55
  else:
59
- system_prompt = args.system_prompt or unified_config.get("system_prompt")
60
- if args.system_prompt:
56
+ system_prompt = args.system or unified_config.get("system_prompt")
57
+ if args.system:
61
58
  runtime_config.set('system_prompt', system_prompt)
62
59
  if system_prompt is None:
63
60
  # Pass full merged config (runtime overrides effective)
@@ -81,11 +78,18 @@ def run_cli(args):
81
78
  model = unified_config.get('model')
82
79
  base_url = unified_config.get('base_url', 'https://openrouter.ai/api/v1')
83
80
  azure_openai_api_version = unified_config.get('azure_openai_api_version', '2023-05-15')
84
- # Handle --enable-tools flag
85
- from janito.agent.tool_handler import ToolHandler
86
- tool_handler = ToolHandler(verbose=args.verbose_tools, enable_tools=not getattr(args, 'no_tools', False))
87
- use_azure_openai = unified_config.get('use_azure_openai', False)
88
- agent = Agent(api_key=api_key, model=model, system_prompt=system_prompt, verbose_tools=args.verbose_tools, base_url=base_url, tool_handler=tool_handler, azure_openai_api_version=azure_openai_api_version, use_azure_openai=use_azure_openai)
81
+ # Handle vanilla mode
82
+ vanilla_mode = getattr(args, 'vanilla', False)
83
+ if vanilla_mode:
84
+ runtime_config.set('vanilla_mode', True)
85
+ system_prompt = None
86
+ runtime_config.set('system_prompt', None)
87
+ # Only set temperature if explicitly provided
88
+ if args.temperature is None:
89
+ runtime_config.set('temperature', None)
90
+ else:
91
+ runtime_config.set('vanilla_mode', False)
92
+ agent = Agent(api_key=api_key, model=model, system_prompt=system_prompt, verbose_tools=args.verbose_tools, base_url=base_url, azure_openai_api_version=azure_openai_api_version, use_azure_openai=unified_config.get('use_azure_openai', False))
89
93
 
90
94
  # Save runtime max_tokens override if provided
91
95
  if args.max_tokens is not None:
@@ -100,10 +104,10 @@ def run_cli(args):
100
104
  prompt = args.prompt
101
105
 
102
106
  console = Console()
107
+ from janito.agent.rich_tool_handler import MessageHandler
108
+ message_handler = MessageHandler()
103
109
 
104
- def on_content(data):
105
- content = data.get("content", "")
106
- console.print(Markdown(content))
110
+ # Removed on_content logic; use message_handler pattern only
107
111
 
108
112
  messages = []
109
113
  if agent.system_prompt:
@@ -116,7 +120,7 @@ def run_cli(args):
116
120
  max_rounds = runtime_config.get('max_rounds', 50)
117
121
  response = agent.chat(
118
122
  messages,
119
- on_content=on_content,
123
+ message_handler=message_handler,
120
124
  spinner=True,
121
125
  max_rounds=max_rounds,
122
126
  )
@@ -1,5 +1,4 @@
1
- from rich.console import Console
2
- from rich.markdown import Markdown
1
+ from janito.agent.rich_tool_handler import MessageHandler
3
2
  from prompt_toolkit.history import InMemoryHistory
4
3
  from .session_manager import load_last_conversation, load_input_history
5
4
  from .ui import print_welcome, get_toolbar_func, get_prompt_session
@@ -11,7 +10,8 @@ from janito.agent.conversation import EmptyResponseError, ProviderError
11
10
 
12
11
 
13
12
  def start_chat_shell(agent, continue_session=False, max_rounds=50):
14
- console = Console()
13
+ message_handler = MessageHandler()
14
+ console = message_handler.console
15
15
 
16
16
  # Load input history
17
17
  history_list = load_input_history()
@@ -45,7 +45,7 @@ def start_chat_shell(agent, continue_session=False, max_rounds=50):
45
45
  state['messages'] = messages
46
46
  state['last_usage_info'] = last_usage_info
47
47
  state['mem_history'] = mem_history
48
- console.print('[bold green]Restored last saved conversation.[/bold green]')
48
+ message_handler.handle_message({'type': 'success', 'message': 'Restored last saved conversation.'})
49
49
 
50
50
  # Add system prompt if needed
51
51
  if agent.system_prompt and not any(m.get('role') == 'system' for m in messages):
@@ -97,10 +97,10 @@ def start_chat_shell(agent, continue_session=False, max_rounds=50):
97
97
  try:
98
98
  confirm = input("Do you really want to exit? (y/n): ").strip().lower()
99
99
  except KeyboardInterrupt:
100
- console.print("\n[bold red]Exiting...[/bold red]")
100
+ message_handler.handle_message({'type': 'error', 'message': 'Exiting...'})
101
101
  break
102
102
  if confirm == 'y':
103
- console.print("[bold red]Exiting...[/bold red]")
103
+ message_handler.handle_message({'type': 'error', 'message': 'Exiting...'})
104
104
  break
105
105
  else:
106
106
  continue
@@ -121,22 +121,17 @@ def start_chat_shell(agent, continue_session=False, max_rounds=50):
121
121
  import time
122
122
  start_time = time.time()
123
123
 
124
- # Define streaming content handler
125
- def on_content(chunk):
126
- content_piece = chunk.get('content')
127
- if content_piece:
128
- console.print(Markdown(content_piece))
129
124
 
130
125
  try:
131
- response = agent.chat(messages, on_content=on_content, spinner=True, max_rounds=max_rounds)
126
+ response = agent.chat(messages, spinner=True, max_rounds=max_rounds, message_handler=message_handler)
132
127
  except KeyboardInterrupt:
133
- console.print("[bold yellow]Request interrupted. Returning to prompt.[/bold yellow]")
128
+ message_handler.handle_message({'type': 'info', 'message': 'Request interrupted. Returning to prompt.'})
134
129
  continue
135
130
  except ProviderError as e:
136
- console.print(f"[bold red]Provider error:[/bold red] {e}")
131
+ message_handler.handle_message({'type': 'error', 'message': f'Provider error: {e}'})
137
132
  continue
138
133
  except EmptyResponseError as e:
139
- console.print(f"[bold red]Error:[/bold red] {e}")
134
+ message_handler.handle_message({'type': 'error', 'message': f'Error: {e}'})
140
135
  continue
141
136
  last_elapsed = time.time() - start_time
142
137
 
@@ -4,7 +4,6 @@ import json
4
4
  from prompt_toolkit.history import InMemoryHistory
5
5
  from janito.render_prompt import render_system_prompt
6
6
  from .load_prompt import load_prompt
7
- from janito.agent.config import effective_config
8
7
 
9
8
 
10
9
  def handle_exit(console, **kwargs):
@@ -171,6 +170,7 @@ def handle_role(console, *args, **kwargs):
171
170
  console.print(f"[bold green]System role updated to:[/bold green] {new_role}")
172
171
 
173
172
 
173
+
174
174
  COMMAND_HANDLERS = {
175
175
  "/history": handle_history,
176
176
  "/continue": handle_continue,
@@ -180,12 +180,17 @@ COMMAND_HANDLERS = {
180
180
  "/help": handle_help,
181
181
  "/multi": handle_multi,
182
182
  "/system": handle_system,
183
- "/role": handle_role,
183
+ }
184
+
185
+ if not runtime_config.get('vanilla_mode', False):
186
+ COMMAND_HANDLERS["/role"] = handle_role
187
+
188
+ COMMAND_HANDLERS.update({
184
189
  "/clear": handle_clear,
185
190
  "/reset": handle_reset,
186
191
  "/config": handle_config_shell,
187
192
  "/reload": handle_reload,
188
- }
193
+ })
189
194
 
190
195
 
191
196
  def handle_command(command, console, **kwargs):
@@ -1,9 +1,6 @@
1
1
  from janito.agent.config import local_config, global_config, CONFIG_OPTIONS
2
2
  from janito.agent.config_defaults import CONFIG_DEFAULTS
3
3
  from janito.agent.runtime_config import unified_config, runtime_config
4
- from janito.cli._print_config import print_config_items
5
- from rich import print
6
- import sys
7
4
 
8
5
  def handle_config_shell(console, *args, **kwargs):
9
6
  """
@@ -54,3 +54,14 @@ def save_input_history(history_list):
54
54
  history_file = os.path.join(history_dir, f'{today_str}.json')
55
55
  with open(history_file, 'w', encoding='utf-8') as f:
56
56
  json.dump(history_list, f, indent=2)
57
+
58
+ def last_conversation_exists(path='.janito/last_conversation.json'):
59
+ if not os.path.exists(path):
60
+ return False
61
+ try:
62
+ with open(path, 'r', encoding='utf-8') as f:
63
+ data = json.load(f)
64
+ messages = data.get('messages', [])
65
+ return bool(messages)
66
+ except Exception:
67
+ return False
@@ -1,11 +1,9 @@
1
- from rich.console import Console
2
- from rich.markdown import Markdown
3
1
  from prompt_toolkit import PromptSession
4
- from prompt_toolkit.key_binding import KeyBindings
5
2
  from prompt_toolkit.enums import EditingMode
6
3
  from prompt_toolkit.formatted_text import HTML
7
4
  from prompt_toolkit.styles import Style
8
- from prompt_toolkit.history import InMemoryHistory
5
+ from janito.agent.runtime_config import runtime_config
6
+ from .session_manager import last_conversation_exists
9
7
 
10
8
 
11
9
  def print_summary(console, data, continue_session):
@@ -26,120 +24,21 @@ def print_summary(console, data, continue_session):
26
24
  ctok = usage.get('completion_tokens')
27
25
  tot = (ptok or 0) + (ctok or 0)
28
26
  console.print(f"Tokens - Prompt: {ptok}, Completion: {ctok}, Total: {tot}")
29
- if not continue_session:
27
+ # Only print /continue suggestion if a last conversation exists
28
+ if not continue_session and last_conversation_exists():
30
29
  console.print("[bold yellow]Type /continue to restore the last saved conversation.[/bold yellow]")
31
30
 
32
31
 
33
32
  def print_welcome(console, version=None, continued=False):
34
33
  version_str = f" (v{version})" if version else ""
35
- console.print(f"[bold green]Welcome to Janito{version_str}! Entering chat mode. Type /exit to exit.[/bold green]")
36
- if not continued:
34
+ vanilla_mode = runtime_config.get('vanilla_mode', False)
35
+ if vanilla_mode:
36
+ console.print(f"[bold magenta]Welcome to Janito{version_str} in [white on magenta]VANILLA MODE[/white on magenta]! Tools, system prompt, and temperature are disabled unless overridden.[/bold magenta]")
37
+ else:
38
+ console.print(f"[bold green]Welcome to Janito{version_str}! Entering chat mode. Type /exit to exit.[/bold green]")
39
+ # Only print /continue suggestion if a last conversation exists
40
+ if not continued and last_conversation_exists():
37
41
  console.print("[yellow]To resume your previous conversation, type /continue at any time.[/yellow]")
38
42
 
39
43
 
40
- def get_toolbar_func(messages_ref, last_usage_info_ref, last_elapsed_ref, model_name=None, role_ref=None):
41
- def format_tokens(n):
42
- if n is None:
43
- return "?"
44
- if n >= 1_000_000:
45
- return f"{n/1_000_000:.1f}m"
46
- if n >= 1_000:
47
- return f"{n/1_000:.1f}k"
48
- return str(n)
49
-
50
- def get_toolbar():
51
- left = f' Messages: <msg_count>{len(messages_ref())}</msg_count>'
52
- usage = last_usage_info_ref()
53
- last_elapsed = last_elapsed_ref()
54
- if usage:
55
- prompt_tokens = usage.get('prompt_tokens')
56
- completion_tokens = usage.get('completion_tokens')
57
- total_tokens = (prompt_tokens or 0) + (completion_tokens or 0)
58
- speed = None
59
- if last_elapsed and last_elapsed > 0:
60
- speed = total_tokens / last_elapsed
61
- left += (
62
- f" | Tokens: In=<tokens_in>{format_tokens(prompt_tokens)}</tokens_in> / "
63
- f"Out=<tokens_out>{format_tokens(completion_tokens)}</tokens_out> / "
64
- f"Total=<tokens_total>{format_tokens(total_tokens)}</tokens_total>"
65
- )
66
- if speed is not None:
67
- left += f", speed=<speed>{speed:.1f}</speed> tokens/sec"
68
-
69
- from prompt_toolkit.application import get_app
70
-
71
- # Compose first line with Model and Role
72
- width = get_app().output.get_size().columns
73
-
74
-
75
- model_part = f" Model: <model>{model_name}</model>" if model_name else ""
76
- role_part = ""
77
- if role_ref:
78
- role = role_ref()
79
- if role:
80
- role_part = f"Role: <b>{role}</b>"
81
-
82
- first_line_parts = []
83
- if model_part:
84
- first_line_parts.append(model_part)
85
- if role_part:
86
- first_line_parts.append(role_part)
87
- first_line = " | ".join(first_line_parts)
88
-
89
- help_part = "<b>/help</b> for help | <b>F12</b>: just do it"
90
-
91
- total_len = len(left) + len(help_part) + 3 # separators and spaces
92
- if first_line:
93
- total_len += len(first_line) + 3
94
-
95
- if total_len < width:
96
- padding = ' ' * (width - total_len)
97
- second_line = f"{left}{padding} | {help_part}"
98
- else:
99
- second_line = f"{left} | {help_part}"
100
-
101
- if first_line:
102
- toolbar_text = first_line + "\n" + second_line
103
- else:
104
- toolbar_text = second_line
105
-
106
- return HTML(toolbar_text)
107
-
108
- return get_toolbar
109
-
110
-
111
- def get_prompt_session(get_toolbar_func, mem_history):
112
- from prompt_toolkit.key_binding import KeyBindings
113
- style = Style.from_dict({
114
- 'bottom-toolbar': 'bg:#333333 #ffffff',
115
- 'b': 'bold',
116
- 'prompt': 'bold bg:#000080 #ffffff',
117
- 'model': 'bold bg:#005f5f #ffffff', # distinct background/foreground
118
- 'msg_count': 'bg:#333333 #ffff00 bold',
119
- 'tokens_in': 'ansicyan bold',
120
- 'tokens_out': 'ansigreen bold',
121
- 'tokens_total': 'ansiyellow bold',
122
- 'speed': 'ansimagenta bold',
123
- 'right': 'bg:#005f5f #ffffff',
124
- 'input': 'bg:#000080 #ffffff',
125
- '': 'bg:#000080 #ffffff',
126
- })
127
-
128
- kb = KeyBindings()
129
-
130
- @kb.add('f12')
131
- def _(event):
132
- """When F12 is pressed, send 'just do it' as input immediately."""
133
- buf = event.app.current_buffer
134
- buf.text = 'just do it'
135
- buf.validate_and_handle()
136
-
137
- session = PromptSession(
138
- multiline=False,
139
- key_bindings=kb,
140
- editing_mode=EditingMode.EMACS,
141
- bottom_toolbar=get_toolbar_func,
142
- style=style,
143
- history=mem_history
144
- )
145
- return session
44
+ # ... rest of the file remains unchanged ...
janito/render_prompt.py CHANGED
@@ -1,7 +1,6 @@
1
1
  import jinja2
2
2
  from pathlib import Path
3
3
 
4
- from janito.agent.runtime_config import unified_config
5
4
 
6
5
  def render_system_prompt(role: str) -> str:
7
6
  template_loader = jinja2.FileSystemLoader(searchpath=str(Path(__file__).parent / "agent" / "templates"))
janito/rich_utils.py ADDED
@@ -0,0 +1,30 @@
1
+ """
2
+ Utilities for working with the Rich library.
3
+ """
4
+ from rich.markdown import Markdown
5
+ from rich.text import Text
6
+ from rich.console import Console
7
+
8
+ def print_markdown(console: Console, message: str):
9
+ console.print(Markdown(message))
10
+
11
+ def print_info(console: Console, message: str):
12
+ console.print(message, style="cyan", end="")
13
+
14
+ def print_success(console: Console, message: str):
15
+ console.print(message, style="bold green", end="\n")
16
+
17
+ def print_error(console: Console, message: str):
18
+ console.print(message, style="bold red", end="\n")
19
+
20
+ def print_warning(console: Console, message: str):
21
+ console.print(message, style="bold yellow", end="\n")
22
+
23
+ def print_magenta(console: Console, message: str):
24
+ console.print(message, style="magenta", end="\n")
25
+
26
+ def print_stdout(console: Console, message: str):
27
+ console.print(Text(message, style="on #003300", no_wrap=True, overflow=None), end="")
28
+
29
+ def print_stderr(console: Console, message: str):
30
+ console.print(Text(message, style="on #330000", no_wrap=True, overflow=None), end="")
janito/web/app.py CHANGED
@@ -1,12 +1,13 @@
1
- from flask import Flask, request, Response, send_from_directory, session, jsonify
1
+ from flask import Flask, request, Response, send_from_directory, jsonify, render_template
2
2
  from queue import Queue
3
3
  import json
4
- from janito.agent.queued_tool_handler import QueuedToolHandler
4
+ from janito.agent.queued_message_handler import QueuedMessageHandler
5
5
  from janito.agent.agent import Agent
6
- from janito.agent.config import get_api_key
7
6
  from janito.render_prompt import render_system_prompt
8
7
  import os
9
8
  import threading
9
+ import traceback
10
+ import sys
10
11
 
11
12
  from janito.agent.runtime_config import unified_config
12
13
 
@@ -37,21 +38,20 @@ conversation = None
37
38
  # Global event queue for streaming
38
39
  stream_queue = Queue()
39
40
 
40
- # Create a QueuedToolHandler with the queue
41
- queued_handler = QueuedToolHandler(stream_queue)
41
+ # Create a QueuedMessageHandler with the queue
42
+ message_handler = QueuedMessageHandler(stream_queue)
42
43
 
43
- # Instantiate the Agent with config-driven parameters
44
+ # Instantiate the Agent with config-driven parameters (no tool_handler)
44
45
  agent = Agent(
45
46
  api_key=unified_config.get("api_key"),
46
47
  model=unified_config.get("model"),
47
- base_url=unified_config.get("base_url"),
48
- tool_handler=queued_handler
48
+ base_url=unified_config.get("base_url")
49
49
  )
50
50
 
51
51
  @app.route('/get_config')
52
52
  def get_config():
53
53
  # Expose full config for the web app: defaults, effective, runtime (mask api_key)
54
- from janito.agent.runtime_config import unified_config
54
+ from janito.agent.runtime_config import unified_config # Kept here: avoids circular import at module level
55
55
  from janito.agent.config_defaults import CONFIG_DEFAULTS
56
56
  # Start with defaults
57
57
  config = dict(CONFIG_DEFAULTS)
@@ -145,7 +145,7 @@ def execute_stream():
145
145
  try:
146
146
  response = agent.chat(
147
147
  conversation,
148
- on_content=lambda data: stream_queue.put({"type": "content", "content": data.get("content")})
148
+ message_handler=message_handler
149
149
  )
150
150
  if response and 'content' in response:
151
151
  conversation.append({"role": "assistant", "content": response['content']})
@@ -156,7 +156,6 @@ def execute_stream():
156
156
  except Exception as e:
157
157
  print(f"Error saving conversation: {e}")
158
158
  except Exception as e:
159
- import traceback
160
159
  tb = traceback.format_exc()
161
160
  stream_queue.put({"type": "error", "error": str(e), "traceback": tb})
162
161
  finally:
@@ -174,7 +173,6 @@ def execute_stream():
174
173
  else:
175
174
  message = json.dumps(content)
176
175
  yield f"data: {message}\n\n"
177
- import sys
178
176
  sys.stdout.flush()
179
177
 
180
178
  return Response(
@@ -0,0 +1,176 @@
1
+ Metadata-Version: 2.4
2
+ Name: janito
3
+ Version: 1.5.0
4
+ Summary: A Natural Programming Language Agent,
5
+ Author-email: João Pinto <joao.pinto@gmail.com>
6
+ License: MIT
7
+ Project-URL: homepage, https://github.com/joaompinto/janito
8
+ Project-URL: repository, https://github.com/joaompinto/janito
9
+ Keywords: agent,framework,tools,automation
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: beautifulsoup4
16
+ Requires-Dist: docstring-parser
17
+ Requires-Dist: flask
18
+ Requires-Dist: jinja2
19
+ Requires-Dist: openai
20
+ Requires-Dist: pathspec
21
+ Requires-Dist: prompt_toolkit
22
+ Requires-Dist: requests
23
+ Requires-Dist: rich
24
+ Dynamic: license-file
25
+
26
+ # 🚀 Janito: Natural Programming Language Agent
27
+
28
+ **Current Version: 1.5.x**
29
+ See [docs/CHANGELOG.md](docs/CHANGELOG.md) and [RELEASE_NOTES_1.5.md](./RELEASE_NOTES_1.5.md) for details on the latest release.
30
+
31
+ Janito is an AI-powered assistant for the command line and web that interprets natural language instructions to edit code, manage files, and analyze projects using patterns and tools designed by experienced software engineers. It prioritizes transparency, interactive clarification, and precise, reviewable changes.
32
+
33
+ For a technical overview, see the [Architecture Guide](docs/ARCHITECTURE.md).
34
+
35
+ ---
36
+
37
+ ## ⚡ Quick Start
38
+
39
+ ## 🖥️ Supported Human Interfaces
40
+ Janito supports multiple ways for users to interact with the agent:
41
+
42
+ - **CLI (Command Line Interface):** Run single prompts or commands directly from your terminal (e.g., `janito "Refactor the data processing module"`).
43
+ - **CLI Chat Shell:** Start an interactive chat session in your terminal for conversational workflows (`janito`).
44
+ - **Web Interface:** Launch a browser-based UI for chat and project management (`janito --web`).
45
+
46
+
47
+ ![Janito Terminal Screenshot](https://github.com/joaompinto/janito/blob/main/docs/imgs/terminal.png?raw=true)
48
+
49
+ ### 🛠️ Common CLI Modifiers
50
+ You can alter Janito's behavior in any interface using these flags:
51
+
52
+ - `--system` / `--system-file`: Override or customize the system prompt for the session.
53
+ - `--no-tools`: Disable all tool usage (Janito will only use the language model, no file/code/shell actions).
54
+ - `--vanilla`: Disables tools, system prompt, and temperature settings for a pure LLM chat experience.
55
+
56
+ These modifiers can be combined with any interface mode for tailored workflows.
57
+
58
+
59
+ Run a one-off prompt:
60
+ ```bash
61
+ janito "Refactor the data processing module to improve readability."
62
+ ```
63
+
64
+ Or start the interactive chat shell:
65
+ ```bash
66
+ janito
67
+ ```
68
+
69
+ While in the chat shell, you can use special commands like `/reload` to reload the system prompt from a file without restarting your session. See the documentation for more shell commands.
70
+
71
+ Launch the web UI:
72
+ ```bash
73
+ janito --web
74
+ ```
75
+
76
+ ---
77
+
78
+ ## ✨ Key Features
79
+ - 📝 **Code Editing via Natural Language:** Modify, create, or delete code files simply by describing the changes.
80
+ - 📁 **File & Directory Management:** Navigate, create, move, or remove files and folders.
81
+ - 🧠 **Context-Aware:** Understands your project structure for precise edits.
82
+ - 💬 **Interactive User Prompts:** Asks for clarification when needed.
83
+ - 🧩 **Extensible Tooling:** Built-in tools for file operations, shell commands, directory and file management, Python code execution and validation, text replacement, and more.
84
+ - See [janito/agent/tools/README.md](janito/agent/tools/README.md) for the full list of built-in tools and their usage details. For the message handler model, see [docs/MESSAGE_HANDLER_MODEL.md](docs/MESSAGE_HANDLER_MODEL.md).
85
+ - 🌐 **Web Interface (In Development):** Simple web UI for streaming responses and tool progress.
86
+
87
+ ## 📦 Installation
88
+
89
+ ### Requirements
90
+ - Python 3.10+
91
+
92
+
93
+ ### Contributing & Developer Guide
94
+
95
+ If you want to extend Janito or add new tools, see the [Developer Guide](docs/README_DEV.md) for instructions, tool registration requirements, and code style guidelines. For the full list of built-in tools and their usage, see the [Tools Reference](janito/agent/tools/README.md).
96
+
97
+
98
+
99
+ For the full changelog, see [docs/CHANGELOG.md](docs/CHANGELOG.md).
100
+
101
+ ...
102
+
103
+ ### Configuration & CLI Options
104
+
105
+ See [docs/CONFIGURATION.md](docs/CONFIGURATION.md) for all configuration parameters, CLI flags, and advanced usage details. All CLI and configuration options have been moved there for clarity and maintainability.
106
+
107
+
108
+ ### Obtaining an API Key from OpenRouter
109
+
110
+ To use Janito with OpenRouter, you need an API key:
111
+
112
+ 1. Visit https://openrouter.ai and sign up for an account.
113
+ 2. After logging in, go to your account dashboard.
114
+ 3. Navigate to the "API Keys" section.
115
+ 4. Click "Create new key" and copy the generated API key.
116
+ 5. Set your API key in Janito using:
117
+ ```bash
118
+ python -m janito --set-api-key YOUR_OPENROUTER_KEY
119
+ ```
120
+ Or add it to your configuration file as `api_key`.
121
+
122
+ **Keep your API key secure and do not share it publicly.**
123
+
124
+ ### Using Azure OpenAI
125
+
126
+ For details on using models hosted on Azure OpenAI, see [docs/AZURE_OPENAI.md](docs/AZURE_OPENAI.md).
127
+
128
+
129
+
130
+ ---
131
+
132
+ ## 🧩 System Prompt & Role
133
+
134
+ Janito operates using a system prompt template that defines its behavior, communication style, and capabilities. By default, Janito assumes the role of a "software engineer"—this means its responses and actions are tailored to the expectations and best practices of professional software engineering.
135
+
136
+ - **Role:** You can customize the agent's role (e.g., "data scientist", "DevOps engineer") using the `--role` flag or config. The default is `software engineer`.
137
+ - **System Prompt Template:** The system prompt is rendered from a Jinja2 template (see `janito/agent/templates/system_instructions.j2`). This template governs how the agent interprets instructions, interacts with files, and communicates with users.
138
+ - **Customization & Precedence:** Advanced users can override the system prompt with the `--system` flag (raw string), or point to a custom file using `--system-file`. The precedence is: `--system-file` > `--system`/config > default template.
139
+
140
+ The default template ensures the agent:
141
+ - Prioritizes safe, reviewable, and minimal changes
142
+ - Asks for clarification when instructions are ambiguous
143
+ - Provides concise plans before taking action
144
+ - Documents any changes made
145
+
146
+ For more details or to customize the prompt, see the template file at `janito/agent/templates/system_instructions.j2` and the architecture overview in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
147
+
148
+ ---
149
+
150
+
151
+ ## 🥛 Vanilla Mode
152
+
153
+ Janito supports a "vanilla mode" for pure LLM interaction:
154
+
155
+ - No tools: Disables all tool use (no file operations, shell commands, etc.).
156
+ - No system prompt: The LLM receives only your input, with no system prompt or role injected.
157
+ - No temperature set: The temperature parameter is not set (unless you explicitly provide `-t`/`--temperature`).
158
+
159
+ Activate vanilla mode with the CLI flag:
160
+
161
+ ```bash
162
+ python -m janito --vanilla "Your prompt here"
163
+ ```
164
+
165
+ Or in chat shell mode:
166
+
167
+ ```bash
168
+ python -m janito --vanilla
169
+ ```
170
+
171
+ Vanilla mode is ideal for:
172
+ - Testing raw model behavior
173
+ - Comparing LLM output with and without agent guidance
174
+ - Ensuring no agent-side intervention or context is added
175
+
176
+ > Note: Vanilla mode is a runtime switch and does not change the Agent API or class signatures. It is controlled via CLI/config only.