janito 0.4.0__py3-none-any.whl → 0.6.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 (104) hide show
  1. janito/__init__.py +1 -1
  2. janito/__main__.py +102 -326
  3. janito/agents/__init__.py +16 -0
  4. janito/agents/agent.py +21 -0
  5. janito/{claude.py → agents/claudeai.py} +13 -17
  6. janito/agents/openai.py +53 -0
  7. janito/agents/test.py +34 -0
  8. janito/change/__init__.py +32 -0
  9. janito/change/__main__.py +0 -0
  10. janito/change/analysis/__init__.py +23 -0
  11. janito/change/analysis/__main__.py +7 -0
  12. janito/change/analysis/analyze.py +61 -0
  13. janito/change/analysis/formatting.py +78 -0
  14. janito/change/analysis/options.py +81 -0
  15. janito/change/analysis/prompts.py +98 -0
  16. janito/change/analysis/view/__init__.py +9 -0
  17. janito/change/analysis/view/terminal.py +171 -0
  18. janito/change/applier/__init__.py +5 -0
  19. janito/change/applier/file.py +58 -0
  20. janito/change/applier/main.py +156 -0
  21. janito/change/applier/text.py +245 -0
  22. janito/change/applier/workspace_dir.py +58 -0
  23. janito/change/core.py +131 -0
  24. janito/change/history.py +44 -0
  25. janito/change/operations.py +7 -0
  26. janito/change/parser.py +289 -0
  27. janito/change/play.py +54 -0
  28. janito/change/preview.py +82 -0
  29. janito/change/prompts.py +126 -0
  30. janito/change/test.py +0 -0
  31. janito/change/validator.py +251 -0
  32. janito/change/viewer/__init__.py +11 -0
  33. janito/change/viewer/content.py +66 -0
  34. janito/change/viewer/diff.py +43 -0
  35. janito/change/viewer/pager.py +56 -0
  36. janito/change/viewer/panels.py +555 -0
  37. janito/change/viewer/styling.py +103 -0
  38. janito/change/viewer/themes.py +55 -0
  39. janito/clear_statement_parser/clear_statement_format.txt +328 -0
  40. janito/clear_statement_parser/examples.txt +326 -0
  41. janito/clear_statement_parser/models.py +104 -0
  42. janito/clear_statement_parser/parser.py +496 -0
  43. janito/cli/__init__.py +2 -0
  44. janito/cli/base.py +30 -0
  45. janito/cli/commands.py +45 -0
  46. janito/cli/functions.py +111 -0
  47. janito/cli/handlers/ask.py +22 -0
  48. janito/cli/handlers/demo.py +22 -0
  49. janito/cli/handlers/request.py +24 -0
  50. janito/cli/handlers/scan.py +9 -0
  51. janito/cli/history.py +61 -0
  52. janito/cli/registry.py +26 -0
  53. janito/common.py +41 -10
  54. janito/config.py +71 -6
  55. janito/demo/__init__.py +4 -0
  56. janito/demo/data.py +13 -0
  57. janito/demo/mock_data.py +20 -0
  58. janito/demo/operations.py +45 -0
  59. janito/demo/runner.py +59 -0
  60. janito/demo/scenarios.py +32 -0
  61. janito/prompts.py +1 -65
  62. janito/qa.py +8 -5
  63. janito/review.py +13 -0
  64. janito/search_replace/README.md +146 -0
  65. janito/search_replace/__init__.py +6 -0
  66. janito/search_replace/__main__.py +21 -0
  67. janito/search_replace/core.py +119 -0
  68. janito/search_replace/parser.py +52 -0
  69. janito/search_replace/play.py +61 -0
  70. janito/search_replace/replacer.py +36 -0
  71. janito/search_replace/searcher.py +299 -0
  72. janito/shell/__init__.py +39 -0
  73. janito/shell/bus.py +31 -0
  74. janito/shell/commands.py +195 -0
  75. janito/shell/handlers.py +122 -0
  76. janito/shell/history.py +20 -0
  77. janito/shell/processor.py +52 -0
  78. janito/tui/__init__.py +21 -0
  79. janito/tui/base.py +22 -0
  80. janito/tui/flows/__init__.py +5 -0
  81. janito/tui/flows/changes.py +65 -0
  82. janito/tui/flows/content.py +128 -0
  83. janito/tui/flows/selection.py +117 -0
  84. janito/tui/screens/__init__.py +3 -0
  85. janito/tui/screens/app.py +1 -0
  86. janito/workspace/__init__.py +7 -0
  87. janito/workspace/analysis.py +121 -0
  88. janito/workspace/manager.py +48 -0
  89. janito/workspace/scan.py +232 -0
  90. janito-0.6.0.dist-info/METADATA +185 -0
  91. janito-0.6.0.dist-info/RECORD +95 -0
  92. {janito-0.4.0.dist-info → janito-0.6.0.dist-info}/WHEEL +1 -1
  93. janito/analysis.py +0 -281
  94. janito/changeapplier.py +0 -436
  95. janito/changeviewer.py +0 -350
  96. janito/console.py +0 -330
  97. janito/contentchange.py +0 -84
  98. janito/contextparser.py +0 -113
  99. janito/fileparser.py +0 -125
  100. janito/scan.py +0 -137
  101. janito-0.4.0.dist-info/METADATA +0 -164
  102. janito-0.4.0.dist-info/RECORD +0 -21
  103. {janito-0.4.0.dist-info → janito-0.6.0.dist-info}/entry_points.txt +0 -0
  104. {janito-0.4.0.dist-info → janito-0.6.0.dist-info}/licenses/LICENSE +0 -0
