janito 0.6.0__py3-none-any.whl → 0.7.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 (54) hide show
  1. janito/__main__.py +37 -30
  2. janito/agents/__init__.py +8 -2
  3. janito/agents/agent.py +10 -3
  4. janito/agents/claudeai.py +13 -23
  5. janito/agents/openai.py +5 -1
  6. janito/change/analysis/analyze.py +8 -7
  7. janito/change/analysis/prompts.py +4 -12
  8. janito/change/analysis/view/terminal.py +21 -11
  9. janito/change/applier/text.py +7 -5
  10. janito/change/core.py +22 -29
  11. janito/change/parser.py +0 -2
  12. janito/change/prompts.py +16 -21
  13. janito/change/validator.py +27 -9
  14. janito/change/viewer/content.py +1 -1
  15. janito/change/viewer/panels.py +93 -115
  16. janito/change/viewer/styling.py +15 -4
  17. janito/cli/commands.py +63 -20
  18. janito/common.py +44 -18
  19. janito/config.py +44 -44
  20. janito/prompt.py +36 -0
  21. janito/qa.py +5 -14
  22. janito/search_replace/README.md +63 -17
  23. janito/search_replace/__init__.py +2 -1
  24. janito/search_replace/core.py +15 -14
  25. janito/search_replace/logger.py +35 -0
  26. janito/search_replace/searcher.py +160 -48
  27. janito/search_replace/strategy_result.py +10 -0
  28. janito/shell/__init__.py +15 -16
  29. janito/shell/commands.py +38 -97
  30. janito/shell/processor.py +7 -27
  31. janito/shell/prompt.py +48 -0
  32. janito/shell/registry.py +60 -0
  33. janito/workspace/__init__.py +4 -5
  34. janito/workspace/analysis.py +2 -2
  35. janito/workspace/show.py +141 -0
  36. janito/workspace/stats.py +43 -0
  37. janito/workspace/types.py +98 -0
  38. janito/workspace/workset.py +108 -0
  39. janito/workspace/workspace.py +114 -0
  40. janito-0.7.0.dist-info/METADATA +167 -0
  41. {janito-0.6.0.dist-info → janito-0.7.0.dist-info}/RECORD +44 -43
  42. janito/change/viewer/pager.py +0 -56
  43. janito/cli/handlers/ask.py +0 -22
  44. janito/cli/handlers/demo.py +0 -22
  45. janito/cli/handlers/request.py +0 -24
  46. janito/cli/handlers/scan.py +0 -9
  47. janito/prompts.py +0 -2
  48. janito/shell/handlers.py +0 -122
  49. janito/workspace/manager.py +0 -48
  50. janito/workspace/scan.py +0 -232
  51. janito-0.6.0.dist-info/METADATA +0 -185
  52. {janito-0.6.0.dist-info → janito-0.7.0.dist-info}/WHEEL +0 -0
  53. {janito-0.6.0.dist-info → janito-0.7.0.dist-info}/entry_points.txt +0 -0
  54. {janito-0.6.0.dist-info → janito-0.7.0.dist-info}/licenses/LICENSE +0 -0
