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.
Files changed (51) hide show
  1. janito/__init__.py +48 -1
  2. janito/__main__.py +29 -235
  3. janito/_contextparser.py +113 -0
  4. janito/agents/__init__.py +22 -0
  5. janito/agents/agent.py +21 -0
  6. janito/agents/claudeai.py +64 -0
  7. janito/agents/openai.py +53 -0
  8. janito/agents/test.py +34 -0
  9. janito/analysis/__init__.py +33 -0
  10. janito/analysis/display.py +149 -0
  11. janito/analysis/options.py +112 -0
  12. janito/analysis/prompts.py +75 -0
  13. janito/change/__init__.py +19 -0
  14. janito/change/applier.py +269 -0
  15. janito/change/content.py +62 -0
  16. janito/change/indentation.py +33 -0
  17. janito/change/position.py +169 -0
  18. janito/changehistory.py +46 -0
  19. janito/changeviewer/__init__.py +12 -0
  20. janito/changeviewer/diff.py +28 -0
  21. janito/changeviewer/panels.py +268 -0
  22. janito/changeviewer/styling.py +59 -0
  23. janito/changeviewer/themes.py +57 -0
  24. janito/cli/__init__.py +2 -0
  25. janito/cli/commands.py +53 -0
  26. janito/cli/functions.py +286 -0
  27. janito/cli/registry.py +26 -0
  28. janito/common.py +23 -0
  29. janito/config.py +8 -3
  30. janito/console/__init__.py +3 -0
  31. janito/console/commands.py +112 -0
  32. janito/console/core.py +62 -0
  33. janito/console/display.py +157 -0
  34. janito/fileparser.py +334 -0
  35. janito/prompts.py +58 -74
  36. janito/qa.py +40 -7
  37. janito/review.py +13 -0
  38. janito/scan.py +68 -14
  39. janito/tests/test_fileparser.py +26 -0
  40. janito/version.py +23 -0
  41. janito-0.5.0.dist-info/METADATA +146 -0
  42. janito-0.5.0.dist-info/RECORD +45 -0
  43. janito/changeviewer.py +0 -64
  44. janito/claude.py +0 -74
  45. janito/console.py +0 -60
  46. janito/contentchange.py +0 -165
  47. janito-0.3.0.dist-info/METADATA +0 -138
  48. janito-0.3.0.dist-info/RECORD +0 -15
  49. {janito-0.3.0.dist-info → janito-0.5.0.dist-info}/WHEEL +0 -0
  50. {janito-0.3.0.dist-info → janito-0.5.0.dist-info}/entry_points.txt +0 -0
  51. {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 janito.claude import ClaudeAPIAgent
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, claude: ClaudeAPIAgent) -> 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 claude.send_message(prompt)
33
+ return progress_send_message(prompt)
34
+
24
35
 
25
36
  def display_answer(answer: str, raw: bool = False) -> None:
26
- """Display the answer in markdown or raw format"""
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
- else:
31
- md = Markdown(answer)
32
- console.print(md)
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
- if (path / special_file).exists():
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
- scan_path(item, level+1)
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
- # Change message based on whether we're scanning included paths or workdir
109
- if len(paths) == 1 and paths[0] == workdir:
110
- console.print(f"\n[bold blue]Scanning working directory:[/bold blue] {workdir.absolute()}")
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(f"\n[bold blue]Working directory:[/bold blue] {workdir.absolute()}")
113
- console.print("\n[bold blue]Scanning included paths:[/bold blue]")
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
- console.print(f" • {path.absolute()}")
116
-
117
- console.print("\n[bold blue]Files that would be analyzed:[/bold blue]")
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