janito 0.3.0__py3-none-any.whl → 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- janito/__init__.py +48 -1
- janito/__main__.py +29 -235
- janito/_contextparser.py +113 -0
- janito/agents/__init__.py +22 -0
- janito/agents/agent.py +21 -0
- janito/agents/claudeai.py +64 -0
- janito/agents/openai.py +53 -0
- janito/agents/test.py +34 -0
- janito/analysis/__init__.py +33 -0
- janito/analysis/display.py +149 -0
- janito/analysis/options.py +112 -0
- janito/analysis/prompts.py +75 -0
- janito/change/__init__.py +19 -0
- janito/change/applier.py +269 -0
- janito/change/content.py +62 -0
- janito/change/indentation.py +33 -0
- janito/change/position.py +169 -0
- janito/changehistory.py +46 -0
- janito/changeviewer/__init__.py +12 -0
- janito/changeviewer/diff.py +28 -0
- janito/changeviewer/panels.py +268 -0
- janito/changeviewer/styling.py +59 -0
- janito/changeviewer/themes.py +57 -0
- janito/cli/__init__.py +2 -0
- janito/cli/commands.py +53 -0
- janito/cli/functions.py +286 -0
- janito/cli/registry.py +26 -0
- janito/common.py +23 -0
- janito/config.py +8 -3
- janito/console/__init__.py +3 -0
- janito/console/commands.py +112 -0
- janito/console/core.py +62 -0
- janito/console/display.py +157 -0
- janito/fileparser.py +334 -0
- janito/prompts.py +58 -74
- janito/qa.py +40 -7
- janito/review.py +13 -0
- janito/scan.py +68 -14
- janito/tests/test_fileparser.py +26 -0
- janito/version.py +23 -0
- janito-0.5.0.dist-info/METADATA +146 -0
- janito-0.5.0.dist-info/RECORD +45 -0
- janito/changeviewer.py +0 -64
- janito/claude.py +0 -74
- janito/console.py +0 -60
- janito/contentchange.py +0 -165
- janito-0.3.0.dist-info/METADATA +0 -138
- janito-0.3.0.dist-info/RECORD +0 -15
- {janito-0.3.0.dist-info → janito-0.5.0.dist-info}/WHEEL +0 -0
- {janito-0.3.0.dist-info → janito-0.5.0.dist-info}/entry_points.txt +0 -0
- {janito-0.3.0.dist-info → janito-0.5.0.dist-info}/licenses/LICENSE +0 -0
janito/qa.py
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
from rich.console import Console
|
2
2
|
from rich.markdown import Markdown
|
3
|
-
from
|
3
|
+
from rich.panel import Panel
|
4
|
+
from rich.syntax import Syntax
|
5
|
+
from rich.table import Table
|
6
|
+
from rich.rule import Rule
|
7
|
+
from janito.agents import AIAgent
|
8
|
+
from janito.common import progress_send_message
|
9
|
+
from janito.scan import show_content_stats
|
4
10
|
|
5
11
|
QA_PROMPT = """Please provide a clear and concise answer to the following question about the codebase:
|
6
12
|
|
@@ -12,21 +18,48 @@ Current files:
|
|
12
18
|
</files>
|
13
19
|
|
14
20
|
Focus on providing factual information and explanations. Do not suggest code changes.
|
21
|
+
Format your response using markdown with appropriate headers and code blocks.
|
15
22
|
"""
|
16
23
|
|
17
|
-
def ask_question(question: str, files_content: str
|
24
|
+
def ask_question(question: str, files_content: str) -> str:
|
18
25
|
"""Process a question about the codebase and return the answer"""
|
26
|
+
# Show content stats before processing
|
27
|
+
show_content_stats(files_content)
|
28
|
+
|
19
29
|
prompt = QA_PROMPT.format(
|
20
30
|
question=question,
|
21
31
|
files_content=files_content
|
22
32
|
)
|
23
|
-
return
|
33
|
+
return progress_send_message(prompt)
|
34
|
+
|
24
35
|
|
25
36
|
def display_answer(answer: str, raw: bool = False) -> None:
|
26
|
-
"""Display the answer
|
37
|
+
"""Display the answer as markdown with consistent colors"""
|
27
38
|
console = Console()
|
39
|
+
|
40
|
+
# Define consistent colors
|
41
|
+
COLORS = {
|
42
|
+
'primary': '#729FCF', # Soft blue for primary elements
|
43
|
+
'secondary': '#8AE234', # Bright green for actions/success
|
44
|
+
'accent': '#AD7FA8', # Purple for accents
|
45
|
+
'muted': '#7F9F7F', # Muted green for less important text
|
46
|
+
}
|
47
|
+
|
28
48
|
if raw:
|
29
49
|
console.print(answer)
|
30
|
-
|
31
|
-
|
32
|
-
|
50
|
+
return
|
51
|
+
|
52
|
+
# Display markdown answer in a panel with consistent styling
|
53
|
+
answer_panel = Panel(
|
54
|
+
Markdown(answer),
|
55
|
+
title="[bold]Answer[/bold]",
|
56
|
+
title_align="center",
|
57
|
+
border_style=COLORS['primary'],
|
58
|
+
padding=(1, 2)
|
59
|
+
)
|
60
|
+
|
61
|
+
console.print("\n")
|
62
|
+
console.print(Rule(style=COLORS['accent']))
|
63
|
+
console.print(answer_panel)
|
64
|
+
console.print(Rule(style=COLORS['accent']))
|
65
|
+
console.print("\n")
|
janito/review.py
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
from rich.console import Console
|
2
|
+
from rich.markdown import Markdown
|
3
|
+
from janito.common import progress_send_message
|
4
|
+
from janito.agents import AIAgent
|
5
|
+
|
6
|
+
def review_text(text: str, raw: bool = False) -> None:
|
7
|
+
"""Review the provided text using Claude"""
|
8
|
+
console = Console()
|
9
|
+
response = progress_send_message(f"Please review this text and provide feedback:\n\n{text}")
|
10
|
+
if raw:
|
11
|
+
console.print(response)
|
12
|
+
else:
|
13
|
+
console.print(Markdown(response))
|
janito/scan.py
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
from pathlib import Path
|
2
|
-
from typing import List, Tuple
|
2
|
+
from typing import List, Tuple, Set
|
3
3
|
from rich.console import Console
|
4
4
|
from rich.columns import Columns
|
5
|
+
from rich.panel import Panel
|
5
6
|
from janito.config import config
|
6
7
|
from pathspec import PathSpec
|
7
8
|
from pathspec.patterns import GitWildMatchPattern
|
9
|
+
from collections import defaultdict
|
10
|
+
|
8
11
|
|
9
12
|
|
10
13
|
SPECIAL_FILES = ["README.md", "__init__.py", "__main__.py"]
|
@@ -14,12 +17,13 @@ def _scan_paths(paths: List[Path], workdir: Path = None) -> Tuple[List[str], Lis
|
|
14
17
|
content_parts = []
|
15
18
|
file_items = []
|
16
19
|
skipped_files = []
|
20
|
+
processed_files: Set[Path] = set() # Track processed files
|
17
21
|
console = Console()
|
18
22
|
|
19
23
|
# Load gitignore if it exists
|
20
24
|
gitignore_path = workdir / '.gitignore' if workdir else None
|
21
25
|
gitignore_spec = None
|
22
|
-
if gitignore_path and gitignore_path.exists():
|
26
|
+
if (gitignore_path and gitignore_path.exists()):
|
23
27
|
with open(gitignore_path) as f:
|
24
28
|
gitignore = f.read()
|
25
29
|
gitignore_spec = PathSpec.from_lines(GitWildMatchPattern, gitignore.splitlines())
|
@@ -34,7 +38,8 @@ def _scan_paths(paths: List[Path], workdir: Path = None) -> Tuple[List[str], Lis
|
|
34
38
|
"""
|
35
39
|
if level > 1:
|
36
40
|
return
|
37
|
-
|
41
|
+
|
42
|
+
path = path.resolve()
|
38
43
|
relative_base = workdir
|
39
44
|
if path.is_dir():
|
40
45
|
relative_path = path.relative_to(relative_base)
|
@@ -43,8 +48,10 @@ def _scan_paths(paths: List[Path], workdir: Path = None) -> Tuple[List[str], Lis
|
|
43
48
|
# Check for special files
|
44
49
|
special_found = []
|
45
50
|
for special_file in SPECIAL_FILES:
|
46
|
-
|
51
|
+
special_path = path / special_file
|
52
|
+
if special_path.exists() and special_path.resolve() not in processed_files:
|
47
53
|
special_found.append(special_file)
|
54
|
+
processed_files.add(special_path.resolve())
|
48
55
|
if special_found:
|
49
56
|
file_items[-1] = f"[blue]•[/blue] {relative_path}/ [cyan]({', '.join(special_found)})[/cyan]"
|
50
57
|
for special_file in special_found:
|
@@ -63,9 +70,15 @@ def _scan_paths(paths: List[Path], workdir: Path = None) -> Tuple[List[str], Lis
|
|
63
70
|
rel_path = str(item.relative_to(workdir))
|
64
71
|
if gitignore_spec.match_file(rel_path):
|
65
72
|
continue
|
66
|
-
|
73
|
+
if item.resolve() not in processed_files: # Skip if already processed
|
74
|
+
scan_path(item, level+1)
|
67
75
|
|
68
76
|
else:
|
77
|
+
resolved_path = path.resolve()
|
78
|
+
if resolved_path in processed_files: # Skip if already processed
|
79
|
+
return
|
80
|
+
|
81
|
+
processed_files.add(resolved_path)
|
69
82
|
relative_path = path.relative_to(relative_base)
|
70
83
|
# check if file is binary
|
71
84
|
try:
|
@@ -97,26 +110,67 @@ def collect_files_content(paths: List[Path], workdir: Path = None) -> str:
|
|
97
110
|
if file_items and config.verbose:
|
98
111
|
console.print("\n[bold blue]Contents being analyzed:[/bold blue]")
|
99
112
|
console.print(Columns(file_items, padding=(0, 4), expand=True))
|
113
|
+
|
114
|
+
if config.verbose:
|
115
|
+
for part in content_parts:
|
116
|
+
if part.startswith('<file>'):
|
117
|
+
# Extract filename from XML content
|
118
|
+
path_start = part.find('<path>') + 6
|
119
|
+
path_end = part.find('</path>')
|
120
|
+
if path_start > 5 and path_end > path_start:
|
121
|
+
filepath = part[path_start:path_end]
|
122
|
+
console.print(f"[dim]Adding content from:[/dim] {filepath}")
|
100
123
|
|
101
124
|
return "\n".join(content_parts)
|
102
125
|
|
126
|
+
|
103
127
|
def preview_scan(paths: List[Path], workdir: Path = None) -> None:
|
104
128
|
"""Preview what files and directories would be scanned"""
|
105
129
|
console = Console()
|
106
130
|
_, file_items = _scan_paths(paths, workdir)
|
107
131
|
|
108
|
-
#
|
109
|
-
|
110
|
-
|
132
|
+
# Display working directory status
|
133
|
+
console.print("\n[bold blue]Analysis Paths:[/bold blue]")
|
134
|
+
console.print(f"[cyan]Working Directory:[/cyan] {workdir.absolute()}")
|
135
|
+
|
136
|
+
# Show if working directory is being scanned
|
137
|
+
is_workdir_scanned = any(p.resolve() == workdir.resolve() for p in paths)
|
138
|
+
if is_workdir_scanned:
|
139
|
+
console.print("[green]✓ Working directory will be scanned[/green]")
|
111
140
|
else:
|
112
|
-
console.print(
|
113
|
-
|
141
|
+
console.print("[yellow]! Working directory will not be scanned[/yellow]")
|
142
|
+
|
143
|
+
# Show included paths relative to working directory
|
144
|
+
if len(paths) > (1 if is_workdir_scanned else 0):
|
145
|
+
console.print("\n[cyan]Additional Included Paths:[/cyan]")
|
114
146
|
for path in paths:
|
115
|
-
|
116
|
-
|
117
|
-
|
147
|
+
if path.resolve() != workdir.resolve():
|
148
|
+
try:
|
149
|
+
rel_path = path.relative_to(workdir)
|
150
|
+
console.print(f" • ./{rel_path}")
|
151
|
+
except ValueError:
|
152
|
+
# Path is outside working directory
|
153
|
+
console.print(f" • {path.absolute()}")
|
154
|
+
|
155
|
+
console.print("\n[bold blue]Files that will be analyzed:[/bold blue]")
|
118
156
|
console.print(Columns(file_items, padding=(0, 4), expand=True))
|
119
157
|
|
120
158
|
def is_dir_empty(path: Path) -> bool:
|
121
159
|
"""Check if directory is empty, ignoring hidden files"""
|
122
|
-
return not any(item for item in path.iterdir() if not item.name.startswith('.'))
|
160
|
+
return not any(item for item in path.iterdir() if not item.name.startswith('.'))
|
161
|
+
|
162
|
+
def show_content_stats(content: str) -> None:
|
163
|
+
if not content:
|
164
|
+
return
|
165
|
+
|
166
|
+
dir_counts = defaultdict(int)
|
167
|
+
for line in content.split('\n'):
|
168
|
+
if line.startswith('<path>'):
|
169
|
+
path = Path(line.replace('<path>', '').replace('</path>', '').strip())
|
170
|
+
dir_counts[str(path.parent)] += 1
|
171
|
+
|
172
|
+
console = Console()
|
173
|
+
stats = [f"{directory} ({count} files)" for directory, count in dir_counts.items()]
|
174
|
+
columns = Columns(stats, equal=True, expand=True)
|
175
|
+
panel = Panel(columns, title="Work Context")
|
176
|
+
console.print(panel)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
import pytest
|
2
|
+
from pathlib import Path
|
3
|
+
from janito.fileparser import validate_file_path, validate_file_content
|
4
|
+
|
5
|
+
def test_validate_file_path():
|
6
|
+
# Valid paths
|
7
|
+
assert validate_file_path(Path("test.py")) == (True, "")
|
8
|
+
assert validate_file_path(Path("folder/test.py")) == (True, "")
|
9
|
+
|
10
|
+
# Invalid paths
|
11
|
+
assert validate_file_path(Path("/absolute/path.py"))[0] == False
|
12
|
+
assert validate_file_path(Path("../escape.py"))[0] == False
|
13
|
+
assert validate_file_path(Path("test?.py"))[0] == False
|
14
|
+
assert validate_file_path(Path("test*.py"))[0] == False
|
15
|
+
|
16
|
+
def test_validate_file_content():
|
17
|
+
# Valid content
|
18
|
+
assert validate_file_content("print('hello')") == (True, "")
|
19
|
+
assert validate_file_content("# Empty file with comment\n") == (True, "")
|
20
|
+
|
21
|
+
# Invalid content
|
22
|
+
assert validate_file_content("")[0] == False
|
23
|
+
|
24
|
+
# Test large content
|
25
|
+
large_content = "x" * (1024 * 1024 + 1) # Slightly over 1MB
|
26
|
+
assert validate_file_content(large_content)[0] == False
|
janito/version.py
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
"""Version management module for Janito."""
|
2
|
+
from pathlib import Path
|
3
|
+
from typing import Optional
|
4
|
+
import tomli
|
5
|
+
from importlib.metadata import version as pkg_version
|
6
|
+
|
7
|
+
def get_version() -> str:
|
8
|
+
"""
|
9
|
+
Get Janito version from package metadata or pyproject.toml.
|
10
|
+
|
11
|
+
Returns:
|
12
|
+
str: The version string
|
13
|
+
"""
|
14
|
+
try:
|
15
|
+
return pkg_version("janito")
|
16
|
+
except Exception:
|
17
|
+
# Fallback to pyproject.toml
|
18
|
+
pyproject_path = Path(__file__).parent.parent / "pyproject.toml"
|
19
|
+
if pyproject_path.exists():
|
20
|
+
with open(pyproject_path, "rb") as f:
|
21
|
+
pyproject_data = tomli.load(f)
|
22
|
+
return pyproject_data.get("project", {}).get("version", "unknown")
|
23
|
+
return "unknown"
|
@@ -0,0 +1,146 @@
|
|
1
|
+
Metadata-Version: 2.3
|
2
|
+
Name: janito
|
3
|
+
Version: 0.5.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
|
+
Classifier: Development Status :: 4 - Beta
|
10
|
+
Classifier: Environment :: Console
|
11
|
+
Classifier: Intended Audience :: Developers
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
16
|
+
Classifier: Topic :: Software Development
|
17
|
+
Requires-Python: >=3.8
|
18
|
+
Requires-Dist: anthropic
|
19
|
+
Requires-Dist: pathspec
|
20
|
+
Requires-Dist: rich
|
21
|
+
Requires-Dist: tomli
|
22
|
+
Requires-Dist: typer
|
23
|
+
Description-Content-Type: text/markdown
|
24
|
+
|
25
|
+
# 🤖 Janito CLI
|
26
|
+
|
27
|
+
A CLI tool for software development tasks powered by AI. Janito is your friendly AI-powered software development buddy that helps with coding tasks like refactoring, documentation updates, and code optimization.
|
28
|
+
|
29
|
+
## 📥 Installation
|
30
|
+
|
31
|
+
1. Install using pip:
|
32
|
+
```bash
|
33
|
+
pip install janito
|
34
|
+
```
|
35
|
+
|
36
|
+
2. Verify installation:
|
37
|
+
```bash
|
38
|
+
janito --version
|
39
|
+
```
|
40
|
+
|
41
|
+
## ⚙️ Setup
|
42
|
+
|
43
|
+
1. Get your Anthropic API key from [Anthropic's website](https://www.anthropic.com/)
|
44
|
+
|
45
|
+
2. Set your API key:
|
46
|
+
```bash
|
47
|
+
# Linux/macOS
|
48
|
+
export ANTHROPIC_API_KEY='your-api-key-here'
|
49
|
+
|
50
|
+
# Windows (Command Prompt)
|
51
|
+
set ANTHROPIC_API_KEY=your-api-key-here
|
52
|
+
|
53
|
+
# Windows (PowerShell)
|
54
|
+
$env:ANTHROPIC_API_KEY='your-api-key-here'
|
55
|
+
```
|
56
|
+
|
57
|
+
3. (Optional) Configure default test command:
|
58
|
+
```bash
|
59
|
+
export JANITO_TEST_CMD='pytest' # or your preferred test command
|
60
|
+
```
|
61
|
+
|
62
|
+
## 🚀 Quick Start
|
63
|
+
|
64
|
+
### Basic Usage
|
65
|
+
|
66
|
+
```bash
|
67
|
+
# Add docstrings to your code
|
68
|
+
janito "add docstrings to this file"
|
69
|
+
|
70
|
+
# Optimize a function
|
71
|
+
janito "optimize the main function"
|
72
|
+
|
73
|
+
# Get code explanations
|
74
|
+
janito --ask "explain this code"
|
75
|
+
```
|
76
|
+
|
77
|
+
### Common Scenarios
|
78
|
+
|
79
|
+
1. **Code Refactoring**
|
80
|
+
```bash
|
81
|
+
# Refactor with test validation
|
82
|
+
janito "refactor this code to use list comprehension" --test "pytest"
|
83
|
+
|
84
|
+
# Refactor specific directory
|
85
|
+
janito "update imports" -i ./src
|
86
|
+
```
|
87
|
+
|
88
|
+
2. **Documentation Updates**
|
89
|
+
```bash
|
90
|
+
# Add or update docstrings
|
91
|
+
janito "add type hints and docstrings"
|
92
|
+
|
93
|
+
# Generate README
|
94
|
+
janito "create a README for this project"
|
95
|
+
```
|
96
|
+
|
97
|
+
3. **Code Analysis**
|
98
|
+
```bash
|
99
|
+
# Get code explanations
|
100
|
+
janito --ask "what does this function do?"
|
101
|
+
|
102
|
+
# Find potential improvements
|
103
|
+
janito --ask "suggest optimizations for this code"
|
104
|
+
```
|
105
|
+
|
106
|
+
## 🛠️ Command Reference
|
107
|
+
|
108
|
+
### Syntax
|
109
|
+
```bash
|
110
|
+
janito [OPTIONS] [REQUEST]
|
111
|
+
```
|
112
|
+
|
113
|
+
### Key Options
|
114
|
+
|
115
|
+
| Option | Description |
|
116
|
+
|--------|-------------|
|
117
|
+
| `REQUEST` | The AI request/instruction (in quotes) |
|
118
|
+
| `-w, --working-dir PATH` | Working directory [default: current] |
|
119
|
+
| `-i, --include PATH` | Include directory int the working context (can be multiple)|
|
120
|
+
| `--ask QUESTION` | Ask questions without making changes |
|
121
|
+
| `--test COMMAND` | Run tests before applying changes |
|
122
|
+
| `--debug` | Enable debug logging |
|
123
|
+
| `--verbose` | Enable verbose mode |
|
124
|
+
| `--version` | Show version information |
|
125
|
+
| `--help` | Show help message |
|
126
|
+
|
127
|
+
## 🔑 Key Features
|
128
|
+
|
129
|
+
- 🤖 AI-powered code analysis and modifications
|
130
|
+
- 💻 Interactive console mode
|
131
|
+
- ✅ Syntax validation for Python files
|
132
|
+
- 👀 Change preview and confirmation
|
133
|
+
- 🧪 Test command execution
|
134
|
+
- 📜 Change history tracking
|
135
|
+
|
136
|
+
## 📚 Additional Information
|
137
|
+
|
138
|
+
- Requires Python 3.8+
|
139
|
+
- Changes are backed up in `.janito/changes_history/`
|
140
|
+
- Environment variables:
|
141
|
+
- `ANTHROPIC_API_KEY`: Required for API access
|
142
|
+
- `JANITO_TEST_CMD`: Default test command (optional)
|
143
|
+
|
144
|
+
## 📄 License
|
145
|
+
|
146
|
+
MIT License - see [LICENSE](LICENSE) file for details.
|
@@ -0,0 +1,45 @@
|
|
1
|
+
janito/__init__.py,sha256=T1hTuIOJgEUjXNHplUuMZIgG_QxC1RH2ZcBe6WJ1XBE,1099
|
2
|
+
janito/__main__.py,sha256=ogcCBQ2YHkQhdhrXF7OV-DthLiL-Zw24XtRRYscT2Yo,2018
|
3
|
+
janito/_contextparser.py,sha256=iDX6nlqUQr-lj7lkEI4B6jHLci_Kxl-XWOaEiAQtVxA,4531
|
4
|
+
janito/changehistory.py,sha256=4Mu60og1pySlyfB36aqHnNKyH0yuSj-5rvBjmmvKGYw,1510
|
5
|
+
janito/common.py,sha256=imIwx0FCDe7p71ze9TBvn_W0Qz99Am3X5U9eHYd9vws,790
|
6
|
+
janito/config.py,sha256=ocg0lyab9ysgczKaqJTAtv0ZRa2VDMwclTJBgps7Vxw,1171
|
7
|
+
janito/fileparser.py,sha256=rJEme9QWbtnsjs9KuO3YI7B499DnfedWtzBONXqrjfU,12925
|
8
|
+
janito/prompts.py,sha256=M8IveFkG3fgLe87_9kTNdWAVkqmsD0T4eP6VmOqHAOQ,2607
|
9
|
+
janito/qa.py,sha256=MSzu9yvLCwXr9sKylTXtMfTy_2fRfPl3CU2NXf0OrS4,1977
|
10
|
+
janito/review.py,sha256=5Oc6BfxMGNmKbIeDP5_EiAKUDeQwVOD0YL7iqfgJLRE,471
|
11
|
+
janito/scan.py,sha256=QBXu64t8CjjJhiFdO-0npwdSPys9IX1oOOOyh1cGVIE,7678
|
12
|
+
janito/version.py,sha256=ylfPwGtdY8dEOFJ-DB9gKUQLggqRCvoLxhpnwjzCM94,739
|
13
|
+
janito/agents/__init__.py,sha256=VPBXIh526D11NrCSnuXUerYT7AI3rynp2klCgz94tPk,648
|
14
|
+
janito/agents/agent.py,sha256=3uGiUrvj9oCW6_oK-oMQQJ77K8jZFv7mAdXlIG1dxNY,751
|
15
|
+
janito/agents/claudeai.py,sha256=bl1MeALicf6arX_m9GmtAj0i3UoUiTbbXytVjLam2S8,2395
|
16
|
+
janito/agents/openai.py,sha256=tNtlzFJMWFv38Z62opR23u6xXlZ9L4xX_mf2f3wjrLU,2082
|
17
|
+
janito/agents/test.py,sha256=xoN1q9DUSYpUbnvTP1qZsEfxYrZfocJlt9DkIuMDvvY,1552
|
18
|
+
janito/analysis/__init__.py,sha256=QVhIoZdOc4eYhQ9ZRoZZiwowUaa-PQ0_7HV_cx4eOZU,734
|
19
|
+
janito/analysis/display.py,sha256=714423TxelieAyBe0M5A78i99qj_VfggZUWRbk2owzU,5204
|
20
|
+
janito/analysis/options.py,sha256=tgsLZOtNy35l4v4F6p_adGk3CrWhrDN0Ba_tw4Zzj5Q,3852
|
21
|
+
janito/analysis/prompts.py,sha256=7NI7XyL6gUeJWTfVP_X0ohtSnYb6gpu4Thy5kxzoeD8,2454
|
22
|
+
janito/change/__init__.py,sha256=ElyiGt6KmwufQouir-0l0ZtxNZdeLWTFRc4Vbg0RW3s,467
|
23
|
+
janito/change/applier.py,sha256=GXs0DRfb48WNoSyAH0uETjpzzFLvmUZGkb6VydKzazk,11601
|
24
|
+
janito/change/content.py,sha256=R8lbOFF2ANV_NF3S6zCV60-Q7hGFWiS-zmJBgu-r5SU,2475
|
25
|
+
janito/change/indentation.py,sha256=qvsGX9ZPm5cRftXJSKSVbK9-Po_95HCAc7sG98BxhIY,1191
|
26
|
+
janito/change/position.py,sha256=Nlkg0sGUvqYwtKA19ne1kT3ixiVYKUcy_40Cdr-gfl8,6385
|
27
|
+
janito/changeviewer/__init__.py,sha256=5kR3fsAYSgnA0Hlty0fMC6392aSK7WifH2ZIW-INjBc,298
|
28
|
+
janito/changeviewer/diff.py,sha256=2nW1Yzu7rzzIsuHWg-t2EJfsoVdbRaRJk3ewQBjQdxY,1117
|
29
|
+
janito/changeviewer/panels.py,sha256=F1yWUd6Vh535rV20NnFrVh6uvpc3w5CDBSNQUf_vOC8,9426
|
30
|
+
janito/changeviewer/styling.py,sha256=qv7DVfu57Qy93XUWIWdyqZWhgcfm-BaJVPslSuch05s,2461
|
31
|
+
janito/changeviewer/themes.py,sha256=BWa4l82fNM0pj01UYK9q_C7t_AqTwNbR8i0-F4I7KI8,1593
|
32
|
+
janito/cli/__init__.py,sha256=3gyMSaEAH2N4mXfZTLsHXKxXDxdhlYUeAPYQuhnOVBE,77
|
33
|
+
janito/cli/commands.py,sha256=6vPT60oT-1jJd_qdy-Si_pcf7yMCGmwpvQ43FZNJjUs,2001
|
34
|
+
janito/cli/functions.py,sha256=5Nn4muPv1KrInhcernByQqn_T0q2bCxPeDA1BoPw4K8,11399
|
35
|
+
janito/cli/registry.py,sha256=R1sI45YonxjMSLhAle7Dt18X_devrMsLt0ljb-rNza4,690
|
36
|
+
janito/console/__init__.py,sha256=0zxlJR88ESfuXtyzARnWNKcZ1rTyWLZwzXrfDQ7NHyw,77
|
37
|
+
janito/console/commands.py,sha256=kuI2w1LfEw0kbF_vOAYVENoMQYC42fXmDFZgxVKJKo8,3422
|
38
|
+
janito/console/core.py,sha256=DTAP_bhw_DTklUyPbGdjSzllUBMg7jXW0SklXkRNMI8,2264
|
39
|
+
janito/console/display.py,sha256=VNyHGHA_MG-dLPTCylgOKgHAl0muwhmsvg3nXFU-OZo,5380
|
40
|
+
janito/tests/test_fileparser.py,sha256=20CfwfnFtVm3_qU11qRAdcBZQXaLlkcHgbqWDOeazj4,997
|
41
|
+
janito-0.5.0.dist-info/METADATA,sha256=Jq4fJLGNs-4O8IOB2iy_8JrszlWbFzOmwrZRjQxIs_Y,3659
|
42
|
+
janito-0.5.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
43
|
+
janito-0.5.0.dist-info/entry_points.txt,sha256=wIo5zZxbmu4fC-ZMrsKD0T0vq7IqkOOLYhrqRGypkx4,48
|
44
|
+
janito-0.5.0.dist-info/licenses/LICENSE,sha256=xLIUXRPjtsgQml2zD1Pn4LpgiyZ49raw6jZDlO_gZdo,1062
|
45
|
+
janito-0.5.0.dist-info/RECORD,,
|
janito/changeviewer.py
DELETED
@@ -1,64 +0,0 @@
|
|
1
|
-
from pathlib import Path
|
2
|
-
from rich.console import Console
|
3
|
-
from rich.text import Text
|
4
|
-
from typing import TypedDict
|
5
|
-
import difflib
|
6
|
-
|
7
|
-
class FileChange(TypedDict):
|
8
|
-
"""Type definition for a file change"""
|
9
|
-
description: str
|
10
|
-
new_content: str
|
11
|
-
|
12
|
-
def show_file_changes(console: Console, filepath: Path, original: str, new_content: str, description: str) -> None:
|
13
|
-
"""Display side by side comparison of file changes"""
|
14
|
-
half_width = (console.width - 3) // 2
|
15
|
-
|
16
|
-
# Show header
|
17
|
-
console.print(f"\n[bold blue]Changes for {filepath}[/bold blue]")
|
18
|
-
console.print(f"[dim]{description}[/dim]\n")
|
19
|
-
|
20
|
-
# Show side by side content
|
21
|
-
console.print(Text("OLD".center(half_width) + "│" + "NEW".center(half_width), style="blue bold"))
|
22
|
-
console.print(Text("─" * half_width + "┼" + "─" * half_width, style="blue"))
|
23
|
-
|
24
|
-
old_lines = original.splitlines()
|
25
|
-
new_lines = new_content.splitlines()
|
26
|
-
|
27
|
-
for i in range(max(len(old_lines), len(new_lines))):
|
28
|
-
old = old_lines[i] if i < len(old_lines) else ""
|
29
|
-
new = new_lines[i] if i < len(new_lines) else ""
|
30
|
-
|
31
|
-
old_text = Text(f"{old:<{half_width}}", style="red" if old != new else None)
|
32
|
-
new_text = Text(f"{new:<{half_width}}", style="green" if old != new else None)
|
33
|
-
console.print(old_text + Text("│", style="blue") + new_text)
|
34
|
-
|
35
|
-
def show_diff_changes(console: Console, filepath: Path, original: str, new_content: str, description: str) -> None:
|
36
|
-
"""Display file changes using unified diff format"""
|
37
|
-
# Show header
|
38
|
-
console.print(f"\n[bold blue]Changes for {filepath}[/bold blue]")
|
39
|
-
console.print(f"[dim]{description}[/dim]\n")
|
40
|
-
|
41
|
-
# Generate diff
|
42
|
-
diff = difflib.unified_diff(
|
43
|
-
original.splitlines(keepends=True),
|
44
|
-
new_content.splitlines(keepends=True),
|
45
|
-
fromfile='old',
|
46
|
-
tofile='new',
|
47
|
-
lineterm=''
|
48
|
-
)
|
49
|
-
|
50
|
-
# Print diff with colors
|
51
|
-
for line in diff:
|
52
|
-
if line.startswith('+++'):
|
53
|
-
console.print(Text(line.rstrip(), style="bold green"))
|
54
|
-
elif line.startswith('---'):
|
55
|
-
console.print(Text(line.rstrip(), style="bold red"))
|
56
|
-
elif line.startswith('+'):
|
57
|
-
console.print(Text(line.rstrip(), style="green"))
|
58
|
-
elif line.startswith('-'):
|
59
|
-
console.print(Text(line.rstrip(), style="red"))
|
60
|
-
elif line.startswith('@@'):
|
61
|
-
console.print(Text(line.rstrip(), style="cyan"))
|
62
|
-
else:
|
63
|
-
console.print(Text(line.rstrip()))
|
64
|
-
|
janito/claude.py
DELETED
@@ -1,74 +0,0 @@
|
|
1
|
-
from rich.traceback import install
|
2
|
-
import anthropic
|
3
|
-
import os
|
4
|
-
from typing import Optional
|
5
|
-
from rich.progress import Progress, SpinnerColumn, TextColumn
|
6
|
-
from threading import Event
|
7
|
-
|
8
|
-
# Install rich traceback handler
|
9
|
-
install(show_locals=True)
|
10
|
-
|
11
|
-
class ClaudeAPIAgent:
|
12
|
-
"""Handles interaction with Claude API, including message handling"""
|
13
|
-
def __init__(self, api_key: Optional[str] = None, system_prompt: str = None):
|
14
|
-
if not system_prompt:
|
15
|
-
raise ValueError("system_prompt is required")
|
16
|
-
self.api_key = api_key or os.getenv('ANTHROPIC_API_KEY')
|
17
|
-
if not self.api_key:
|
18
|
-
raise ValueError("ANTHROPIC_API_KEY environment variable is required")
|
19
|
-
self.client = anthropic.Client(api_key=self.api_key)
|
20
|
-
self.model = "claude-3-5-sonnet-20241022"
|
21
|
-
self.stop_progress = Event()
|
22
|
-
self.system_message = system_prompt
|
23
|
-
self.last_prompt = None
|
24
|
-
self.last_full_message = None
|
25
|
-
self.last_response = None
|
26
|
-
self.messages_history = []
|
27
|
-
if system_prompt:
|
28
|
-
self.messages_history.append(("system", system_prompt))
|
29
|
-
|
30
|
-
def send_message(self, message: str, stop_event: Event = None) -> str:
|
31
|
-
"""Send message to Claude API and return response"""
|
32
|
-
try:
|
33
|
-
self.messages_history.append(("user", message))
|
34
|
-
# Store the full message
|
35
|
-
self.last_full_message = message
|
36
|
-
|
37
|
-
try:
|
38
|
-
# Check if already cancelled
|
39
|
-
if stop_event and stop_event.is_set():
|
40
|
-
return ""
|
41
|
-
|
42
|
-
# Start API request
|
43
|
-
response = self.client.messages.create(
|
44
|
-
model=self.model, # Use discovered model
|
45
|
-
system=self.system_message,
|
46
|
-
max_tokens=4000,
|
47
|
-
messages=[
|
48
|
-
{"role": "user", "content": message}
|
49
|
-
],
|
50
|
-
temperature=0,
|
51
|
-
)
|
52
|
-
|
53
|
-
# Handle response
|
54
|
-
response_text = response.content[0].text
|
55
|
-
|
56
|
-
# Only store and process response if not cancelled
|
57
|
-
if not (stop_event and stop_event.is_set()):
|
58
|
-
self.last_response = response_text
|
59
|
-
self.messages_history.append(("assistant", response_text))
|
60
|
-
|
61
|
-
# Always return the response, let caller handle cancellation
|
62
|
-
return response_text
|
63
|
-
|
64
|
-
except KeyboardInterrupt:
|
65
|
-
if stop_event:
|
66
|
-
stop_event.set()
|
67
|
-
return ""
|
68
|
-
|
69
|
-
except Exception as e:
|
70
|
-
error_msg = f"Error: {str(e)}"
|
71
|
-
self.messages_history.append(("error", error_msg))
|
72
|
-
if stop_event and stop_event.is_set():
|
73
|
-
return ""
|
74
|
-
return error_msg
|
janito/console.py
DELETED
@@ -1,60 +0,0 @@
|
|
1
|
-
from prompt_toolkit import PromptSession
|
2
|
-
from prompt_toolkit.history import FileHistory
|
3
|
-
from pathlib import Path
|
4
|
-
from rich.console import Console
|
5
|
-
from janito.claude import ClaudeAPIAgent
|
6
|
-
from janito.prompts import build_request_analisys_prompt, SYSTEM_PROMPT
|
7
|
-
from janito.scan import collect_files_content
|
8
|
-
from janito.__main__ import handle_option_selection
|
9
|
-
|
10
|
-
def start_console_session(workdir: Path, include: list[Path] = None) -> None:
|
11
|
-
"""Start an interactive console session using prompt_toolkit"""
|
12
|
-
console = Console()
|
13
|
-
claude = ClaudeAPIAgent(system_prompt=SYSTEM_PROMPT)
|
14
|
-
|
15
|
-
# Setup prompt session with history
|
16
|
-
history_file = workdir / '.janito' / 'console_history'
|
17
|
-
history_file.parent.mkdir(parents=True, exist_ok=True)
|
18
|
-
session = PromptSession(history=FileHistory(str(history_file)))
|
19
|
-
|
20
|
-
from importlib.metadata import version
|
21
|
-
try:
|
22
|
-
ver = version("janito")
|
23
|
-
except:
|
24
|
-
ver = "dev"
|
25
|
-
|
26
|
-
console.print("\n[bold blue]╔═══════════════════════════════════════════╗[/bold blue]")
|
27
|
-
console.print("[bold blue]║ Janito AI Assistant ║[/bold blue]")
|
28
|
-
console.print("[bold blue]║ v" + ver.ljust(8) + " ║[/bold blue]")
|
29
|
-
console.print("[bold blue]╠═══════════════════════════════════════════╣[/bold blue]")
|
30
|
-
console.print("[bold blue]║ Your AI-powered development companion ║[/bold blue]")
|
31
|
-
console.print("[bold blue]╚═══════════════════════════════════════════╝[/bold blue]")
|
32
|
-
console.print("\n[cyan]Type your requests or 'exit' to quit[/cyan]\n")
|
33
|
-
|
34
|
-
while True:
|
35
|
-
try:
|
36
|
-
request = session.prompt("janito> ")
|
37
|
-
if request.lower() in ('exit', 'quit'):
|
38
|
-
break
|
39
|
-
|
40
|
-
if not request.strip():
|
41
|
-
continue
|
42
|
-
|
43
|
-
# Get current files content
|
44
|
-
paths_to_scan = [workdir] if workdir else []
|
45
|
-
if include:
|
46
|
-
paths_to_scan.extend(include)
|
47
|
-
files_content = collect_files_content(paths_to_scan, workdir)
|
48
|
-
|
49
|
-
# Get initial analysis
|
50
|
-
initial_prompt = build_request_analisys_prompt(files_content, request)
|
51
|
-
initial_response = claude.send_message(initial_prompt)
|
52
|
-
|
53
|
-
# Show response and handle options
|
54
|
-
console.print(initial_response)
|
55
|
-
handle_option_selection(claude, initial_response, request, False, workdir, include)
|
56
|
-
|
57
|
-
except KeyboardInterrupt:
|
58
|
-
continue
|
59
|
-
except EOFError:
|
60
|
-
break
|