janito/workspace/scan.py DELETED
@@ -1,232 +0,0 @@
1
- from collections import defaultdict
2
- from pathlib import Path
3
- from typing import Dict, List, Set, Tuple
4
-
5
- from pathspec import PathSpec
6
- from pathspec.patterns import GitWildMatchPattern
7
- from rich.columns import Columns
8
- from rich.console import Console
9
- from rich.panel import Panel
10
- from rich.text import Text
11
- from rich.rule import Rule
12
- from rich.console import Group
13
- from janito.config import config
14
-
15
-
16
- SPECIAL_FILES = ["README.md", "__init__.py", "__main__.py"]
17
-
18
-
19
- def _get_gitignore_spec() -> PathSpec:
20
- """Load gitignore patterns if available"""
21
- gitignore_path = config.workspace_dir / '.gitignore' if config.workspace_dir else None
22
- if gitignore_path and gitignore_path.exists():
23
- with gitignore_path.open() as f:
24
- lines = f.readlines()
25
- return PathSpec.from_lines(GitWildMatchPattern, lines)
26
-
27
-
28
- def _process_file(path: Path, relative_base: Path) -> Tuple[str, str, bool]:
29
- """Process a single file and return its XML content, display item and success status"""
30
- relative_path = path.relative_to(relative_base)
31
- try:
32
- # Skip binary files
33
- if path.read_bytes().find(b'\x00') != -1:
34
- return "", "", False
35
-
36
- file_content = path.read_text(encoding='utf-8')
37
- xml_content = f"<file>\n<path>{relative_path}</path>\n<content>\n{file_content}\n</content>\n</file>"
38
- display_item = f"[cyan]•[/cyan] {relative_path}"
39
- return xml_content, display_item, True
40
- except UnicodeDecodeError:
41
- return "", str(relative_path), False
42
-
43
- def _scan_paths(paths: List[Path] = None) -> Tuple[List[str], List[str], List[str], List[str]]:
44
- """Common scanning logic for both preview and content collection"""
45
- content_parts = []
46
- file_items = []
47
- skipped_files = []
48
- ignored_items = []
49
- processed_files: Set[Path] = set()
50
- console = Console()
51
- gitignore_spec = _get_gitignore_spec()
52
-
53
- def scan_path(path: Path, depth: int, is_recursive: bool) -> None:
54
- if depth > 1 and not is_recursive:
55
- return
56
-
57
- path = path.resolve()
58
- if '.janito' in path.parts or '.git' in path.parts or '.pytest_cache' in path.parts:
59
- return
60
-
61
- relative_base = config.workspace_dir
62
- if path.is_dir():
63
- relative_path = path.relative_to(relative_base)
64
- content_parts.append(f'<directory><path>{relative_path}</path>not sent</directory>')
65
- file_items.append(f"[blue]•[/blue] {relative_path}/")
66
-
67
- # Process special files
68
- special_found = []
69
- for special_file in SPECIAL_FILES:
70
- special_path = path / special_file
71
- if special_path.exists() and special_path.resolve() not in processed_files:
72
- special_found.append(special_file)
73
- processed_files.add(special_path.resolve())
74
- xml_content, _, success = _process_file(special_path, relative_base)
75
- if success:
76
- content_parts.append(xml_content)
77
- else:
78
- skipped_files.append(str(special_path.relative_to(relative_base)))
79
-
80
- if special_found:
81
- file_items[-1] = f"[blue]•[/blue] {relative_path}/ [cyan]({', '.join(special_found)})[/cyan]"
82
-
83
- for item in path.iterdir():
84
- # Skip ignored files/directories
85
- if gitignore_spec and gitignore_spec.match_file(str(item.relative_to(config.workspace_dir))):
86
- rel_path = item.relative_to(config.workspace_dir)
87
- ignored_items.append(f"[dim red]•[/dim red] {rel_path}")
88
- continue
89
- scan_path(item, depth+1, is_recursive)
90
- else:
91
- if path.resolve() in processed_files:
92
- return
93
-
94
- processed_files.add(path.resolve())
95
- xml_content, display_item, success = _process_file(path, relative_base)
96
- if success:
97
- content_parts.append(xml_content)
98
- file_items.append(display_item)
99
- else:
100
- skipped_files.append(display_item)
101
- if display_item:
102
- console.print(f"[yellow]Warning: Skipping file due to encoding issues: {display_item}[/yellow]")
103
-
104
- for path in paths:
105
- is_recursive = Path(path) in config.recursive
106
- scan_path(path, 0, is_recursive)
107
-
108
- if skipped_files and config.verbose:
109
- console.print("\n[yellow]Files skipped due to encoding issues:[/yellow]")
110
- for file in skipped_files:
111
- console.print(f" • {file}")
112
-
113
- return content_parts, file_items, skipped_files, ignored_items
114
-
115
- def collect_files_content(paths: List[Path] = None) -> str:
116
- """Collect content from all files in XML format"""
117
- console = Console()
118
-
119
- # If no paths specified and skipwork not set, use workspace_dir
120
- if not paths and not config.skipwork:
121
- paths = [config.workspace_dir]
122
- # If paths specified and skipwork not set, include workspace_dir
123
- elif paths and not config.skipwork:
124
- paths = [config.workspace_dir] + paths
125
- # If skipwork set, use only specified paths
126
- elif not paths and config.skipwork:
127
- console.print("[yellow]Warning: No paths to scan - skipwork enabled but no include paths specified[/yellow]")
128
- return ""
129
-
130
- content_parts, file_items, skipped_files, ignored_items = _scan_paths(paths)
131
-
132
- if file_items and config.verbose:
133
- console.print("\n[bold blue]Contents being analyzed:[/bold blue]")
134
- console.print(Columns(file_items, padding=(0, 4), expand=True))
135
- console.print("\n[bold green]Scan completed successfully[/bold green]")
136
-
137
- return "\n".join(content_parts)
138
-
139
- def preview_scan(paths: List[Path] = None) -> None:
140
- """Preview what files and directories would be scanned with structured output."""
141
- console = Console()
142
- _, file_items, skipped_files, ignored_items = _scan_paths(paths)
143
-
144
- # Create sections list for structured output
145
- sections = []
146
-
147
- # Section 1: Paths Information
148
- paths_section = []
149
- is_workspace_dir_scanned = any(p.resolve() == config.workspace_dir.resolve() for p in paths)
150
-
151
- # Show workspace_dir unless skipwork is set
152
- if not config.skipwork:
153
- paths_section.append(Panel(
154
- f"📂 {config.workspace_dir.absolute()}",
155
- title="[bold cyan]Working Directory[/bold cyan]",
156
- border_style="cyan",
157
- padding=(1, 2)
158
- ))
159
-
160
- # Show included paths
161
- if paths:
162
- included_paths = []
163
- for path in paths:
164
- try:
165
- rel_path = path.relative_to(config.workspace_dir)
166
- is_recursive = path in config.recursive
167
- included_paths.append(f"📁 ./{rel_path}" + ("/*" if is_recursive else "/"))
168
- except ValueError:
169
- included_paths.append(f"📁 {path.absolute()}")
170
-
171
- paths_section.append(Panel(
172
- Group(*[Text(p) for p in included_paths]),
173
- title="[bold green]Included Paths[/bold green]",
174
- border_style="green",
175
- padding=(1, 2)
176
- ))
177
-
178
- sections.extend(paths_section)
179
- sections.append(Rule(style="blue"))
180
-
181
- # Section 2: Files to be scanned
182
- if file_items:
183
- sections.append(Panel(
184
- Columns(file_items, padding=(0, 2), expand=True),
185
- title="[bold blue]Files to be Scanned[/bold blue]",
186
- border_style="blue",
187
- padding=(1, 2)
188
- ))
189
-
190
- # Section 3: Ignored items
191
- if ignored_items:
192
- sections.append(Panel(
193
- Columns(ignored_items, padding=(0, 2), expand=True),
194
- title="[bold red]Ignored Items[/bold red]",
195
- border_style="red",
196
- padding=(1, 2)
197
- ))
198
-
199
- # Section 4: Skipped files (only in verbose mode)
200
- if skipped_files and config.verbose:
201
- sections.append(Panel(
202
- Columns([f"[yellow]•[/yellow] {f}" for f in skipped_files], padding=(0, 2), expand=True),
203
- title="[bold yellow]Skipped Files[/bold yellow]",
204
- border_style="yellow",
205
- padding=(1, 2)
206
- ))
207
-
208
- # Display all sections with separators
209
- console.print("\n")
210
- for section in sections:
211
- console.print(section)
212
- console.print("\n")
213
-
214
-
215
- def is_dir_empty(path: Path) -> bool:
216
- """
217
- Check if directory is empty (ignoring hidden files/directories).
218
-
219
- Args:
220
- path: Directory path to check
221
-
222
- Returns:
223
- True if directory has no visible files/directories, False otherwise
224
- """
225
- if not path.is_dir():
226
- return False
227
-
228
- # List all non-hidden files and directories
229
- visible_items = [item for item in path.iterdir()
230
- if not item.name.startswith('.')]
231
-
232
- return len(visible_items) == 0
@@ -1,185 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: janito
3
- Version: 0.6.0
4
- Summary: A CLI tool for software development tasks powered by AI
5
- Project-URL: Homepage, https://github.com/joaompinto/janito
6
- Project-URL: Repository, https://github.com/joaompinto/janito.git
7
- Author-email: João Pinto <lamego.pinto@gmail.com>
8
- License: MIT
9
- License-File: LICENSE
10
- Classifier: Development Status :: 4 - Beta
11
- Classifier: Environment :: Console
12
- Classifier: Intended Audience :: Developers
13
- Classifier: License :: OSI Approved :: MIT License
14
- Classifier: Programming Language :: Python :: 3.8
15
- Classifier: Programming Language :: Python :: 3.9
16
- Classifier: Programming Language :: Python :: 3.10
17
- Classifier: Topic :: Software Development
18
- Requires-Python: >=3.8
19
- Requires-Dist: anthropic
20
- Requires-Dist: pathspec
21
- Requires-Dist: rich
22
- Requires-Dist: tomli
23
- Requires-Dist: typer
24
- Description-Content-Type: text/markdown
25
-
26
- # Janito
27
-
28
- [![PyPI version](https://badge.fury.io/py/janito.svg)](https://badge.fury.io/py/janito)
29
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
30
-
31
- AI-powered CLI tool for code modifications and analysis. Janito helps you modify, analyze, and understand your codebase using natural language commands.
32
-
33
- ## Table of Contents
34
-
35
- - [Features](#features)
36
- - [Installation](#installation)
37
- - [Prerequisites](#prerequisites)
38
- - [Steps](#steps)
39
- - [Usage](#usage)
40
- - [Basic Commands](#basic-commands)
41
- - [Common Use Cases](#common-use-cases)
42
- - [Configuration](#configuration)
43
- - [Environment Variables](#environment-variables)
44
- - [Command Line Options](#command-line-options)
45
- - [Troubleshooting](#troubleshooting)
46
- - [Common Issues](#common-issues)
47
- - [Error Messages](#error-messages)
48
- - [Contributing](#contributing)
49
- - [License](#license)
50
-
51
- ## ✨ Features
52
-
53
- - Natural language code modifications
54
- - Codebase analysis and question answering
55
- - Smart search and replace with indentation awareness
56
- - Git-aware operations
57
- - Interactive shell mode
58
- - Change preview and validation
59
- - Automatic backup and restore
60
-
61
- ## 🚀 Installation
62
-
63
- ### Prerequisites
64
-
65
- - Python 3.8 or higher
66
- - pip package manager
67
- - An Anthropic API key (default) or OpenAI API key
68
-
69
- ### Steps
70
-
71
- 1. Install using pip:
72
- ```bash
73
- pip install janito
74
- ```
75
-
76
- 2. Configure your API key:
77
-
78
- For Anthropic Claude (default):
79
- ```bash
80
- export ANTHROPIC_API_KEY=your_api_key_here
81
- ```
82
-
83
- For OpenAI:
84
- ```bash
85
- export OPENAI_API_KEY=your_api_key_here
86
- export AI_BACKEND=openai
87
- ```
88
-
89
- ## 💡 Usage
90
-
91
- ### Basic Commands
92
-
93
- ```bash
94
- # Modify code
95
- janito "add docstrings to this file"
96
-
97
- # Ask questions about the codebase
98
- janito --ask "explain the main function in this file"
99
-
100
- # Preview files that would be analyzed
101
- janito --scan
102
-
103
- # Start interactive shell
104
- janito
105
- ```
106
-
107
- ### Common Use Cases
108
-
109
- 1. Code Documentation:
110
- ```bash
111
- janito "add type hints to all functions"
112
- janito "improve docstrings with more details"
113
- ```
114
-
115
- 2. Code Analysis:
116
- ```bash
117
- janito --ask "what are the main classes in this project?"
118
- janito --ask "explain the error handling flow"
119
- ```
120
-
121
- 3. Code Refactoring:
122
- ```bash
123
- janito "convert this class to use dataclasses"
124
- janito "split this large function into smaller ones"
125
- ```
126
-
127
- ## ⚙️ Configuration
128
-
129
- ### Environment Variables
130
-
131
- - `ANTHROPIC_API_KEY`: Anthropic API key
132
- - `OPENAI_API_KEY`: OpenAI API key (if using OpenAI backend)
133
- - `AI_BACKEND`: AI provider ('claudeai' or 'openai')
134
- - `JANITO_TEST_CMD`: Default test command to run after changes
135
-
136
- ### Command Line Options
137
-
138
- - `-w, --workspace_dir`: Set working directory
139
- - `-i, --include`: Additional paths to include
140
- - `--debug`: Show debug information
141
- - `--verbose`: Show verbose output
142
- - `--auto-apply`: Apply changes without confirmation
143
- - `--history`: Display history of requests
144
-
145
- ## 🔧 Troubleshooting
146
-
147
- ### Common Issues
148
-
149
- 1. API Key Issues:
150
- ```bash
151
- # Verify API key is set
152
- echo $ANTHROPIC_API_KEY
153
-
154
- # Temporarily set API key for single command
155
- ANTHROPIC_API_KEY=your_key janito "your request"
156
- ```
157
-
158
- 2. Path Issues:
159
- ```bash
160
- # Use absolute paths if having issues with relative paths
161
- janito -w /full/path/to/project "your request"
162
-
163
- # Specify additional paths explicitly
164
- janito -i ./src -i ./tests "your request"
165
- ```
166
-
167
- 3. Debug Mode:
168
- ```bash
169
- # Enable debug output for troubleshooting
170
- janito --debug "your request"
171
- ```
172
-
173
- ### Error Messages
174
-
175
- - "No command given": Provide a change request or command
176
- - "No input provided": Check if using --input mode correctly
177
- - "Duplicate path provided": Remove duplicate paths from includes
178
-
179
- ## 👥 Contributing
180
-
181
- Contributions are welcome! Please feel free to submit a Pull Request.
182
-
183
- ## 📄 License
184
-
185
- MIT License - see [LICENSE](LICENSE)
File without changes