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,75 @@
1
+ from janito.agent.config import local_config, global_config, CONFIG_OPTIONS
2
+ from janito.agent.config_defaults import CONFIG_DEFAULTS
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
+
8
+ def handle_config_shell(console, *args, **kwargs):
9
+ """
10
+ /config show
11
+ /config set local key=value
12
+ /config set global key=value
13
+ /config reset local
14
+ /config reset global
15
+ """
16
+ if not args or args[0] not in ("show", "set", "reset"):
17
+ console.print("[bold red]Usage:[/bold red] /config show | /config set local|global key=value | /config reset local|global")
18
+ return
19
+
20
+ if args[0] == "show":
21
+ # Show config, unified with CLI
22
+ from janito.cli._print_config import print_full_config
23
+ print_full_config(local_config, global_config, unified_config, CONFIG_DEFAULTS, console=console)
24
+ return
25
+
26
+ if args[0] == "reset":
27
+ if len(args) < 2 or args[1] not in ("local", "global"):
28
+ console.print("[bold red]Usage:[/bold red] /config reset local|global")
29
+ return
30
+ import os
31
+ from pathlib import Path
32
+ scope = args[1]
33
+ if scope == "local":
34
+ local_path = Path('.janito/config.json')
35
+ if local_path.exists():
36
+ os.remove(local_path)
37
+ console.print(f"[green]Removed local config file:[/green] {local_path}")
38
+ else:
39
+ console.print(f"[yellow]Local config file does not exist:[/yellow] {local_path}")
40
+ elif scope == "global":
41
+ global_path = Path.home() / '.janito/config.json'
42
+ if global_path.exists():
43
+ os.remove(global_path)
44
+ console.print(f"[green]Removed global config file:[/green] {global_path}")
45
+ else:
46
+ console.print(f"[yellow]Global config file does not exist:[/yellow] {global_path}")
47
+ console.print("[bold yellow]Please use /restart for changes to take full effect.[/bold yellow]")
48
+ return
49
+
50
+ if args[0] == "set":
51
+ if len(args) < 3 or args[1] not in ("local", "global"):
52
+ console.print("[bold red]Usage:[/bold red] /config set local|global key=value")
53
+ return
54
+ scope = args[1]
55
+ try:
56
+ key, val = args[2].split("=", 1)
57
+ except ValueError:
58
+ console.print("[bold red]Invalid format, expected key=val[/bold red]")
59
+ return
60
+ key = key.strip()
61
+ if key not in CONFIG_OPTIONS:
62
+ console.print(f"[bold red]Invalid config key: '{key}'. Supported keys are: {', '.join(CONFIG_OPTIONS.keys())}")
63
+ return
64
+ val = val.strip()
65
+ if scope == "local":
66
+ local_config.set(key, val)
67
+ local_config.save()
68
+ runtime_config.set(key, val)
69
+ console.print(f"[green]Local config updated:[/green] {key} = {val}")
70
+ elif scope == "global":
71
+ global_config.set(key, val)
72
+ global_config.save()
73
+ runtime_config.set(key, val)
74
+ console.print(f"[green]Global config updated:[/green] {key} = {val}")
75
+ return
@@ -0,0 +1,15 @@
1
+ import os
2
+
3
+ def load_prompt(filename=None):
4
+ """
5
+ Load the system prompt from a file. If filename is None, use the default prompt file.
6
+ Returns the prompt string.
7
+ """
8
+ if filename is None:
9
+ # Default prompt file path (can be customized)
10
+ filename = os.path.join(os.path.dirname(__file__), '../render_prompt/templates/system_instructions.j2')
11
+ filename = os.path.abspath(filename)
12
+ if not os.path.exists(filename):
13
+ raise FileNotFoundError(f"Prompt file not found: {filename}")
14
+ with open(filename, 'r', encoding='utf-8') as f:
15
+ return f.read()
@@ -0,0 +1,60 @@
1
+ import os
2
+ import json
3
+ from datetime import datetime
4
+
5
+
6
+ def load_last_summary(path='.janito/last_conversation.json'):
7
+ if not os.path.exists(path):
8
+ return None
9
+ try:
10
+ with open(path, 'r', encoding='utf-8') as f:
11
+ data = json.load(f)
12
+ return data
13
+ except Exception:
14
+ return None
15
+
16
+
17
+ def load_last_conversation(path='.janito/last_conversation.json'):
18
+ if not os.path.exists(path):
19
+ return [], [], None
20
+ try:
21
+ with open(path, 'r', encoding='utf-8') as f:
22
+ data = json.load(f)
23
+ messages = data.get('messages', [])
24
+ prompts = data.get('prompts', [])
25
+ usage = data.get('last_usage_info')
26
+ return messages, prompts, usage
27
+ except Exception:
28
+ return [], [], None
29
+
30
+
31
+ def save_conversation(messages, prompts, usage_info=None, path='.janito/last_conversation.json'):
32
+ os.makedirs(os.path.dirname(path), exist_ok=True)
33
+ data = {
34
+ 'messages': messages,
35
+ 'prompts': prompts,
36
+ 'last_usage_info': usage_info
37
+ }
38
+ with open(path, 'w', encoding='utf-8') as f:
39
+ json.dump(data, f, indent=2)
40
+
41
+
42
+ def load_input_history():
43
+ history_dir = os.path.join('.janito', 'input_history')
44
+ os.makedirs(history_dir, exist_ok=True)
45
+ today_str = datetime.now().strftime('%y%m%d')
46
+ history_file = os.path.join(history_dir, f'{today_str}.json')
47
+ try:
48
+ with open(history_file, 'r', encoding='utf-8') as f:
49
+ return json.load(f)
50
+ except (FileNotFoundError, json.JSONDecodeError):
51
+ return []
52
+
53
+
54
+ def save_input_history(history_list):
55
+ history_dir = os.path.join('.janito', 'input_history')
56
+ os.makedirs(history_dir, exist_ok=True)
57
+ today_str = datetime.now().strftime('%y%m%d')
58
+ history_file = os.path.join(history_dir, f'{today_str}.json')
59
+ with open(history_file, 'w', encoding='utf-8') as f:
60
+ json.dump(history_list, f, indent=2)
@@ -0,0 +1,136 @@
1
+ from rich.console import Console
2
+ from rich.markdown import Markdown
3
+ from prompt_toolkit import PromptSession
4
+ from prompt_toolkit.key_binding import KeyBindings
5
+ from prompt_toolkit.enums import EditingMode
6
+ from prompt_toolkit.formatted_text import HTML
7
+ from prompt_toolkit.styles import Style
8
+ from prompt_toolkit.history import InMemoryHistory
9
+
10
+
11
+ def print_summary(console, data, continue_session):
12
+ if not data:
13
+ return
14
+ msgs = data.get('messages', [])
15
+ last_user = next((m['content'] for m in reversed(msgs) if m.get('role') == 'user'), None)
16
+ last_assistant = next((m['content'] for m in reversed(msgs) if m.get('role') == 'assistant'), None)
17
+ usage = data.get('last_usage_info', {})
18
+ console.print('[bold cyan]Last saved conversation:[/bold cyan]')
19
+ console.print(f"Messages: {len(msgs)}")
20
+ if last_user:
21
+ console.print(f"Last user: [italic]{last_user[:100]}{'...' if len(last_user)>100 else ''}[/italic]")
22
+ if last_assistant:
23
+ console.print(f"Last assistant: [italic]{last_assistant[:100]}{'...' if len(last_assistant)>100 else ''}[/italic]")
24
+ if usage:
25
+ ptok = usage.get('prompt_tokens')
26
+ ctok = usage.get('completion_tokens')
27
+ tot = (ptok or 0) + (ctok or 0)
28
+ console.print(f"Tokens - Prompt: {ptok}, Completion: {ctok}, Total: {tot}")
29
+ if not continue_session:
30
+ console.print("[bold yellow]Type /continue to restore the last saved conversation.[/bold yellow]")
31
+
32
+
33
+ def print_welcome(console, version=None):
34
+ 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
+ console.print("[yellow]To resume your previous conversation, type /continue at any time.[/yellow]")
37
+
38
+
39
+ def get_toolbar_func(messages_ref, last_usage_info_ref, last_elapsed_ref, model_name=None, role_ref=None):
40
+ def format_tokens(n):
41
+ if n is None:
42
+ return "?"
43
+ if n >= 1_000_000:
44
+ return f"{n/1_000_000:.1f}m"
45
+ if n >= 1_000:
46
+ return f"{n/1_000:.1f}k"
47
+ return str(n)
48
+
49
+ def get_toolbar():
50
+ left = f' Messages: <msg_count>{len(messages_ref())}</msg_count>'
51
+ usage = last_usage_info_ref()
52
+ last_elapsed = last_elapsed_ref()
53
+ if usage:
54
+ prompt_tokens = usage.get('prompt_tokens')
55
+ completion_tokens = usage.get('completion_tokens')
56
+ total_tokens = (prompt_tokens or 0) + (completion_tokens or 0)
57
+ speed = None
58
+ if last_elapsed and last_elapsed > 0:
59
+ speed = total_tokens / last_elapsed
60
+ left += (
61
+ f" | Tokens: In=<tokens_in>{format_tokens(prompt_tokens)}</tokens_in> / "
62
+ f"Out=<tokens_out>{format_tokens(completion_tokens)}</tokens_out> / "
63
+ f"Total=<tokens_total>{format_tokens(total_tokens)}</tokens_total>"
64
+ )
65
+ if speed is not None:
66
+ left += f", speed=<speed>{speed:.1f}</speed> tokens/sec"
67
+
68
+ from prompt_toolkit.application import get_app
69
+
70
+ # Compose first line with Model and Role
71
+ try:
72
+ width = get_app().output.get_size().columns
73
+ except Exception:
74
+ width = 80 # fallback default
75
+
76
+ model_part = f" Model: <model>{model_name}</model>" if model_name else ""
77
+ role_part = ""
78
+ if role_ref:
79
+ role = role_ref()
80
+ if role:
81
+ role_part = f"Role: <b>{role}</b>"
82
+
83
+ first_line_parts = []
84
+ if model_part:
85
+ first_line_parts.append(model_part)
86
+ if role_part:
87
+ first_line_parts.append(role_part)
88
+ first_line = " | ".join(first_line_parts)
89
+
90
+ help_part = "<b>/help</b> for help"
91
+
92
+ total_len = len(left) + len(help_part) + 3 # separators and spaces
93
+ if first_line:
94
+ total_len += len(first_line) + 3
95
+
96
+ if total_len < width:
97
+ padding = ' ' * (width - total_len)
98
+ second_line = f"{left}{padding} | {help_part}"
99
+ else:
100
+ second_line = f"{left} | {help_part}"
101
+
102
+ if first_line:
103
+ toolbar_text = first_line + "\n" + second_line
104
+ else:
105
+ toolbar_text = second_line
106
+
107
+ return HTML(toolbar_text)
108
+
109
+ return get_toolbar
110
+
111
+
112
+ def get_prompt_session(get_toolbar_func, mem_history):
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
+ session = PromptSession(
129
+ multiline=False,
130
+ key_bindings=KeyBindings(),
131
+ editing_mode=EditingMode.EMACS,
132
+ bottom_toolbar=get_toolbar_func,
133
+ style=style,
134
+ history=mem_history
135
+ )
136
+ return session
@@ -0,0 +1,12 @@
1
+ import jinja2
2
+ from pathlib import Path
3
+
4
+ def render_system_prompt(role: str) -> str:
5
+ template_loader = jinja2.FileSystemLoader(searchpath=str(Path(__file__).parent / "templates"))
6
+ env = jinja2.Environment(loader=template_loader)
7
+ template = env.get_template("system_instructions.j2")
8
+ return template.render(role=role)
9
+
10
+ if __name__ == "__main__":
11
+ prompt = render_system_prompt("software engineer")
12
+ print(prompt)
@@ -0,0 +1,36 @@
1
+ You are a helpful {{ role }}.
2
+
3
+ <context>
4
+ Always review `docs/structure.md` before conducting file-specific searches.
5
+ Unless specified otherwise, look for the files that match the questions context.
6
+ Explore files that might be relevant to the current task.
7
+ </context>
8
+
9
+ <analysis>
10
+ When analyzing issues, you might want to look into the git history for clues.
11
+ When using read_file specific a minimum of 100 lines.
12
+ </analysis>
13
+
14
+ <editing>
15
+ If in doubt during editing, use the `ask_user` function to get additional information; otherwise, proceed and inform the user of the decision made.
16
+
17
+ When you need to make changes to a file, consider the following:
18
+
19
+ - Use `file_str_replace` when you want to update or fix specific text fragments within a file without altering the rest of its content. It is preferred over full file replacement when:
20
+ - Only small, targeted changes are needed.
21
+ - You want to avoid the risk of accidentally overwriting unrelated content.
22
+ - The file is large, and rewriting the entire file would be inefficient.
23
+ - You want to preserve formatting, comments, or code structure outside the replaced text.
24
+
25
+ - When replacing files, review their current content before requesting the update.
26
+
27
+ - When reorganizing, moving files, or functions, search for references in other files that might need to be updated accordingly.
28
+ </editing>
29
+
30
+ <finishing>
31
+ After performing changes:
32
+
33
+ - Review the README content if there are user-exposed or public API changes.
34
+ - Use `git commit` to save the changes. Unless requested otherwise, review `git diff` when composing the commit message.
35
+ - Review `docs/structure.md` considering discovered, created, or modified files.
36
+ </finishing>
janito/web/__init__.py ADDED
File without changes
janito/web/__main__.py ADDED
@@ -0,0 +1,17 @@
1
+ import sys
2
+ from . import app
3
+
4
+
5
+ def main():
6
+ port = 8000
7
+ if len(sys.argv) > 1:
8
+ try:
9
+ port = int(sys.argv[1])
10
+ except ValueError:
11
+ print(f"Invalid port number: {sys.argv[1]}")
12
+ sys.exit(1)
13
+ app.app.run(host='0.0.0.0', port=port, debug=True)
14
+
15
+
16
+ if __name__ == '__main__':
17
+ main()
janito/web/app.py ADDED
@@ -0,0 +1,132 @@
1
+ from flask import Flask, request, render_template, Response, send_from_directory, session, jsonify
2
+ from queue import Queue
3
+ import json
4
+ from janito.agent.queued_tool_handler import QueuedToolHandler
5
+ from janito.agent.agent import Agent
6
+ from janito.agent.config import get_api_key
7
+ from janito.render_prompt import render_system_prompt
8
+ import os
9
+ import threading
10
+
11
+ # Render system prompt once
12
+ system_prompt = render_system_prompt("software engineer")
13
+
14
+ app = Flask(
15
+ __name__,
16
+ static_url_path='/static',
17
+ static_folder=os.path.join(os.path.dirname(__file__), 'static')
18
+ )
19
+
20
+ # Secret key for session management
21
+ app.secret_key = 'replace_with_a_secure_random_secret_key'
22
+
23
+ # Path for persistent conversation storage
24
+ conversation_file = os.path.expanduser('~/.janito/last_conversation_web.json')
25
+
26
+ # Initially no conversation loaded
27
+ conversation = None
28
+
29
+
30
+ # Global event queue for streaming
31
+ stream_queue = Queue()
32
+
33
+ # Create a QueuedToolHandler with the queue
34
+ queued_handler = QueuedToolHandler(stream_queue)
35
+
36
+ # Instantiate the Agent with the custom tool handler
37
+ agent = Agent(
38
+ api_key=get_api_key(),
39
+ tool_handler=queued_handler
40
+ )
41
+
42
+ @app.route('/get_model_name')
43
+ def get_model_name():
44
+ return jsonify({"model": agent.model})
45
+
46
+
47
+ @app.route('/favicon.ico')
48
+ def favicon():
49
+ return send_from_directory(
50
+ os.path.join(app.root_path, 'static'),
51
+ 'favicon.ico',
52
+ mimetype='image/vnd.microsoft.icon'
53
+ )
54
+
55
+
56
+ @app.route('/')
57
+ def index():
58
+ return render_template('index.html')
59
+
60
+ @app.route('/load_conversation')
61
+ def load_conversation():
62
+ global conversation
63
+ try:
64
+ with open(conversation_file, 'r') as f:
65
+ conversation = json.load(f)
66
+ except (FileNotFoundError, json.JSONDecodeError):
67
+ conversation = []
68
+ return jsonify({'status': 'ok', 'conversation': conversation})
69
+
70
+ @app.route('/new_conversation', methods=['POST'])
71
+ def new_conversation():
72
+ global conversation
73
+ conversation = []
74
+ return jsonify({'status': 'ok'})
75
+
76
+ @app.route('/execute_stream', methods=['POST'])
77
+ def execute_stream():
78
+ data = request.get_json()
79
+ user_input = data.get('input', '')
80
+
81
+ global conversation
82
+ if conversation is None:
83
+ # If no conversation loaded, start a new one
84
+ conversation = []
85
+
86
+ # Always start with the system prompt as the first message
87
+ if not conversation or conversation[0]['role'] != 'system':
88
+ conversation.insert(0, {"role": "system", "content": system_prompt})
89
+
90
+ # Append the new user message
91
+ conversation.append({"role": "user", "content": user_input})
92
+
93
+ def run_agent():
94
+ response = agent.chat(
95
+ conversation,
96
+ on_content=lambda data: stream_queue.put({"type": "content", "content": data.get("content")})
97
+ )
98
+ if response and 'content' in response:
99
+ conversation.append({"role": "assistant", "content": response['content']})
100
+ try:
101
+ os.makedirs(os.path.dirname(conversation_file), exist_ok=True)
102
+ with open(conversation_file, 'w') as f:
103
+ json.dump(conversation, f, indent=2)
104
+ except Exception as e:
105
+ print(f"Error saving conversation: {e}")
106
+ stream_queue.put(None)
107
+
108
+ threading.Thread(target=run_agent, daemon=True).start()
109
+
110
+ def generate():
111
+ while True:
112
+ content = stream_queue.get()
113
+ if content is None:
114
+ break
115
+ if isinstance(content, tuple) and content[0] == 'tool_progress':
116
+ message = json.dumps({"type": "tool_progress", "data": content[1]})
117
+ else:
118
+ message = json.dumps(content)
119
+ yield f"data: {message}\n\n"
120
+ import sys
121
+ sys.stdout.flush()
122
+
123
+ return Response(
124
+ generate(),
125
+ mimetype='text/event-stream',
126
+ headers={
127
+ 'Cache-Control': 'no-cache',
128
+ 'X-Accel-Buffering': 'no',
129
+ 'Connection': 'keep-alive',
130
+ 'Transfer-Encoding': 'chunked'
131
+ }
132
+ )
@@ -0,0 +1,144 @@
1
+ Metadata-Version: 2.4
2
+ Name: janito
3
+ Version: 1.0.0
4
+ Summary: An agent framework with built-in tools.
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
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Requires-Python: >=3.8
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: rich
17
+ Requires-Dist: openai
18
+ Requires-Dist: flask
19
+ Requires-Dist: pathspec
20
+ Dynamic: license-file
21
+
22
+ # 🚀 Janito: Natural Language Code Editing Agent
23
+
24
+ ## ⚡ Quick Start
25
+
26
+ Run a one-off prompt:
27
+ ```bash
28
+ python -m janito "Refactor the data processing module to improve readability."
29
+ ```
30
+
31
+ Or start the interactive chat shell:
32
+ ```bash
33
+ python -m janito
34
+ ```
35
+
36
+ Launch the web UI:
37
+ ```bash
38
+ python -m janito.web
39
+ ```
40
+
41
+ ---
42
+
43
+ Janito is a command-line and web-based AI agent designed to **edit code and manage files** using natural language instructions.
44
+
45
+ ---
46
+
47
+ ## ✨ Key Features
48
+ - 📝 **Code Editing via Natural Language:** Modify, create, or delete code files simply by describing the changes.
49
+ - 📁 **File & Directory Management:** Navigate, create, move, or remove files and folders.
50
+ - 🧠 **Context-Aware:** Understands your project structure for precise edits.
51
+ - 💬 **Interactive User Prompts:** Asks for clarification when needed.
52
+ - 🧩 **Extensible Tooling:** Built-in tools for file operations, shell commands, and more.
53
+ - 🌐 **Web Interface (In Development):** Upcoming simple web UI for streaming responses and tool progress.
54
+
55
+ ---
56
+
57
+ ## 📦 Installation
58
+
59
+ ### Requirements
60
+ - Python 3.8+
61
+
62
+ ### Install dependencies
63
+ ```bash
64
+ pip install -e .
65
+ ```
66
+
67
+ ### Set your API key
68
+ Janito uses OpenAI-compatible APIs (default: `openrouter/optimus-alpha`). Set your API key using the CLI:
69
+ ```bash
70
+ python -m janito --set-api-key your_api_key_here
71
+ ```
72
+
73
+ ### Obtain an API key from openrouter.io
74
+ 1. Visit [https://openrouter.io/](https://openrouter.io/)
75
+ 2. Sign in or create a free account.
76
+ 3. Navigate to **API Keys** in your account dashboard.
77
+ 4. Click **Create new key**, provide a name, and save the generated key.
78
+ 5. Save it using the CLI:
79
+ ```bash
80
+ python -m janito --set-api-key your_api_key_here
81
+ ```
82
+
83
+ ---
84
+
85
+ ## ⚙️ Configuration
86
+
87
+ Janito supports multiple ways to configure API access, model, and behavior:
88
+
89
+ ### API Key
90
+
91
+ - Set via CLI:
92
+ ```bash
93
+ python -m janito --set-api-key your_api_key_here
94
+ ```
95
+
96
+ ### Configurable Options
97
+
98
+ | Key | Description | How to set | Default |
99
+ |-----------------|-----------------------------------------------------------|-----------------------------------------------------------------|--------------------------------------------|
100
+ | `api_key` | API key for OpenAI-compatible service | `--set-api-key`, config file | _None_ (required) |
101
+ | `model` | Model name to use | `--set-local-config model=...` or `--set-global-config` | `openrouter/optimus-alpha` |
102
+ | `base_url` | API base URL (OpenAI-compatible endpoint) | `--set-local-config base_url=...` or `--set-global-config` | `https://openrouter.ai/api/v1` |
103
+ | `role` | Role description for system prompt | CLI `--role` or config | "software engineer" |
104
+ | `system_prompt` | Override the entire system prompt | CLI `--system-prompt` or config | _Template-generated prompt_ |
105
+ | `temperature` | Sampling temperature (float, e.g., 0.0 - 2.0) | CLI `--temperature` or config | 0.2 |
106
+ | `max_tokens` | Maximum tokens for model response | CLI `--max-tokens` or config | 200000 |
107
+
108
+ ### Config files
109
+
110
+ - **Local config:** `.janito/config.json` (project-specific)
111
+ - **Global config:** `~/.config/janito/config.json` (user-wide)
112
+
113
+ Set values via:
114
+
115
+ ```bash
116
+ python -m janito --set-local-config key=value
117
+ python -m janito --set-global-config key=value
118
+ ```
119
+
120
+ ---
121
+
122
+ ## 🚀 Build and Release
123
+
124
+ Janito provides scripts for automated build and release to PyPI:
125
+
126
+ ### Bash (Linux/macOS)
127
+
128
+ ```bash
129
+ ./tools/release.sh
130
+ ```
131
+
132
+ ### PowerShell (Windows)
133
+
134
+ ```powershell
135
+ ./tools/release.ps1
136
+ ```
137
+
138
+ These scripts will:
139
+ - Check for required tools (`hatch`, `twine`)
140
+ - Validate the version in `pyproject.toml` against PyPI and git tags
141
+ - Build the package
142
+ - Upload to PyPI
143
+
144
+ ---