janito 1.2.0__py3-none-any.whl → 1.3.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 (48) hide show
  1. janito/__init__.py +1 -1
  2. janito/agent/agent.py +27 -10
  3. janito/agent/config.py +37 -9
  4. janito/agent/config_utils.py +9 -0
  5. janito/agent/conversation.py +19 -3
  6. janito/agent/runtime_config.py +1 -0
  7. janito/agent/tool_handler.py +154 -52
  8. janito/agent/tools/__init__.py +9 -8
  9. janito/agent/tools/ask_user.py +2 -1
  10. janito/agent/tools/fetch_url.py +27 -35
  11. janito/agent/tools/file_ops.py +72 -0
  12. janito/agent/tools/find_files.py +47 -26
  13. janito/agent/tools/get_lines.py +58 -0
  14. janito/agent/tools/py_compile.py +26 -0
  15. janito/agent/tools/python_exec.py +47 -0
  16. janito/agent/tools/remove_directory.py +38 -0
  17. janito/agent/tools/replace_text_in_file.py +67 -0
  18. janito/agent/tools/run_bash_command.py +134 -0
  19. janito/agent/tools/search_files.py +52 -0
  20. janito/cli/_print_config.py +12 -12
  21. janito/cli/arg_parser.py +6 -1
  22. janito/cli/config_commands.py +56 -8
  23. janito/cli/runner.py +21 -9
  24. janito/cli_chat_shell/chat_loop.py +5 -3
  25. janito/cli_chat_shell/commands.py +34 -37
  26. janito/cli_chat_shell/config_shell.py +1 -1
  27. janito/cli_chat_shell/load_prompt.py +1 -1
  28. janito/cli_chat_shell/session_manager.py +11 -15
  29. janito/cli_chat_shell/ui.py +17 -8
  30. janito/render_prompt.py +3 -1
  31. janito/web/app.py +76 -19
  32. janito-1.3.0.dist-info/METADATA +142 -0
  33. janito-1.3.0.dist-info/RECORD +51 -0
  34. janito/agent/tools/bash_exec.py +0 -58
  35. janito/agent/tools/create_directory.py +0 -19
  36. janito/agent/tools/create_file.py +0 -43
  37. janito/agent/tools/file_str_replace.py +0 -48
  38. janito/agent/tools/move_file.py +0 -37
  39. janito/agent/tools/remove_file.py +0 -19
  40. janito/agent/tools/search_text.py +0 -41
  41. janito/agent/tools/view_file.py +0 -34
  42. janito/templates/system_instructions.j2 +0 -38
  43. janito-1.2.0.dist-info/METADATA +0 -85
  44. janito-1.2.0.dist-info/RECORD +0 -51
  45. {janito-1.2.0.dist-info → janito-1.3.0.dist-info}/WHEEL +0 -0
  46. {janito-1.2.0.dist-info → janito-1.3.0.dist-info}/entry_points.txt +0 -0
  47. {janito-1.2.0.dist-info → janito-1.3.0.dist-info}/licenses/LICENSE +0 -0
  48. {janito-1.2.0.dist-info → janito-1.3.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,142 @@
1
+ Metadata-Version: 2.4
2
+ Name: janito
3
+ Version: 1.3.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: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Requires-Python: >=3.10
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: beautifulsoup4
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: Agent
27
+
28
+ 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.
29
+
30
+ ---
31
+
32
+ ## ⚡ Quick Start
33
+
34
+ Run a one-off prompt:
35
+ ```bash
36
+ python -m janito "Refactor the data processing module to improve readability."
37
+ ```
38
+
39
+ Or start the interactive chat shell:
40
+ ```bash
41
+ python -m janito
42
+ ```
43
+
44
+ Launch the web UI:
45
+ ```bash
46
+ python -m janito.web
47
+ ```
48
+
49
+ ---
50
+
51
+ ## ✨ Key Features
52
+ - 📝 **Code Editing via Natural Language:** Modify, create, or delete code files simply by describing the changes.
53
+ - 📁 **File & Directory Management:** Navigate, create, move, or remove files and folders.
54
+ - 🧠 **Context-Aware:** Understands your project structure for precise edits.
55
+ - 💬 **Interactive User Prompts:** Asks for clarification when needed.
56
+ - 🧩 **Extensible Tooling:** Built-in tools for file operations, shell commands, directory listing, Python file validation, text replacement, code execution, and more. Recent tools include:
57
+ - `find_files`: Search for files matching a pattern in directories.
58
+ - `get_lines`: Retrieve specific lines from files for efficient context.
59
+ - `py_compile_file`: Validate Python files for syntax correctness.
60
+ - `replace_text_in_file`: Replace exact text fragments in files.
61
+ - `search_files`: Search for text patterns across files.
62
+ - `python_exec`: Execute Python code and capture output.
63
+ - And more, see `janito/agent/tools/` for the full list.
64
+ - 🌐 **Web Interface (In Development):** Upcoming simple web UI for streaming responses and tool progress.
65
+
66
+ ---
67
+
68
+
69
+ ## 📦 Installation
70
+
71
+ ### Requirements
72
+ - Python 3.10+
73
+
74
+ ...
75
+
76
+ ### Configuration & CLI Options
77
+
78
+ Below are the supported configuration parameters and CLI flags. Some options can be set via config files, CLI flags, or both. Use `python -m janito --help` for a full list, or `python -m janito --help-config` to see all config keys and their descriptions.
79
+
80
+ | Key / Flag | Description | How to set | Default |
81
+ |---------------------------|---------------------------------------------------------------------------------------------|-----------------------------------------------------------------|--------------------------------------------|
82
+ | `api_key` | API key for OpenAI-compatible service | `--set-api-key`, config file | _None_ (required) |
83
+ | `model` | Model name to use for this session | `--model` (session only), `--set-local-config model=...`, or `--set-global-config` | `openai/gpt-4.1` |
84
+ | `base_url` | API base URL (OpenAI-compatible endpoint) | `--set-local-config base_url=...` or `--set-global-config` | `https://openrouter.ai/api/v1` |
85
+ | `role` | Role description for the system prompt | `--role` or config | "software engineer" |
86
+ | `system_prompt` | Override the entire system prompt as a raw string | `--system-prompt` or config | _Default prompt_ |
87
+ | `system_file` | Use a plain text file as the system prompt (takes precedence over `system_prompt`) | `--system-file` (CLI only) | _None_ |
88
+ | `temperature` | Sampling temperature (float, e.g., 0.0 - 2.0) | `--temperature` or config | 0.2 |
89
+ | `max_tokens` | Maximum tokens for model response | `--max-tokens` or config | 200000 |
90
+ | `max_rounds` | Maximum number of agent rounds per prompt/session | `--max-rounds` or config | 50 |
91
+ | `max_tools` | Maximum number of tool calls allowed within a chat session | `--max-tools` or config | _None_ (unlimited) |
92
+ | `no_tools` | Disable tool use (no tools passed to agent) | `-n`, `--no-tools` (CLI only) | False |
93
+ | `trust` | Trust mode: suppresses run_bash_command output, only shows output file locations | `--trust` (CLI only) | False |
94
+ | `template` / `template.*` | Template context dictionary for prompt rendering (nested or flat keys) | Config only | _None_ |
95
+
96
+ Other config-related CLI flags:
97
+
98
+ - `--set-local-config key=val` Set a local config value
99
+ - `--set-global-config key=val` Set a global config value
100
+ - `--run-config key=val` Set a runtime (in-memory only) config value (can be repeated)
101
+ - `--show-config` Show effective configuration and exit
102
+ - `--config-reset-local` Remove the local config file
103
+ - `--config-reset-global` Remove the global config file
104
+ - `--set-api-key KEY` Set and save the API key globally
105
+ - `--help-config` Show all configuration options and exit
106
+
107
+ Session & shell options:
108
+
109
+ - `--continue-session` Continue from the last saved conversation
110
+ - `--web` Launch the Janito web server instead of CLI
111
+
112
+ Verbose/debugging flags:
113
+
114
+ - `--verbose-http` Enable verbose HTTP logging
115
+ - `--verbose-http-raw` Enable raw HTTP wire-level logging
116
+ - `--verbose-response` Pretty print the full response object
117
+ - `--verbose-tools` Print tool call parameters and results
118
+ - `--show-system` Show model, parameters, system prompt, and tool definitions, then exit
119
+ - `--version` Show program's version number and exit
120
+
121
+
122
+
123
+ ---
124
+
125
+ ## 🧩 System Prompt & Role
126
+
127
+ 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.
128
+
129
+ - **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`.
130
+ - **System Prompt Template:** The system prompt is rendered from a Jinja2 template (see `janito/agent/templates/system_instructions.j2` (now located directly under the agent directory)). This template governs how the agent interprets instructions, interacts with files, and communicates with users.
131
+ - **Customization:** Advanced users can override the system prompt with the `--system-prompt` flag (raw string), or point to a custom file using `--system-file`.
132
+
133
+ The default template ensures the agent:
134
+ - Prioritizes safe, reviewable, and minimal changes
135
+ - Asks for clarification when instructions are ambiguous
136
+ - Provides concise plans before taking action
137
+ - Documents any changes made
138
+
139
+ For more details or to customize the prompt, see the template file at `janito/agent/templates/system_instructions.j2`.
140
+
141
+ ---
142
+
@@ -0,0 +1,51 @@
1
+ janito/__init__.py,sha256=N53F87jnDXnPWTxIl8Act-J_9sVbOVV8kluLQav-f9o,23
2
+ janito/__main__.py,sha256=CBScR30Tm-vuhIJM8o5HXKr0q-smICiwSVyuU68BP8U,78
3
+ janito/render_prompt.py,sha256=xCQgYRqMyz9Pzi7096NoZfC4lFiKEHEaXiTYP6ByViY,513
4
+ janito/agent/__init__.py,sha256=CByAH5Yk-yH64zo0RU7Z3nsn_7Vmandphqk0JNlpyj8,21
5
+ janito/agent/agent.py,sha256=X5swt-SUKnIxrSQaGfhxPk61M-BXREz4n02ynE0dVVU,4245
6
+ janito/agent/config.py,sha256=uGjqQPLQZxzQ6JdpmgRdA1g0g-R7zeHLwidQI78JSJw,4392
7
+ janito/agent/config_defaults.py,sha256=Ow-LKq88MmMTQ6LDH_u26NqJ8-db35KpcfR8FYuWGBw,363
8
+ janito/agent/config_utils.py,sha256=UmvR236wDrMc-aTy9LxVbop6YeoJaaPb1d2DBMlkSRg,254
9
+ janito/agent/conversation.py,sha256=vbCL-1LlJjKRN6q3CjfSe4ENGplP-YQKsAEA1uX3RWU,6184
10
+ janito/agent/queued_tool_handler.py,sha256=THPymKXnpoXfN49EhW5b4hrwpWZZup73JKFDJ_U03tI,540
11
+ janito/agent/runtime_config.py,sha256=H6bnRVYR0zC-WhmLpBU-IXikXWKcIiBQqFnc4uGooj4,908
12
+ janito/agent/tool_handler.py,sha256=sKnySRfZQBUjZxve8CH74me9Q4_ewtIege_HBiCs9wY,8291
13
+ janito/agent/tools/__init__.py,sha256=j0MGKIrtEkI3f6yumsJeDqiPyvDMehZ_3e2Osujev3c,486
14
+ janito/agent/tools/ask_user.py,sha256=-omTj2woHGbl4xR14sB3dbnRD1zGIbvKv4wjq9g4NXE,1917
15
+ janito/agent/tools/fetch_url.py,sha256=tce4L5LDiIsSreo-aJVmpwu1yCkDCY0kEZId3lgGiCU,1578
16
+ janito/agent/tools/file_ops.py,sha256=vu3DttsaPfqBX82-UhPY079CmMXFDuaCXtre5kOXKW0,3171
17
+ janito/agent/tools/find_files.py,sha256=OxJghgmPvk3RFF5N_6CCW_ftB6RgG3QzeBzc1bItLKA,2353
18
+ janito/agent/tools/get_lines.py,sha256=eazwPkwB_P9VkHN-ubOZK5lK5qOJGU0QXVOb6RwQl3g,2948
19
+ janito/agent/tools/gitignore_utils.py,sha256=zXiqx4HetZ7iKkV5qXyHyZ5yrrVex4G17WHBWToFo3Y,1158
20
+ janito/agent/tools/py_compile.py,sha256=bQ6P0Vr8FBZQ_hqNjIEylJIzeiH2aggRjNW_WCgG7sA,1042
21
+ janito/agent/tools/python_exec.py,sha256=C4qG6Um9RsnpHqii1Z3mVFp3qXnaL87Ns0mJSeBgOJc,1769
22
+ janito/agent/tools/remove_directory.py,sha256=CUzXaotktgpyX6LUsfK_-m91Onkwk9wbdAZcUayt2jw,1727
23
+ janito/agent/tools/replace_text_in_file.py,sha256=2uOZJW2SzZPFp7_wcU8Hw38pKWDWkmq_24mxiUrRWDo,3533
24
+ janito/agent/tools/rich_live.py,sha256=cuZ3-ZxpuHxR1TvIcp0bi9F3QM1M6Ms0XiOMd8If8gU,1161
25
+ janito/agent/tools/rich_utils.py,sha256=aQMqeaq3hIpzZ5EHQBNTKS5dNsojQp9MDfJSoqOQe0k,837
26
+ janito/agent/tools/run_bash_command.py,sha256=AUswjRcVHODhHQtx1fH3zQqQjSftolxAwhNzAtnj8BY,6020
27
+ janito/agent/tools/search_files.py,sha256=HbLq9oHcb9udrbql6-QE53zo5F3UeuhlptMvf2mIQVo,1945
28
+ janito/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
+ janito/cli/_print_config.py,sha256=vMMlCY-hJPsLHUxx9TJuOY9iyYVBH8C4nVACCwY4l5U,3294
30
+ janito/cli/_utils.py,sha256=Q_OCFZmbr78qW4hSSUUhjondVc0ao7-iUHE7Ig8IP1g,289
31
+ janito/cli/arg_parser.py,sha256=4dSxHk02VD2_-utR5XCDt6Xs7tsye2HeQmTbaJiWFME,3724
32
+ janito/cli/config_commands.py,sha256=kYZ_SlHkZRlg-hLAdYKj50qnJgPCZUbW7zU4FykE7XQ,8098
33
+ janito/cli/logging_setup.py,sha256=dWQ0wFf4YuF5lxZlhpN6lis7G56LeFYUwQdh9nA5NmA,1141
34
+ janito/cli/main.py,sha256=ONmn_lIPu8_Rd57j3YfWEx46fWj8gAkONPLdctABwy0,1333
35
+ janito/cli/runner.py,sha256=64BErYxTvJjzFPKpbQvOoq6ARiEbPp03ZGFZBVogkgg,5043
36
+ janito/cli_chat_shell/__init__.py,sha256=PDGl7xK_vgkROoXvUxGZqVQFfuL9U4TjdexpP8E2JKg,41
37
+ janito/cli_chat_shell/chat_loop.py,sha256=AcryIgXb36g6214XKj75Xx4VxOYG-i8_IC_ROZyOLdk,5516
38
+ janito/cli_chat_shell/commands.py,sha256=ytt_5dym2sKOIlTjwAmfMg2jQVBlenDbPNwKJasPsDg,7245
39
+ janito/cli_chat_shell/config_shell.py,sha256=WlOxt7YOTIJluD3hURXbtOSodPHPnCwcO0LaH8_XXJE,3301
40
+ janito/cli_chat_shell/load_prompt.py,sha256=B7C_IhMk3n1XBQcfOwFVDnjrj1278JL3kV4UtkeeZx8,604
41
+ janito/cli_chat_shell/session_manager.py,sha256=PdqmFw4jZaCtQpazjgkKk_yhleZrzZOz_QqFeIf9Km4,1818
42
+ janito/cli_chat_shell/ui.py,sha256=9IngH8HFherjatfa9Eu37jb4b2gOP0lsgcUhbnQKCZA,5557
43
+ janito/web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
44
+ janito/web/__main__.py,sha256=oPXNF332aCeI7aUWr7_8M57oOKugw422VrEubxFp0P4,354
45
+ janito/web/app.py,sha256=stogs_HaM-axsWTwoiVma9M46Y8dMu0QRjopsekRjQs,6622
46
+ janito-1.3.0.dist-info/licenses/LICENSE,sha256=sHBqv0bvtrb29H7WRR-Z603YHm9pLtJIo3nHU_9cmgE,1091
47
+ janito-1.3.0.dist-info/METADATA,sha256=5qk7QmINq8vmDCcUMj00IuWrOSMEj_NPNeiroXKH_fo,9071
48
+ janito-1.3.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
49
+ janito-1.3.0.dist-info/entry_points.txt,sha256=wIo5zZxbmu4fC-ZMrsKD0T0vq7IqkOOLYhrqRGypkx4,48
50
+ janito-1.3.0.dist-info/top_level.txt,sha256=m0NaVCq0-ivxbazE2-ND0EA9Hmuijj_OGkmCbnBcCig,7
51
+ janito-1.3.0.dist-info/RECORD,,
@@ -1,58 +0,0 @@
1
- from janito.agent.tool_handler import ToolHandler
2
- from janito.agent.tools.rich_utils import print_info, print_bash_stdout, print_bash_stderr
3
- import subprocess
4
- import threading
5
- from typing import Callable, Optional
6
-
7
-
8
- @ToolHandler.register_tool
9
- def bash_exec(command: str, on_progress: Optional[Callable[[dict], None]] = None) -> str:
10
- """
11
- command: The Bash command to execute.
12
- on_progress: Optional callback function for streaming progress updates.
13
-
14
- Execute a non interactive bash command and print output live.
15
-
16
- Returns:
17
- str: A formatted message string containing stdout, stderr, and return code.
18
- """
19
- print_info(f"[bash_exec] Executing command: {command}")
20
- result = {'stdout': '', 'stderr': '', 'returncode': None}
21
-
22
- def run_command():
23
- try:
24
- process = subprocess.Popen(
25
- command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding='utf-8', errors='replace'
26
- )
27
- stdout_lines = []
28
- stderr_lines = []
29
-
30
- def read_stream(stream, collector, print_func, stream_name):
31
- for line in iter(stream.readline, ''):
32
- collector.append(line)
33
- print_func(line.rstrip())
34
- if callable(on_progress):
35
- on_progress({'stream': stream_name, 'line': line.rstrip()})
36
- stream.close()
37
-
38
- stdout_thread = threading.Thread(target=read_stream, args=(process.stdout, stdout_lines, print_bash_stdout, 'stdout'))
39
- stderr_thread = threading.Thread(target=read_stream, args=(process.stderr, stderr_lines, print_bash_stderr, 'stderr'))
40
- stdout_thread.start()
41
- stderr_thread.start()
42
- stdout_thread.join()
43
- stderr_thread.join()
44
- result['returncode'] = process.wait()
45
- result['stdout'] = ''.join(stdout_lines)
46
- result['stderr'] = ''.join(stderr_lines)
47
- except Exception as e:
48
- result['stderr'] = str(e)
49
- result['returncode'] = -1
50
-
51
- thread = threading.Thread(target=run_command)
52
- thread.start()
53
- thread.join() # Wait for the thread to finish
54
-
55
- print_info(f"[bash_exec] Command execution completed.")
56
- print_info(f"[bash_exec] Return code: {result['returncode']}")
57
-
58
- return f"stdout:\n{result['stdout']}\nstderr:\n{result['stderr']}\nreturncode: {result['returncode']}"
@@ -1,19 +0,0 @@
1
- import os
2
- from janito.agent.tool_handler import ToolHandler
3
- from janito.agent.tools.rich_utils import print_info, print_success, print_error, format_path
4
-
5
- @ToolHandler.register_tool
6
- def create_directory(path: str) -> str:
7
- """
8
- Create a directory at the specified path.
9
-
10
- path: The path of the directory to create
11
- """
12
- print_info(f"📁 Creating directory: '{format_path(path)}' ... ")
13
- try:
14
- os.makedirs(path, exist_ok=True)
15
- print_success("✅ Success")
16
- return f"✅ Directory '{path}' created successfully."
17
- except Exception as e:
18
- print_error(f"❌ Error: {e}")
19
- return f"❌ Error creating directory '{path}': {e}"
@@ -1,43 +0,0 @@
1
- import os
2
- from janito.agent.tool_handler import ToolHandler
3
- from janito.agent.tools.rich_utils import print_info, print_success, print_error, format_path
4
-
5
- @ToolHandler.register_tool
6
- def create_file(path: str, content: str, overwrite: bool = False) -> str:
7
- """
8
- Create a file with the specified content.
9
-
10
- path: The path of the file to create
11
- content: The content to write into the file
12
- overwrite: Whether to overwrite the file if it exists (default: False)
13
- """
14
- old_lines = None
15
- if os.path.exists(path):
16
- if os.path.isdir(path):
17
- print_error("❌ Error: is a directory")
18
- return f"❌ Cannot create file: '{path}' is an existing directory."
19
- if overwrite:
20
- try:
21
- with open(path, "r", encoding="utf-8") as f:
22
- old_lines = sum(1 for _ in f)
23
- except Exception:
24
- old_lines = 'unknown'
25
- else:
26
- print_error(f"❗ Error: file '{path}' exists and overwrite is False")
27
- return f"❗ Cannot create file: '{path}' already exists and overwrite is False."
28
-
29
- new_lines = content.count('\n') + 1 if content else 0
30
-
31
- if old_lines is not None:
32
- print_info(f"♻️ Replacing file: '{format_path(path)}' (line count: {old_lines} -> {new_lines}) ... ")
33
- else:
34
- print_info(f"📝 Creating file: '{format_path(path)}' (lines: {new_lines}) ... ")
35
-
36
- try:
37
- with open(path, "w", encoding="utf-8") as f:
38
- f.write(content)
39
- print_success("✅ Success")
40
- return f"✅ Successfully created the file at '{path}'."
41
- except Exception as e:
42
- print_error(f"❌ Error: {e}")
43
- return f"❌ Failed to create the file at '{path}': {e}"
@@ -1,48 +0,0 @@
1
- import os
2
- from janito.agent.tool_handler import ToolHandler
3
- from janito.agent.tools.rich_utils import print_info, print_success, print_error, format_path
4
-
5
- @ToolHandler.register_tool
6
- def file_str_replace(path: str, old_string: str, new_string: str) -> str:
7
- """
8
- Replace a unique occurrence of a string in a file.
9
-
10
- path: Path to the file
11
- old_string: The exact string to replace
12
- - must be unique within all the file lines
13
- new_string: The replacement string
14
-
15
-
16
-
17
- Returns a message indicating success on an error
18
- """
19
- if not os.path.isfile(path):
20
- print_error(f"❌ Error: '{path}' is not a valid file.")
21
- return f"❌ Error: '{path}' is not a valid file."
22
-
23
- try:
24
- with open(path, 'r', encoding='utf-8') as f:
25
- content = f.read()
26
- except Exception as e:
27
- print_error(f"❌ Error reading file: {e}")
28
- return f"❌ Failed to read file '{path}': {e}"
29
-
30
- num_matches = content.count(old_string)
31
-
32
- if num_matches == 0:
33
- print_info(f"ℹ️ No occurrences of the target string found in '{format_path(path)}'.")
34
- return f"ℹ️ No occurrences of the target string found in '{path}'."
35
- elif num_matches > 1:
36
- print_error(f"❌ Error: More than one occurrence ({num_matches}) of the target string found in '{format_path(path)}'. Aborting replacement.")
37
- return f"❌ Error: More than one occurrence ({num_matches}) of the target string found in '{path}'. Aborting replacement."
38
-
39
- new_content = content.replace(old_string, new_string, 1)
40
-
41
- try:
42
- with open(path, 'w', encoding='utf-8') as f:
43
- f.write(new_content)
44
- print_success(f"✅ Replaced the unique occurrence in '{format_path(path)}'.")
45
- return f"✅ Successfully replaced the unique occurrence in '{path}'."
46
- except Exception as e:
47
- print_error(f"❌ Error writing file: {e}")
48
- return f"❌ Failed to write updated content to '{path}': {e}"
@@ -1,37 +0,0 @@
1
- import shutil
2
- import os
3
- from janito.agent.tool_handler import ToolHandler
4
- from janito.agent.tools.rich_utils import print_info, print_success, print_error, format_path
5
-
6
-
7
- @ToolHandler.register_tool
8
- def move_file(source_path: str, destination_path: str, overwrite: bool = False) -> str:
9
- """
10
- Move a file or directory from source_path to destination_path.
11
-
12
- source_path: The path of the file or directory to move
13
- destination_path: The target path
14
- overwrite: Whether to overwrite the destination if it exists (default: False)
15
- """
16
- print_info(f"🚚 Moving '{format_path(source_path)}' to '{format_path(destination_path)}' ... ")
17
- try:
18
- if not os.path.exists(source_path):
19
- print_error("❌ Error: source does not exist")
20
- return f"❌ Source path '{source_path}' does not exist."
21
-
22
- if os.path.exists(destination_path):
23
- if not overwrite:
24
- print_error("❌ Error: destination exists and overwrite is False")
25
- return f"❌ Destination path '{destination_path}' already exists. Use overwrite=True to replace it."
26
- # Remove destination if overwrite is True
27
- if os.path.isdir(destination_path):
28
- shutil.rmtree(destination_path)
29
- else:
30
- os.remove(destination_path)
31
-
32
- shutil.move(source_path, destination_path)
33
- print_success("✅ Success")
34
- return f"✅ Successfully moved '{source_path}' to '{destination_path}'."
35
- except Exception as e:
36
- print_error(f"❌ Error: {e}")
37
- return f"❌ Failed to move '{source_path}' to '{destination_path}': {e}"
@@ -1,19 +0,0 @@
1
- import os
2
- from janito.agent.tool_handler import ToolHandler
3
- from janito.agent.tools.rich_utils import print_info, print_success, print_error, format_path
4
-
5
- @ToolHandler.register_tool
6
- def remove_file(path: str) -> str:
7
- """
8
- Remove a specified file.
9
-
10
- path: The path of the file to remove
11
- """
12
- print_info(f"🗑️ Removing file: '{format_path(path)}' ... ")
13
- try:
14
- os.remove(path)
15
- print_success("✅ Success")
16
- return f"✅ Successfully deleted the file at '{path}'."
17
- except Exception as e:
18
- print_error(f"❌ Error: {e}")
19
- return f"❌ Failed to delete the file at '{path}': {e}"
@@ -1,41 +0,0 @@
1
- import os
2
- import re
3
- import fnmatch
4
- from janito.agent.tool_handler import ToolHandler
5
- from janito.agent.tools.rich_utils import print_info, print_success, print_error, format_path, format_number
6
- from janito.agent.tools.gitignore_utils import load_gitignore_patterns, filter_ignored
7
-
8
- @ToolHandler.register_tool
9
- def search_text(directory: str, file_pattern: str, text_pattern: str, case_sensitive: bool = False):
10
- """
11
- directory: Root directory to search.
12
- file_pattern: Glob pattern for filenames (e.g., '*.py').
13
- text_pattern: Regex pattern to search within files.
14
- case_sensitive: Whether the search is case sensitive.
15
-
16
- Returns a string with matches, each in 'filepath:line_number:matched_line' format, separated by newlines.
17
- """
18
- print_info(f"🔎 Searching for pattern '{text_pattern}' in files under '{format_path(directory)}' matching '{file_pattern}' ...")
19
- flags = 0 if case_sensitive else re.IGNORECASE
20
- regex = re.compile(text_pattern, flags)
21
- results = []
22
- ignore_patterns = load_gitignore_patterns()
23
-
24
- try:
25
- for root, dirs, files in os.walk(directory):
26
- dirs, files = filter_ignored(root, dirs, files, ignore_patterns)
27
- for filename in fnmatch.filter(files, file_pattern):
28
- filepath = os.path.join(root, filename)
29
- try:
30
- with open(filepath, 'r', encoding='utf-8', errors='replace') as f:
31
- for lineno, line in enumerate(f, start=1):
32
- if regex.search(line):
33
- results.append(f"{filepath}:{lineno}:{line.rstrip()}")
34
- except Exception as e:
35
- print_error(f"❌ Error reading file '{filepath}': {e}")
36
- continue # Ignore unreadable files
37
- print_success(f"✅ Found {format_number(len(results))} matches")
38
- except Exception as e:
39
- print_error(f"❌ Error during search: {e}")
40
-
41
- return "\n".join(results)
@@ -1,34 +0,0 @@
1
- import os
2
- from janito.agent.tool_handler import ToolHandler
3
- from janito.agent.tools.rich_utils import print_info, print_success, print_error, format_path, format_number
4
-
5
- @ToolHandler.register_tool
6
- def view_file(path: str, start_line: int = 1, end_line: int = None) -> str:
7
- """
8
- View the contents of a file or list the contents of a directory.
9
-
10
- path: The path of the file or directory to view
11
- start_line: The starting line number (1-based, default: 1)
12
- end_line: The ending line number (inclusive). If None, view until end of file.
13
- """
14
- print_info(f"📂 View '{format_path(path)}' lines {format_number(start_line)} to {format_number(end_line) if end_line else 'end of file'}")
15
- if os.path.isdir(path):
16
- files = os.listdir(path)
17
- print_success(f"✅ {format_number(len(files))} items")
18
- return "\n".join(files)
19
- else:
20
- with open(path, "r", encoding="utf-8") as f:
21
- lines = f.readlines()
22
-
23
- total_lines = len(lines)
24
- if end_line is None or end_line > total_lines:
25
- end_line = total_lines
26
-
27
- # Adjust for 0-based index
28
- start_idx = max(start_line - 1, 0)
29
- end_idx = end_line
30
-
31
- selected_lines = lines[start_idx:end_idx]
32
- content = '\n'.join(f"{i + start_line}: {line.rstrip()}" for i, line in enumerate(selected_lines))
33
- print_success(f"✅ Returned lines {format_number(start_line)} to {format_number(end_line)} of {format_number(total_lines)}")
34
- return content
@@ -1,38 +0,0 @@
1
- You are a helpful {{ role }}.
2
-
3
- You will start every response with a concise plan on how to gather additional information.
4
-
5
- <context>
6
- Always review `docs/structure.md` before conducting file-specific searches.
7
- Unless specified otherwise, look for the files that match the questions context.
8
- Explore files that might be relevant to the current task.
9
- </context>
10
-
11
- <analysis>
12
- When analyzing issues, you might want to look into the git history for clues.
13
- When using read_file specific a minimum of 100 lines.
14
- </analysis>
15
-
16
- <editing>
17
- If in doubt during editing, use the `ask_user` function to get additional information; otherwise, proceed and inform the user of the decision made.
18
-
19
- When you need to make changes to a file, consider the following:
20
-
21
- - 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:
22
- - Only small, targeted changes are needed.
23
- - You want to avoid the risk of accidentally overwriting unrelated content.
24
- - The file is large, and rewriting the entire file would be inefficient.
25
- - You want to preserve formatting, comments, or code structure outside the replaced text.
26
-
27
- - When replacing files, review their current content before requesting the update.
28
-
29
- - When reorganizing, moving files, or functions, search for references in other files that might need to be updated accordingly.
30
- </editing>
31
-
32
- <finishing>
33
- After performing changes:
34
-
35
- - Review the README content if there are user-exposed or public API changes.
36
- - Use `git commit` to save the changes. Unless requested otherwise, review `git diff` when composing the commit message.
37
- - Review `docs/structure.md` considering discovered, created, or modified files.
38
- </finishing>
@@ -1,85 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: janito
3
- Version: 1.2.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
- Requires-Dist: beautifulsoup4
21
- Dynamic: license-file
22
-
23
- # 🚀 Janito: Natural Language Code Editing Agent
24
-
25
- ## ⚡ Quick Start
26
-
27
- Run a one-off prompt:
28
- ```bash
29
- python -m janito "Refactor the data processing module to improve readability."
30
- ```
31
-
32
- Or start the interactive chat shell:
33
- ```bash
34
- python -m janito
35
- ```
36
-
37
- Launch the web UI:
38
- ```bash
39
- python -m janito.web
40
- ```
41
-
42
- ---
43
-
44
- Janito is a command-line and web-based AI agent designed to **edit code and manage files** using natural language instructions.
45
-
46
- ---
47
-
48
- ## ✨ Key Features
49
- - 📝 **Code Editing via Natural Language:** Modify, create, or delete code files simply by describing the changes.
50
- - 📁 **File & Directory Management:** Navigate, create, move, or remove files and folders.
51
- - 🧠 **Context-Aware:** Understands your project structure for precise edits.
52
- - 💬 **Interactive User Prompts:** Asks for clarification when needed.
53
- - 🧩 **Extensible Tooling:** Built-in tools for file operations, shell commands, and more.
54
- - 🌐 **Web Interface (In Development):** Upcoming simple web UI for streaming responses and tool progress.
55
-
56
- ---
57
-
58
- ## 📦 Installation
59
-
60
- ### Requirements
61
- - Python 3.8+
62
-
63
- ...
64
-
65
- ### Configurable Options
66
-
67
- | Key | Description | How to set | Default |
68
- |---------------------|---------------------------------------------------------------------------------------------|-----------------------------------------------------------------|--------------------------------------------|
69
- | `api_key` | API key for OpenAI-compatible service | `--set-api-key`, config file | _None_ (required) |
70
- | `model` | Model name to use | `--model` (session only), `--set-local-config model=...`, or `--set-global-config` | `openai/gpt-4.1` |
71
- | `base_url` | API base URL (OpenAI-compatible endpoint) | `--set-local-config base_url=...` or `--set-global-config` | `https://openrouter.ai/api/v1` |
72
- | `role` | Role description for system prompt | CLI `--role` or config | "software engineer" |
73
- | `system_prompt` | Override the entire system prompt as a raw string. | CLI `--system-prompt` or config | _Template-generated prompt_ |
74
- | `system_prompt_file`| Use a plain text file as the system prompt (no template rendering, takes precedence over `system_prompt`). | CLI `--system-file` | _None_ |
75
- | `temperature` | Sampling temperature (float, e.g., 0.0 - 2.0) | CLI `--temperature` or config | 0.2 |
76
- | `max_tokens` | Maximum tokens for model response | CLI `--max-tokens` or config | 200000 |
77
- | `disable_tools` | Disable tool use (no tools passed to agent) | CLI `--disable-tools` | _False_ |
78
-
79
- #### System Prompt Precedence
80
-
81
- - If `--system-file` is provided, the file's content is used as the system prompt (no template rendering).
82
- - Otherwise, if `--system-prompt` or the config value is set, that string is used.
83
- - Otherwise, a default template is rendered using the current role.
84
-
85
- ...