janito/contentchange.py DELETED
@@ -1,84 +0,0 @@
1
- from pathlib import Path
2
- from typing import Dict, Tuple
3
- from rich.console import Console
4
- from datetime import datetime
5
- from janito.fileparser import FileChange, parse_block_changes
6
- from janito.changeapplier import preview_and_apply_changes
7
-
8
- def get_file_type(filepath: Path) -> str:
9
- """Determine the type of saved file based on its name"""
10
- name = filepath.name.lower()
11
- if 'changes' in name:
12
- return 'changes'
13
- elif 'selected' in name:
14
- return 'selected'
15
- elif 'analysis' in name:
16
- return 'analysis'
17
- elif 'response' in name:
18
- return 'response'
19
- return 'unknown'
20
-
21
- def save_changes_to_history(content: str, request: str, workdir: Path) -> Path:
22
- """Save change content to history folder with timestamp and request info"""
23
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") # Already in the correct format
24
- history_dir = workdir / '.janito' / 'history'
25
- history_dir.mkdir(parents=True, exist_ok=True)
26
-
27
- # Create history entry with request and changes
28
- history_file = history_dir / f"changes_{timestamp}.txt"
29
-
30
- history_content = f"""Request: {request}
31
- Timestamp: {timestamp}
32
-
33
- Changes:
34
- {content}
35
- """
36
- history_file.write_text(history_content)
37
- return history_file
38
-
39
- def process_and_save_changes(content: str, request: str, workdir: Path) -> Tuple[Dict[Path, Tuple[str, str]], Path]:
40
- """Parse changes and save to history, returns (changes_dict, history_file)"""
41
- changes = parse_block_changes(content)
42
- history_file = save_changes_to_history(content, request, workdir)
43
- return changes, history_file
44
-
45
- def validate_python_syntax(content: str, filepath: Path) -> Tuple[bool, str]:
46
- """Validate Python syntax and return (is_valid, error_message)"""
47
- try:
48
- ast.parse(content)
49
- return True, ""
50
- except SyntaxError as e:
51
- return False, f"Line {e.lineno}: {e.msg}"
52
-
53
- def format_parsed_changes(changes: Dict[Path, Tuple[str, str]]) -> str:
54
- """Format parsed changes to show only file change descriptions"""
55
- result = []
56
- for filepath, (_, description) in changes.items(): # Updated tuple unpacking
57
- result.append(f"=== {filepath} ===\n{description}\n")
58
- return "\n".join(result)
59
-
60
- def apply_content_changes(content: str, request: str, workdir: Path, test_cmd: str = None) -> Tuple[bool, Path]:
61
- """Regular flow: Parse content, save to history, and apply changes."""
62
- console = Console()
63
- changes = parse_block_changes(content)
64
-
65
- if not changes:
66
- console.print("\n[yellow]No file changes were found in the response[/yellow]")
67
- return False, None
68
-
69
- history_file = save_changes_to_history(content, request, workdir)
70
- success = preview_and_apply_changes(changes, workdir, test_cmd)
71
- return success, history_file
72
-
73
- def handle_changes_file(filepath: Path, workdir: Path, test_cmd: str = None) -> Tuple[bool, Path]:
74
- """Replay flow: Load changes from file and apply them."""
75
- content = filepath.read_text()
76
- changes = parse_block_changes(content)
77
-
78
- if not changes:
79
- console = Console()
80
- console.print("\n[yellow]No file changes were found in the file[/yellow]")
81
- return False, None
82
-
83
- success = preview_and_apply_changes(changes, workdir, test_cmd)
84
- return success, filepath
janito/contextparser.py DELETED
@@ -1,113 +0,0 @@
1
- from typing import List, Tuple, Optional, NamedTuple
2
- from difflib import SequenceMatcher
3
- from janito.config import config
4
- from rich.console import Console
5
-
6
- class ContextError(NamedTuple):
7
- """Contains error details for context matching failures"""
8
- pre_context: List[str]
9
- post_context: List[str]
10
- content: str
11
-
12
- def parse_change_block(content: str) -> Tuple[List[str], List[str], List[str]]:
13
- """Parse a change block into pre-context, post-context and change lines.
14
- Returns (pre_context_lines, post_context_lines, change_lines)"""
15
- pre_context_lines = []
16
- post_context_lines = []
17
- change_lines = []
18
- in_pre_context = True
19
-
20
- for line in content.splitlines():
21
- if line.startswith('='):
22
- if in_pre_context:
23
- pre_context_lines.append(line[1:])
24
- else:
25
- post_context_lines.append(line[1:])
26
- elif line.startswith('>'):
27
- in_pre_context = False
28
- change_lines.append(line[1:])
29
-
30
- return pre_context_lines, post_context_lines, change_lines
31
-
32
- def find_context_match(file_content: str, pre_context: List[str], post_context: List[str], min_context: int = 2) -> Optional[Tuple[int, int]]:
33
- """Find exact matching location using line-by-line matching.
34
- Returns (start_index, end_index) or None if no match found."""
35
- if not (pre_context or post_context) or (len(pre_context) + len(post_context)) < min_context:
36
- return None
37
-
38
- file_lines = file_content.splitlines()
39
-
40
- # Function to check if lines match at a given position
41
- def lines_match_at(pos: int, target_lines: List[str]) -> bool:
42
- if pos + len(target_lines) > len(file_lines):
43
- return False
44
- return all(a == b for a, b in zip(file_lines[pos:pos + len(target_lines)], target_lines))
45
-
46
- # For debug output
47
- debug_matches = []
48
-
49
- # Try to find pre_context match
50
- pre_match_pos = None
51
- if pre_context:
52
- for i in range(len(file_lines) - len(pre_context) + 1):
53
- if lines_match_at(i, pre_context):
54
- pre_match_pos = i
55
- break
56
- if config.debug:
57
- # Record first 20 non-matches for debug output
58
- if len(debug_matches) < 20:
59
- debug_matches.append((i, file_lines[i:i + len(pre_context)]))
60
-
61
- # Try to find post_context match after pre_context if found
62
- if pre_match_pos is not None and post_context:
63
- expected_post_pos = pre_match_pos + len(pre_context)
64
- if not lines_match_at(expected_post_pos, post_context):
65
- pre_match_pos = None
66
-
67
- if pre_match_pos is None and config.debug:
68
- console = Console()
69
- console.print("\n[bold red]Context Match Debug:[/bold red]")
70
-
71
- if pre_context:
72
- console.print("\n[yellow]Expected pre-context:[/yellow]")
73
- for i, line in enumerate(pre_context):
74
- console.print(f" {i+1:2d} | '{line}'")
75
-
76
- if post_context:
77
- console.print("\n[yellow]Expected post-context:[/yellow]")
78
- for i, line in enumerate(post_context):
79
- console.print(f" {i+1:2d} | '{line}'")
80
-
81
- console.print("\n[yellow]First 20 attempted matches in file:[/yellow]")
82
- for pos, lines in debug_matches:
83
- console.print(f"\n[cyan]At line {pos+1}:[/cyan]")
84
- for i, line in enumerate(lines):
85
- match_status = "≠" if i < len(pre_context) and line != pre_context[i] else "="
86
- console.print(f" {i+1:2d} | '{line}' {match_status}")
87
-
88
- return None
89
-
90
- if pre_match_pos is None:
91
- return None
92
-
93
- end_pos = pre_match_pos + len(pre_context)
94
-
95
- return pre_match_pos, end_pos
96
-
97
- def apply_changes(content: str,
98
- pre_context_lines: List[str],
99
- post_context_lines: List[str],
100
- change_lines: List[str]) -> Optional[Tuple[str, Optional[ContextError]]]:
101
- """Apply changes with context matching, returns (new_content, error_details)"""
102
- if not content.strip() and not pre_context_lines and not post_context_lines:
103
- return '\n'.join(change_lines), None
104
-
105
- pre_context = '\n'.join(pre_context_lines)
106
- post_context = '\n'.join(post_context_lines)
107
-
108
- if pre_context and pre_context not in content:
109
- return None, ContextError(pre_context_lines, post_context_lines, content)
110
-
111
- if post_context and post_context not in content:
112
- return None, ContextError(pre_context_lines, post_context_lines, content)
113
-
janito/fileparser.py DELETED
@@ -1,125 +0,0 @@
1
- from pathlib import Path
2
- from typing import Dict, Tuple, List, Optional
3
- from dataclasses import dataclass
4
- import re
5
- import ast
6
- import sys # Add this import
7
- from rich.console import Console
8
- from rich.panel import Panel # Add this import
9
- from janito.config import config # Add this import
10
-
11
- @dataclass
12
- class FileChange:
13
- """Represents a file change with search/replace, search/delete or create instructions"""
14
- description: str
15
- is_new_file: bool
16
- content: str = "" # For new files
17
- search_blocks: List[Tuple[str, Optional[str], Optional[str]]] = None # (search, replace, description)
18
-
19
- def add_search_block(self, search: str, replace: Optional[str], description: Optional[str] = None) -> None:
20
- """Add a search/replace or search/delete block with optional description"""
21
- if self.search_blocks is None:
22
- self.search_blocks = []
23
- self.search_blocks.append((search, replace, description))
24
-
25
- def validate_python_syntax(content: str, filepath: Path) -> Tuple[bool, str]:
26
- """Validate Python syntax and return (is_valid, error_message)"""
27
- try:
28
- ast.parse(content)
29
- console = Console()
30
- console.print(f"[green]✓ Python syntax validation passed:[/green] {filepath.absolute()}")
31
- return True, ""
32
- except SyntaxError as e:
33
- error_msg = f"Line {e.lineno}: {e.msg}"
34
- console = Console()
35
- console.print(f"[red]✗ Python syntax validation failed:[/red] {filepath.absolute()}")
36
- console.print(f"[red] {error_msg}[/red]")
37
- return False, error_msg
38
-
39
-
40
- def parse_block_changes(response_text: str) -> Dict[Path, FileChange]:
41
- """Parse file changes from response blocks"""
42
- changes = {}
43
- console = Console()
44
- # Match file blocks with UUID
45
- file_pattern = r'## ([a-f0-9]{8}) file (.*?) (modify|create) "(.*?)" ##\n?(.*?)## \1 file end ##'
46
-
47
- for match in re.finditer(file_pattern, response_text, re.DOTALL):
48
- uuid, filepath, action, description, content = match.groups()
49
- path = Path(filepath.strip())
50
-
51
- if action == 'create':
52
- changes[path] = FileChange(
53
- description=description,
54
- is_new_file=True,
55
- content=content[1:] if content.startswith('\n') else content,
56
- search_blocks=[]
57
- )
58
- continue
59
-
60
- # For modifications, find all search/replace and search/delete blocks
61
- search_blocks = []
62
- block_patterns = [
63
- # Match search/replace blocks with description - updated pattern
64
- (r'## ' + re.escape(uuid) + r' search/replace "(.*?)" ##\n?(.*?)## ' +
65
- re.escape(uuid) + r' replace with ##\n?(.*?)(?=## ' + re.escape(uuid) + r'|$)', False),
66
- # Match search/delete blocks with description
67
- (r'## ' + re.escape(uuid) + r' search/delete "(.*?)" ##\n?(.*?)(?=## ' + re.escape(uuid) + r'|$)', True)
68
- ]
69
-
70
- if config.debug:
71
- console.print("\n[blue]Updated regex patterns:[/blue]")
72
- for pattern, is_delete in block_patterns:
73
- console.print(Panel(pattern, title="Search/Replace Pattern" if not is_delete else "Search/Delete Pattern", border_style="blue"))
74
-
75
- for pattern, is_delete in block_patterns:
76
- if config.debug:
77
- console.print(f"\n[blue]Looking for pattern:[/blue]")
78
- console.print(Panel(pattern, title="Pattern", border_style="blue"))
79
- console.print(f"\n[blue]In content:[/blue]")
80
- console.print(Panel(content, title="Content", border_style="blue"))
81
-
82
- for block_match in re.finditer(pattern, content, re.DOTALL):
83
- if is_delete:
84
- description, search = block_match.groups()
85
- search = search.rstrip('\n') + '\n' # Ensure single trailing newline
86
- replace = None
87
- else:
88
- description, search, replace = block_match.groups()
89
- search = search.rstrip('\n') + '\n' # Ensure single trailing newline
90
- replace = (replace.rstrip('\n') + '\n') if replace else None
91
-
92
- # Abort parsing if replace content is empty
93
- if not is_delete and (replace is None or replace.strip() == ''):
94
- console.print(f"\n[red]Error: Empty replace content found![/red]")
95
- console.print(f"[red]File:[/red] {filepath}")
96
- console.print(f"[red]Description:[/red] {description}")
97
- console.print("[yellow]Search block:[/yellow]")
98
- console.print(Panel(search, title="Search Content", border_style="yellow"))
99
- console.print("[red]Replace block is empty or contains only whitespace![/red]")
100
- console.print("[red]Aborting due to empty replace content.[/red]")
101
- sys.exit(1)
102
-
103
- # Enhanced debug info
104
- if config.debug or (not is_delete and (replace is None or replace.strip() == '')):
105
- console.print(f"\n[yellow]Search/Replace block analysis:[/yellow]")
106
- console.print(f"[yellow]File:[/yellow] {filepath}")
107
- console.print(f"[yellow]Description:[/yellow] {description}")
108
- console.print("[yellow]Search block:[/yellow]")
109
- console.print(Panel(search, title="Search Content", border_style="yellow"))
110
- console.print("[yellow]Replace block:[/yellow]")
111
- console.print(Panel(replace if replace else "<empty>", title="Replace Content", border_style="yellow"))
112
- console.print("\n[blue]Match groups:[/blue]")
113
- for i, group in enumerate(block_match.groups()):
114
- console.print(Panel(str(group), title=f"Group {i}", border_style="blue"))
115
-
116
- search_blocks.append((search, replace, description))
117
-
118
- # Add debug info if no blocks were found
119
- if config.debug and not search_blocks:
120
- console.print(f"\n[red]No search/replace blocks found for file:[/red] {filepath}")
121
- console.print("[red]Check if the content format matches the expected patterns[/red]")
122
-
123
- changes[path] = FileChange(description=description, is_new_file=False, search_blocks=search_blocks)
124
-
125
- return changes
janito/scan.py DELETED
@@ -1,137 +0,0 @@
1
- from pathlib import Path
2
- from typing import List, Tuple
3
- from rich.console import Console
4
- from rich.columns import Columns
5
- from janito.config import config
6
- from pathspec import PathSpec
7
- from pathspec.patterns import GitWildMatchPattern
8
-
9
-
10
- SPECIAL_FILES = ["README.md", "__init__.py", "__main__.py"]
11
-
12
- def _scan_paths(paths: List[Path], workdir: Path = None) -> Tuple[List[str], List[str]]:
13
- """Common scanning logic used by both preview and content collection"""
14
- content_parts = []
15
- file_items = []
16
- skipped_files = []
17
- console = Console()
18
-
19
- # Load gitignore if it exists
20
- gitignore_path = workdir / '.gitignore' if workdir else None
21
- gitignore_spec = None
22
- if (gitignore_path and gitignore_path.exists()):
23
- with open(gitignore_path) as f:
24
- gitignore = f.read()
25
- gitignore_spec = PathSpec.from_lines(GitWildMatchPattern, gitignore.splitlines())
26
-
27
-
28
- def scan_path(path: Path, level: int) -> None:
29
- """
30
- Scan a path and add it to the content_parts list
31
- level 0 means we are scanning the root directory
32
- level 1 we provide both directory directory name and file content
33
- level > 1 we just return
34
- """
35
- if level > 1:
36
- return
37
-
38
- relative_base = workdir
39
- if path.is_dir():
40
- relative_path = path.relative_to(relative_base)
41
- content_parts.append(f'<directory><path>{relative_path}</path>not sent</directory>')
42
- file_items.append(f"[blue]•[/blue] {relative_path}/")
43
- # Check for special files
44
- special_found = []
45
- for special_file in SPECIAL_FILES:
46
- if (path / special_file).exists():
47
- special_found.append(special_file)
48
- if special_found:
49
- file_items[-1] = f"[blue]•[/blue] {relative_path}/ [cyan]({', '.join(special_found)})[/cyan]"
50
- for special_file in special_found:
51
- special_path = path / special_file
52
- try:
53
- relative_path = special_path.relative_to(relative_base)
54
- file_content = special_path.read_text(encoding='utf-8')
55
- content_parts.append(f"<file>\n<path>{relative_path}</path>\n<content>\n{file_content}\n</content>\n</file>")
56
- except UnicodeDecodeError:
57
- skipped_files.append(str(relative_path))
58
- console.print(f"[yellow]Warning: Skipping file due to encoding issues: {relative_path}[/yellow]")
59
-
60
- for item in path.iterdir():
61
- # Skip if matches gitignore patterns
62
- if gitignore_spec:
63
- rel_path = str(item.relative_to(workdir))
64
- if gitignore_spec.match_file(rel_path):
65
- continue
66
- scan_path(item, level+1)
67
-
68
- else:
69
- relative_path = path.relative_to(relative_base)
70
- # check if file is binary
71
- try:
72
- if path.is_file() and path.read_bytes().find(b'\x00') != -1:
73
- console.print(f"[red]Skipped binary file found: {relative_path}[/red]")
74
- return
75
- file_content = path.read_text(encoding='utf-8')
76
- content_parts.append(f"<file>\n<path>{relative_path}</path>\n<content>\n{file_content}\n</content>\n</file>")
77
- file_items.append(f"[cyan]•[/cyan] {relative_path}")
78
- except UnicodeDecodeError:
79
- skipped_files.append(str(relative_path))
80
- console.print(f"[yellow]Warning: Skipping file due to encoding issues: {relative_path}[/yellow]")
81
-
82
- for path in paths:
83
- scan_path(path, 0)
84
-
85
- if skipped_files and config.verbose:
86
- console.print("\n[yellow]Files skipped due to encoding issues:[/yellow]")
87
- for file in skipped_files:
88
- console.print(f" • {file}")
89
-
90
- return content_parts, file_items
91
-
92
- def collect_files_content(paths: List[Path], workdir: Path = None) -> str:
93
- """Collect content from all files in XML format"""
94
- console = Console()
95
- content_parts, file_items = _scan_paths(paths, workdir)
96
-
97
- if file_items and config.verbose:
98
- console.print("\n[bold blue]Contents being analyzed:[/bold blue]")
99
- console.print(Columns(file_items, padding=(0, 4), expand=True))
100
-
101
- return "\n".join(content_parts)
102
-
103
-
104
- def preview_scan(paths: List[Path], workdir: Path = None) -> None:
105
- """Preview what files and directories would be scanned"""
106
- console = Console()
107
- _, file_items = _scan_paths(paths, workdir)
108
-
109
- # Display working directory status
110
- console.print("\n[bold blue]Analysis Paths:[/bold blue]")
111
- console.print(f"[cyan]Working Directory:[/cyan] {workdir.absolute()}")
112
-
113
- # Show if working directory is being scanned
114
- is_workdir_scanned = any(p.resolve() == workdir.resolve() for p in paths)
115
- if is_workdir_scanned:
116
- console.print("[green]✓ Working directory will be scanned[/green]")
117
- else:
118
- console.print("[yellow]! Working directory will not be scanned[/yellow]")
119
-
120
- # Show included paths relative to working directory
121
- if len(paths) > (1 if is_workdir_scanned else 0):
122
- console.print("\n[cyan]Additional Included Paths:[/cyan]")
123
- for path in paths:
124
- if path.resolve() != workdir.resolve():
125
- try:
126
- rel_path = path.relative_to(workdir)
127
- console.print(f" • ./{rel_path}")
128
- except ValueError:
129
- # Path is outside working directory
130
- console.print(f" • {path.absolute()}")
131
-
132
- console.print("\n[bold blue]Files that will be analyzed:[/bold blue]")
133
- console.print(Columns(file_items, padding=(0, 4), expand=True))
134
-
135
- def is_dir_empty(path: Path) -> bool:
136
- """Check if directory is empty, ignoring hidden files"""
137
- return not any(item for item in path.iterdir() if not item.name.startswith('.'))
@@ -1,164 +0,0 @@
1
- Metadata-Version: 2.3
2
- Name: janito
3
- Version: 0.4.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.
28
-
29
- Meet Janito, your friendly AI-powered software development buddy! Janito helps you with coding tasks like refactoring, documentation updates, and code optimization while being clear and concise in its responses.
30
-
31
- ## 📥 Installation
32
-
33
- ```bash
34
- # Install from PyPI
35
- pip install janito
36
-
37
- # Install from source
38
- git clone https://github.com/joaompinto/janito.git
39
- cd janito
40
- pip install -e .
41
- ```
42
-
43
- ## ⚡ Requirements
44
-
45
- - Python 3.8+
46
- - Anthropic API key
47
- - Required packages (automatically installed):
48
- - typer
49
- - pathspec
50
- - rich
51
-
52
- ## ⚙️ Configuration
53
-
54
- ### 🔑 API Key Setup
55
- Janito requires an Anthropic API key to function. Set it as an environment variable:
56
-
57
- ```bash
58
- export ANTHROPIC_API_KEY='your-api-key-here'
59
- ```
60
-
61
- You can also add this to your shell profile (~/.bashrc, ~/.zshrc, etc.) for persistence.
62
-
63
- ### ⚙️ Test Command Setup
64
- You can configure a test command to run before applying changes:
65
-
66
- ```bash
67
- export JANITO_TEST_CMD='your-test-command'
68
- ```
69
-
70
- This command will be executed in the preview directory before applying changes to verify they don't break anything.
71
-
72
- ## 📖 Usage
73
-
74
- Janito can be used in two modes: Command Line or Interactive Console.
75
-
76
- ### 💻 Command Line Mode
77
-
78
- ```bash
79
- janito REQUEST [OPTIONS]
80
- ```
81
-
82
- #### Arguments
83
- - `REQUEST`: The modification request to process (optional)
84
-
85
- #### Options
86
- ##### General Options
87
- - `--version`: Show version and exit
88
- - `-w, --workdir PATH`: Working directory (defaults to current directory)
89
- - `-i, --include PATH`: Additional paths to include in analysis (can be used multiple times)
90
-
91
- ##### Operation Modes
92
- - `--ask TEXT`: Ask a question about the codebase instead of making changes
93
- - `--scan`: Preview files that would be analyzed without making changes
94
- - `--play PATH`: Replay a saved prompt file
95
-
96
- ##### Output Control
97
- - `--raw`: Print raw response instead of markdown format
98
- - `-v, --verbose`: Show verbose output
99
- - `--debug`: Show debug information
100
-
101
- ##### Testing
102
- - `-t, --test COMMAND`: Test command to run before applying changes (overrides JANITO_TEST_CMD)
103
-
104
- ### 🖥️ Interactive Console Mode
105
-
106
- Start the interactive console by running `janito` without arguments:
107
-
108
- ```bash
109
- janito
110
- ```
111
-
112
- In console mode, you can:
113
- - Enter requests directly
114
- - Navigate history with up/down arrows
115
- - Use special commands starting with /
116
-
117
- ### 📝 Examples
118
-
119
- ```bash
120
- # Command Line Mode Examples
121
- janito "create docstrings for all functions"
122
- janito "add error handling" -w ./myproject
123
- janito "update tests" -i ./tests -i ./lib
124
- janito --ask "explain the authentication flow"
125
- janito --scan # Preview files to be analyzed
126
-
127
- # Test Command Examples
128
- janito "update code" --test "pytest" # Run pytest before applying changes
129
- janito "refactor module" -t "make test" # Run make test before applying
130
- export JANITO_TEST_CMD="python -m unittest" # Set default test command
131
- janito "optimize function" # Will use JANITO_TEST_CMD
132
-
133
- # Console Mode
134
- janito # Starts interactive session
135
- ```
136
-
137
- ## ✨ Features
138
-
139
- - 🤖 AI-powered code analysis and modifications
140
- - 💻 Interactive console mode for continuous interaction
141
- - 📁 Support for multiple file types
142
- - ✅ Syntax validation for Python files
143
- - 👀 Interactive change preview and confirmation
144
- - 📜 History tracking of all changes
145
- - 🐛 Debug and verbose output modes
146
- - ❓ Question-answering about codebase
147
- - 🔍 File scanning preview
148
- - 🧪 Test command execution before applying changes
149
-
150
- ## 📚 History and Debugging
151
-
152
- Changes are automatically saved in `.janito/history/` with timestamps:
153
- - `*_analysis.txt`: Initial analysis
154
- - `*_selected.txt`: Selected implementation
155
- - `*_changes.txt`: Actual changes
156
-
157
- Enable debug mode for detailed logging:
158
- ```bash
159
- janito "request" --debug
160
- ```
161
-
162
- ## 📄 License
163
-
164
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
@@ -1,21 +0,0 @@
1
- janito/__init__.py,sha256=CLeVFqpY9Ki3R3MgLAiTjNsJjsj1BD3_9CzP2kgCj-k,52
2
- janito/__main__.py,sha256=DqbMlHNWCX-XwiZz1qEYP3qAADKtfmlDhhbTgpXB5_M,14158
3
- janito/analysis.py,sha256=dAzMARAcDZ1LN6KC2-Xb0bIB0p5VsaT2lhurtPLLIKk,9368
4
- janito/changeapplier.py,sha256=fDP87Hh3uY8qoSv3-SSnafLCRJHLPQZ5U8GIMkdVZ9Y,17697
5
- janito/changeviewer.py,sha256=51J0pvGe1rfYpiYo0mhZMonsYJmyAN44mg1TfKRxdM4,12716
6
- janito/claude.py,sha256=N7ZX6WCrLKcj9na3o2MMPqH8tlobHYtIG1HnhgMVUKw,2272
7
- janito/common.py,sha256=1blM7rY55-mLPXD4L3xwS-RaJ2tBZx8xHhaLYQku3Rk,801
8
- janito/config.py,sha256=ocg0lyab9ysgczKaqJTAtv0ZRa2VDMwclTJBgps7Vxw,1171
9
- janito/console.py,sha256=E2mLPoBw7Gh1K1hPuCoRDBEFE-klIERt5KFgE3fHqUg,11100
10
- janito/contentchange.py,sha256=2HkNhqA_ZIXDHfiv3xRcwfoxMzFUNMuFfbMtf-4jhRM,3282
11
- janito/contextparser.py,sha256=iDX6nlqUQr-lj7lkEI4B6jHLci_Kxl-XWOaEiAQtVxA,4531
12
- janito/fileparser.py,sha256=8nngFmR9s6NVv2YuMcNKO4udNh-ZcKN4_Jo11vKzLhI,6586
13
- janito/prompts.py,sha256=aUDDx16L2ygPJZwrWn4FmrXt1KGNfLeYRcDSPD9BzIA,1879
14
- janito/qa.py,sha256=qrAXbsGPCRSW378V88w8e19uU3hfbDnx6lsiRYhq324,1927
15
- janito/scan.py,sha256=c_IKSuWx1_525RlIgPtP-tFoH7lManVGGNFMxVg1hMk,6014
16
- janito/version.py,sha256=ylfPwGtdY8dEOFJ-DB9gKUQLggqRCvoLxhpnwjzCM94,739
17
- janito-0.4.0.dist-info/METADATA,sha256=uUCven38ndkbyLrrLw3B42P7AGyijqTGICKRzLJkDDE,4705
18
- janito-0.4.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
19
- janito-0.4.0.dist-info/entry_points.txt,sha256=wIo5zZxbmu4fC-ZMrsKD0T0vq7IqkOOLYhrqRGypkx4,48
20
- janito-0.4.0.dist-info/licenses/LICENSE,sha256=xLIUXRPjtsgQml2zD1Pn4LpgiyZ49raw6jZDlO_gZdo,1062
21
- janito-0.4.0.dist-info/RECORD,,