janito 0.10.1__py3-none-any.whl → 0.12.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 (49) hide show
  1. janito/__init__.py +1 -1
  2. janito/__main__.py +3 -147
  3. janito/callbacks.py +13 -109
  4. janito/cli/__init__.py +6 -0
  5. janito/cli/agent.py +287 -0
  6. janito/cli/app.py +86 -0
  7. janito/cli/commands.py +329 -0
  8. janito/cli/output.py +29 -0
  9. janito/cli/utils.py +22 -0
  10. janito/config.py +338 -63
  11. janito/data/instructions_template.txt +27 -0
  12. janito/token_report.py +124 -43
  13. janito/tools/__init__.py +29 -1
  14. janito/tools/bash/bash.py +82 -0
  15. janito/tools/bash/unix_persistent_bash.py +182 -0
  16. janito/tools/bash/win_persistent_bash.py +306 -0
  17. janito/tools/decorators.py +90 -84
  18. janito/tools/delete_file.py +65 -44
  19. janito/tools/fetch_webpage/__init__.py +34 -0
  20. janito/tools/fetch_webpage/chunking.py +76 -0
  21. janito/tools/fetch_webpage/core.py +155 -0
  22. janito/tools/fetch_webpage/extractors.py +276 -0
  23. janito/tools/fetch_webpage/news.py +137 -0
  24. janito/tools/fetch_webpage/utils.py +108 -0
  25. janito/tools/find_files.py +108 -42
  26. janito/tools/move_file.py +72 -0
  27. janito/tools/prompt_user.py +57 -0
  28. janito/tools/replace_file.py +63 -0
  29. janito/tools/rich_console.py +139 -0
  30. janito/tools/search_text.py +33 -21
  31. janito/tools/str_replace_editor/editor.py +55 -43
  32. janito/tools/str_replace_editor/handlers/__init__.py +16 -0
  33. janito/tools/str_replace_editor/handlers/create.py +60 -0
  34. janito/tools/str_replace_editor/handlers/insert.py +100 -0
  35. janito/tools/str_replace_editor/handlers/str_replace.py +92 -0
  36. janito/tools/str_replace_editor/handlers/undo.py +64 -0
  37. janito/tools/str_replace_editor/handlers/view.py +153 -0
  38. janito/tools/str_replace_editor/utils.py +7 -62
  39. janito/tools/usage_tracker.py +136 -0
  40. janito-0.12.0.dist-info/METADATA +203 -0
  41. janito-0.12.0.dist-info/RECORD +47 -0
  42. janito/cli.py +0 -202
  43. janito/data/instructions.txt +0 -4
  44. janito/tools/str_replace_editor/handlers.py +0 -338
  45. janito-0.10.1.dist-info/METADATA +0 -86
  46. janito-0.10.1.dist-info/RECORD +0 -23
  47. {janito-0.10.1.dist-info → janito-0.12.0.dist-info}/WHEEL +0 -0
  48. {janito-0.10.1.dist-info → janito-0.12.0.dist-info}/entry_points.txt +0 -0
  49. {janito-0.10.1.dist-info → janito-0.12.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,338 +0,0 @@
1
- """
2
- Command handlers for the str_replace_editor package.
3
- """
4
- import os
5
- import pathlib
6
- from typing import Dict, Any, Tuple
7
- from janito.config import get_config
8
- from .utils import normalize_path, _file_history, backup_file, get_backup_info
9
-
10
- def handle_create(args: Dict[str, Any]) -> Tuple[str, bool]:
11
- """
12
- Create a new file with the specified content.
13
-
14
- Args:
15
- args: Dictionary containing:
16
- - path: Path to the file to create
17
- - file_text: Content to write to the file
18
-
19
- Returns:
20
- A tuple containing (message, is_error)
21
- """
22
- path = args.get("path")
23
- file_text = args.get("file_text", "")
24
-
25
- if not path:
26
- return ("Missing required parameter: path", True)
27
-
28
- path = normalize_path(path)
29
-
30
- # Convert to Path object for better path handling
31
- file_path = pathlib.Path(path)
32
-
33
- # Check if the file already exists
34
- if file_path.exists():
35
- return (f"File {path} already exists", True)
36
-
37
- # Create parent directories if they don't exist
38
- file_path.parent.mkdir(parents=True, exist_ok=True)
39
-
40
- # Write the content to the file
41
- try:
42
- with open(file_path, 'w', encoding='utf-8') as f:
43
- f.write(file_text)
44
- # Show relative path if it's not an absolute path
45
- display_path = path if os.path.isabs(path) else os.path.relpath(file_path, get_config().workspace_dir)
46
- return (f"Successfully created file {display_path}", False)
47
- except Exception as e:
48
- return (f"Error creating file {path}: {str(e)}", True)
49
-
50
-
51
- def handle_view(args: Dict[str, Any]) -> Tuple[str, bool]:
52
- """
53
- View the contents of a file or list directory contents.
54
-
55
- Args:
56
- args: Dictionary containing:
57
- - path: Path to the file or directory to view
58
- - view_range (optional): Array of two integers specifying start and end line numbers
59
-
60
- Returns:
61
- A tuple containing (content_or_message, is_error)
62
- """
63
- path = args.get("path")
64
- view_range = args.get("view_range")
65
-
66
- if not path:
67
- return ("Missing required parameter: path", True)
68
-
69
- path = normalize_path(path)
70
- file_path = pathlib.Path(path)
71
-
72
- if not file_path.exists():
73
- return (f"File or directory {path} does not exist", True)
74
-
75
- # If the path is a directory, list its contents
76
- if file_path.is_dir():
77
- try:
78
- # Get all files and directories in the directory
79
- items = list(file_path.iterdir())
80
-
81
- # Sort items (directories first, then files)
82
- dirs = [item.name + "/" for item in items if item.is_dir()]
83
- files = [item.name for item in items if item.is_file()]
84
-
85
- dirs.sort()
86
- files.sort()
87
-
88
- # Combine the lists
89
- contents = dirs + files
90
-
91
- if not contents:
92
- return (f"Directory {path} is empty", False)
93
-
94
- # Add count information to the output
95
- dir_count = len(dirs)
96
- file_count = len(files)
97
- count_info = f"Total: {len(contents)} ({dir_count} directories, {file_count} files)"
98
-
99
- return ("\n".join(contents) + f"\n{count_info}", False)
100
- except Exception as e:
101
- return (f"Error listing directory {path}: {str(e)}", True)
102
-
103
- # If the path is a file, view its contents
104
- try:
105
- with open(file_path, 'r', encoding='utf-8') as f:
106
- content = f.readlines()
107
-
108
- # If view_range is specified, return only the specified lines
109
- if view_range:
110
- start_line = max(1, view_range[0]) - 1 # Convert to 0-indexed
111
- end_line = view_range[1] if view_range[1] != -1 else len(content)
112
- end_line = min(end_line, len(content))
113
-
114
- # Adjust content to only include the specified lines
115
- content = content[start_line:end_line]
116
-
117
- # Add line numbers to each line
118
- numbered_content = []
119
- for i, line in enumerate(content):
120
- line_number = start_line + i + 1 # Convert back to 1-indexed
121
- numbered_content.append(f"{line_number}: {line}")
122
-
123
- # Add line count information
124
- line_count = end_line - start_line
125
-
126
- # Show relative path if it's not an absolute path
127
- display_path = path if os.path.isabs(path) else os.path.relpath(file_path, get_config().workspace_dir)
128
- line_info = f"Viewed {line_count} lines from {display_path}"
129
-
130
- return ("".join(numbered_content) + f"\n{line_info}", False)
131
- else:
132
- # Add line numbers to each line
133
- numbered_content = []
134
- for i, line in enumerate(content):
135
- line_number = i + 1 # 1-indexed line numbers
136
- numbered_content.append(f"{line_number}: {line}")
137
-
138
- # Add line count information
139
- # Show relative path if it's not an absolute path
140
- display_path = path if os.path.isabs(path) else os.path.relpath(file_path, get_config().workspace_dir)
141
- line_info = f"Viewed {len(content)} lines from {display_path}"
142
-
143
- return ("".join(numbered_content) + f"\n{line_info}", False)
144
- except Exception as e:
145
- return (f"Error viewing file {path}: {str(e)}", True)
146
-
147
-
148
- def handle_str_replace(args: Dict[str, Any]) -> Tuple[str, bool]:
149
- """
150
- Replace a specific string in a file with a new string.
151
-
152
- Args:
153
- args: Dictionary containing:
154
- - path: Path to the file to modify
155
- - old_str: The text to replace
156
- - new_str: The new text to insert
157
-
158
- Returns:
159
- A tuple containing (message, is_error)
160
- """
161
- path = args.get("path")
162
- old_str = args.get("old_str")
163
- new_str = args.get("new_str")
164
-
165
- if not path:
166
- return ("Missing required parameter: path", True)
167
- if old_str is None:
168
- return ("Missing required parameter: old_str", True)
169
- if new_str is None:
170
- return ("Missing required parameter: new_str", True)
171
-
172
- path = normalize_path(path)
173
- file_path = pathlib.Path(path)
174
-
175
- if not file_path.exists():
176
- return (f"File {path} does not exist", True)
177
-
178
- try:
179
- # Read the file content
180
- with open(file_path, 'r', encoding='utf-8') as f:
181
- content = f.read()
182
-
183
- # Backup the file before making changes
184
- backup_file(path, content)
185
-
186
- # Save the current content for undo (legacy approach, will be deprecated)
187
- if path not in _file_history:
188
- _file_history[path] = []
189
- _file_history[path].append(content)
190
-
191
- # Check if old_str exists in the content
192
- if old_str not in content:
193
- return ("Error: No match found for replacement. Please check your text and try again.", True)
194
-
195
- # Count occurrences to check for multiple matches
196
- match_count = content.count(old_str)
197
- if match_count > 1:
198
- return (f"Error: Found {match_count} matches for replacement text. Please provide more context to make a unique match.", True)
199
-
200
- # Replace the string
201
- new_content = content.replace(old_str, new_str)
202
-
203
- # Write the new content
204
- with open(file_path, 'w', encoding='utf-8') as f:
205
- f.write(new_content)
206
-
207
- return (f"Successfully replaced string in file {path}", False)
208
- except Exception as e:
209
- return (f"Error replacing string in file {path}: {str(e)}", True)
210
-
211
-
212
- def handle_insert(args: Dict[str, Any]) -> Tuple[str, bool]:
213
- """
214
- Insert text at a specific location in a file.
215
-
216
- Args:
217
- args: Dictionary containing:
218
- - path: Path to the file to modify
219
- - insert_line: The line number after which to insert the text
220
- - new_str: The text to insert
221
-
222
- Returns:
223
- A tuple containing (message, is_error)
224
- """
225
- path = args.get("path")
226
- insert_line = args.get("insert_line")
227
- new_str = args.get("new_str")
228
-
229
- if not path:
230
- return ("Missing required parameter: path", True)
231
- if insert_line is None:
232
- return ("Missing required parameter: insert_line", True)
233
- if new_str is None:
234
- return ("Missing required parameter: new_str", True)
235
-
236
- # Get the workspace directory from config
237
- workspace_dir = get_config().workspace_dir
238
-
239
- # Make path absolute if it's not already
240
- if not os.path.isabs(path):
241
- path = os.path.join(workspace_dir, path)
242
-
243
- file_path = pathlib.Path(path)
244
-
245
- if not file_path.exists():
246
- return (f"File {path} does not exist", True)
247
-
248
- try:
249
- # Read the file content
250
- with open(file_path, 'r', encoding='utf-8') as f:
251
- lines = f.readlines()
252
- content = "".join(lines)
253
-
254
- # Backup the file before making changes
255
- backup_file(path, content)
256
-
257
- # Save the current content for undo (legacy approach, will be deprecated)
258
- if path not in _file_history:
259
- _file_history[path] = []
260
- _file_history[path].append(content)
261
-
262
- # Check if insert_line is valid
263
- if insert_line < 0 or insert_line > len(lines):
264
- return (f"Invalid insert line {insert_line} for file {path}", True)
265
-
266
- # Ensure new_str ends with a newline if it doesn't already
267
- if new_str and not new_str.endswith('\n'):
268
- new_str += '\n'
269
-
270
- # Insert the new string
271
- lines.insert(insert_line, new_str)
272
-
273
- # Write the new content
274
- with open(file_path, 'w', encoding='utf-8') as f:
275
- f.writelines(lines)
276
-
277
- return (f"Successfully inserted text at line {insert_line} in file {path}", False)
278
- except Exception as e:
279
- return (f"Error inserting text in file {path}: {str(e)}", True)
280
-
281
-
282
- def handle_undo_edit(args: Dict[str, Any]) -> Tuple[str, bool]:
283
- """
284
- Undo the last edit made to a file.
285
-
286
- Args:
287
- args: Dictionary containing:
288
- - path: Path to the file whose last edit should be undone
289
-
290
- Returns:
291
- A tuple containing (message, is_error)
292
- """
293
- path = args.get("path")
294
-
295
- if not path:
296
- return ("Missing required parameter: path", True)
297
-
298
- # Get the workspace directory from config
299
- workspace_dir = get_config().workspace_dir
300
-
301
- # Make path absolute if it's not already
302
- if not os.path.isabs(path):
303
- path = os.path.join(workspace_dir, path)
304
-
305
- # First try to use the file-based backup system
306
- backup_info = get_backup_info()
307
- if backup_info:
308
- backup_path = backup_info['path']
309
- backup_content = backup_info['content']
310
-
311
- # If a path was provided, check if it matches the backup
312
- if path != backup_path:
313
- return (f"No backup found for file {path}. Last edited file was {backup_path}", True)
314
-
315
- try:
316
- # Write the backup content back to the file
317
- with open(path, 'w', encoding='utf-8') as f:
318
- f.write(backup_content)
319
-
320
- return (f"Successfully undid last edit to file {path}", False)
321
- except Exception as e:
322
- return (f"Error undoing edit to file {path}: {str(e)}", True)
323
-
324
- # Fall back to the in-memory history if no file backup exists
325
- if path not in _file_history or not _file_history[path]:
326
- return (f"No edit history for file {path}", True)
327
-
328
- try:
329
- # Get the last content
330
- last_content = _file_history[path].pop()
331
-
332
- # Write the last content back to the file
333
- with open(path, 'w', encoding='utf-8') as f:
334
- f.write(last_content)
335
-
336
- return (f"Successfully undid last edit to file {path}", False)
337
- except Exception as e:
338
- return (f"Error undoing edit to file {path}: {str(e)}", True)
@@ -1,86 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: janito
3
- Version: 0.10.1
4
- Summary: Janito CLI tool
5
- Project-URL: Homepage, https://github.com/joaompinto/janito
6
- Author-email: João Pinto <lamego.pinto@gmail.com>
7
- License-File: LICENSE
8
- Requires-Python: >=3.8
9
- Requires-Dist: claudine>=0.1.0
10
- Requires-Dist: rich>=13.0.0
11
- Requires-Dist: typer>=0.9.0
12
- Description-Content-Type: text/markdown
13
-
14
- # 🤖 Janito
15
-
16
- Janito is a powerful AI-assisted command-line interface (CLI) tool built with Python, leveraging Anthropic's Claude for intelligent code and file management.
17
-
18
- ## ✨ Features
19
-
20
- - 🧠 Intelligent AI assistant powered by Claude
21
- - 📁 File management capabilities
22
- - 🔍 Smart code search and editing
23
- - 💻 Interactive terminal interface with rich formatting
24
- - 📊 Token usage tracking and cost reporting
25
-
26
- ## 🛠️ Installation
27
-
28
- ```bash
29
- # Install directly from PyPI
30
- pip install janito
31
- ```
32
-
33
- For development or installation from source, please see [README_DEV.md](README_DEV.md).
34
-
35
- ## 🚀 Usage
36
-
37
- After installation, you can use the `janito` command in your terminal:
38
-
39
- ```bash
40
- # Get help
41
- janito --help
42
-
43
-
44
- # Ask the AI assistant a question
45
- janito "Suggest improvements to this project"
46
-
47
- janito "Add a --version to the cli to report he version"
48
-
49
- ```
50
-
51
- ## 🔧 Available Tools
52
-
53
- Janito comes with several built-in tools:
54
- - 📄 `str_replace_editor` - View, create, and edit files
55
- - 🔎 `find_files` - Find files matching patterns
56
- - 🗑️ `delete_file` - Delete files
57
- - 🔍 `search_text` - Search for text patterns in files
58
-
59
- ## ⚙️ Requirements
60
-
61
- - Python 3.8 or higher
62
- - Dependencies:
63
- - typer (>=0.9.0)
64
- - rich (>=13.0.0)
65
- - claudine (for Claude AI integration)
66
-
67
- ## 🔑 API Key
68
-
69
- Janito requires an Anthropic API key to function. You can:
70
- 1. Set it as an environment variable: `export ANTHROPIC_API_KEY=your_api_key`
71
- 2. Or enter it when prompted
72
-
73
- ## 💻 Development
74
-
75
- ```bash
76
- # Create a virtual environment
77
- python -m venv .venv
78
- source .venv/bin/activate # On Windows: .venv\Scripts\activate
79
-
80
- # Install development dependencies
81
- pip install -e ".[dev]"
82
- ```
83
-
84
- ## 📜 License
85
-
86
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
@@ -1,23 +0,0 @@
1
- janito/__init__.py,sha256=Ral9Ev43UBuUjoDGLTUMJ62fAiWUC6zMMkOKTkzAQmo,53
2
- janito/__main__.py,sha256=gskP0c2f1Zu9VwZ9V5QjdGf20wginiuGWa33qeZVDyY,6867
3
- janito/callbacks.py,sha256=_kmoRR2lDPQzNLfWPPibilbna4W-abj6hMO1VFICmwY,5288
4
- janito/cli.py,sha256=Vbg8W79d-LEB2b4cc58a2HE-8jmZrTvaNoEg2J2543k,9381
5
- janito/config.py,sha256=SYaMg3sqWroTaByfxGASleMLxux3s6b-fYZnT-ggqFw,2097
6
- janito/test_file.py,sha256=c6GWGdTYG3z-Y5XBao9Tmhmq3G-v0L37OfwLgBo8zIU,126
7
- janito/token_report.py,sha256=qLCAPce90Pgh_Q5qssA7irRB1C9e9pOfBC01Wi-ZUug,4006
8
- janito/data/instructions.txt,sha256=aJDhPv_iJa0s6KrwG9JSsRFpjSzJiWJSPGWhAJpsagA,257
9
- janito/tools/__init__.py,sha256=izLbyETR5piuFjQZ6ZY6zRgS7Tlx0yXk_wzhPn_CVYc,279
10
- janito/tools/decorators.py,sha256=VzUHsoxtxmsd5ic1KAW42eCOT56gjjSzWbEZTcv0RZs,2617
11
- janito/tools/delete_file.py,sha256=5JgSFtiF8bpfo0Z15ifj_RFHEHkl9cto1BES9TxIBIA,1245
12
- janito/tools/find_files.py,sha256=bN97u3VbFBA78ssXCaEo_cFloni5PE1UW6cSDP9kvjw,5993
13
- janito/tools/search_text.py,sha256=d3iYMGHS7ZeMJxek4A_7DwMrnsWkp_XOtp3bkyNor0M,9292
14
- janito/tools/str_replace_editor/__init__.py,sha256=kYmscmQgft3Jzt3oCNz7k2FiRbJvku6OFDDC3Q_zoAA,144
15
- janito/tools/str_replace_editor/editor.py,sha256=XGrBADTlKwlcXat38T5th_KOPrspb9CBCP0g9KRuqmg,1345
16
- janito/tools/str_replace_editor/handlers.py,sha256=-7HJinfiJP2s-XHHVAS6TtrNwoNtTH8IJHxuLlYH2pA,12423
17
- janito/tools/str_replace_editor/utils.py,sha256=WOkos4bZ5Pe9U_UES6bS_QNISob0GvGN8TQVaRi6RbM,2670
18
- janito/data/instructions.txt,sha256=aJDhPv_iJa0s6KrwG9JSsRFpjSzJiWJSPGWhAJpsagA,257
19
- janito-0.10.1.dist-info/METADATA,sha256=UDGs3sAXllO6svDIUnTGwWoaDhziWBxSmlRV6cVmo6w,2146
20
- janito-0.10.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
21
- janito-0.10.1.dist-info/entry_points.txt,sha256=JMbF_1jg-xQddidpAYkzjOKdw70fy_ymJfcmerY2wIY,47
22
- janito-0.10.1.dist-info/licenses/LICENSE,sha256=6-H8LXExbBIAuT4cyiE-Qy8Bad1K4pagQRVTWr6wkhk,1096
23
- janito-0.10.1.dist-info/RECORD,,