janito 0.5.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.
- janito/__init__.py +0 -47
- janito/__main__.py +96 -15
- janito/agents/__init__.py +2 -8
- janito/agents/claudeai.py +3 -12
- janito/change/__init__.py +29 -16
- janito/change/__main__.py +0 -0
- janito/{analysis → change/analysis}/__init__.py +5 -15
- janito/change/analysis/__main__.py +7 -0
- janito/change/analysis/analyze.py +61 -0
- janito/change/analysis/formatting.py +78 -0
- janito/change/analysis/options.py +81 -0
- janito/{analysis → change/analysis}/prompts.py +35 -12
- janito/change/analysis/view/__init__.py +9 -0
- janito/change/analysis/view/terminal.py +171 -0
- janito/change/applier/__init__.py +5 -0
- janito/change/applier/file.py +58 -0
- janito/change/applier/main.py +156 -0
- janito/change/applier/text.py +245 -0
- janito/change/applier/workspace_dir.py +58 -0
- janito/change/core.py +131 -0
- janito/{changehistory.py → change/history.py} +12 -14
- janito/change/operations.py +7 -0
- janito/change/parser.py +289 -0
- janito/change/play.py +54 -0
- janito/change/preview.py +82 -0
- janito/change/prompts.py +126 -0
- janito/change/test.py +0 -0
- janito/change/validator.py +251 -0
- janito/{changeviewer → change/viewer}/__init__.py +3 -4
- janito/change/viewer/content.py +66 -0
- janito/{changeviewer → change/viewer}/diff.py +19 -4
- janito/change/viewer/pager.py +56 -0
- janito/change/viewer/panels.py +555 -0
- janito/change/viewer/styling.py +103 -0
- janito/{changeviewer → change/viewer}/themes.py +3 -5
- janito/clear_statement_parser/clear_statement_format.txt +328 -0
- janito/clear_statement_parser/examples.txt +326 -0
- janito/clear_statement_parser/models.py +104 -0
- janito/clear_statement_parser/parser.py +496 -0
- janito/cli/base.py +30 -0
- janito/cli/commands.py +30 -38
- janito/cli/functions.py +19 -194
- janito/cli/handlers/ask.py +22 -0
- janito/cli/handlers/demo.py +22 -0
- janito/cli/handlers/request.py +24 -0
- janito/cli/handlers/scan.py +9 -0
- janito/cli/history.py +61 -0
- janito/common.py +34 -3
- janito/config.py +71 -6
- janito/demo/__init__.py +4 -0
- janito/demo/data.py +13 -0
- janito/demo/mock_data.py +20 -0
- janito/demo/operations.py +45 -0
- janito/demo/runner.py +59 -0
- janito/demo/scenarios.py +32 -0
- janito/prompts.py +1 -80
- janito/qa.py +4 -3
- janito/search_replace/README.md +146 -0
- janito/search_replace/__init__.py +6 -0
- janito/search_replace/__main__.py +21 -0
- janito/search_replace/core.py +119 -0
- janito/search_replace/parser.py +52 -0
- janito/search_replace/play.py +61 -0
- janito/search_replace/replacer.py +36 -0
- janito/search_replace/searcher.py +299 -0
- janito/shell/__init__.py +39 -0
- janito/shell/bus.py +31 -0
- janito/shell/commands.py +195 -0
- janito/shell/handlers.py +122 -0
- janito/shell/history.py +20 -0
- janito/shell/processor.py +52 -0
- janito/tui/__init__.py +21 -0
- janito/tui/base.py +22 -0
- janito/tui/flows/__init__.py +5 -0
- janito/tui/flows/changes.py +65 -0
- janito/tui/flows/content.py +128 -0
- janito/tui/flows/selection.py +117 -0
- janito/tui/screens/__init__.py +3 -0
- janito/tui/screens/app.py +1 -0
- janito/workspace/__init__.py +7 -0
- janito/workspace/analysis.py +121 -0
- janito/workspace/manager.py +48 -0
- janito/workspace/scan.py +232 -0
- janito-0.6.0.dist-info/METADATA +185 -0
- janito-0.6.0.dist-info/RECORD +95 -0
- {janito-0.5.0.dist-info → janito-0.6.0.dist-info}/WHEEL +1 -1
- janito/_contextparser.py +0 -113
- janito/analysis/display.py +0 -149
- janito/analysis/options.py +0 -112
- janito/change/applier.py +0 -269
- janito/change/content.py +0 -62
- janito/change/indentation.py +0 -33
- janito/change/position.py +0 -169
- janito/changeviewer/panels.py +0 -268
- janito/changeviewer/styling.py +0 -59
- janito/console/__init__.py +0 -3
- janito/console/commands.py +0 -112
- janito/console/core.py +0 -62
- janito/console/display.py +0 -157
- janito/fileparser.py +0 -334
- janito/scan.py +0 -176
- janito/tests/test_fileparser.py +0 -26
- janito-0.5.0.dist-info/METADATA +0 -146
- janito-0.5.0.dist-info/RECORD +0 -45
- {janito-0.5.0.dist-info → janito-0.6.0.dist-info}/entry_points.txt +0 -0
- {janito-0.5.0.dist-info → janito-0.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,121 @@
|
|
1
|
+
from collections import defaultdict
|
2
|
+
from pathlib import Path
|
3
|
+
from typing import Dict, List
|
4
|
+
|
5
|
+
from rich.columns import Columns
|
6
|
+
from rich.console import Console, Group
|
7
|
+
from rich.panel import Panel
|
8
|
+
from rich.rule import Rule
|
9
|
+
from janito.config import config
|
10
|
+
|
11
|
+
def analyze_workspace_content(content: str) -> None:
|
12
|
+
"""Show statistics about the scanned content"""
|
13
|
+
if not content:
|
14
|
+
return
|
15
|
+
|
16
|
+
# Collect include paths
|
17
|
+
paths = []
|
18
|
+
if config.include:
|
19
|
+
for path in config.include:
|
20
|
+
is_recursive = path in config.recursive
|
21
|
+
path_str = str(path.relative_to(config.workspace_dir))
|
22
|
+
paths.append(f"{path_str}/*" if is_recursive else f"{path_str}/")
|
23
|
+
else:
|
24
|
+
# Use workspace_dir as fallback when no include paths specified
|
25
|
+
paths.append("./")
|
26
|
+
|
27
|
+
console = Console()
|
28
|
+
|
29
|
+
dir_counts: Dict[str, int] = defaultdict(int)
|
30
|
+
dir_sizes: Dict[str, int] = defaultdict(int)
|
31
|
+
file_types: Dict[str, int] = defaultdict(int)
|
32
|
+
current_path = None
|
33
|
+
current_content = []
|
34
|
+
|
35
|
+
for line in content.split('\n'):
|
36
|
+
if line.startswith('<path>'):
|
37
|
+
path = Path(line.replace('<path>', '').replace('</path>', '').strip())
|
38
|
+
current_path = str(path.parent)
|
39
|
+
dir_counts[current_path] += 1
|
40
|
+
file_types[path.suffix.lower() or 'no_ext'] += 1
|
41
|
+
elif line.startswith('<content>'):
|
42
|
+
current_content = []
|
43
|
+
elif line.startswith('</content>'):
|
44
|
+
content_size = sum(len(line.encode('utf-8')) for line in current_content)
|
45
|
+
if current_path:
|
46
|
+
dir_sizes[current_path] += content_size
|
47
|
+
current_content = []
|
48
|
+
elif current_content is not None:
|
49
|
+
current_content.append(line)
|
50
|
+
|
51
|
+
console = Console()
|
52
|
+
|
53
|
+
# Directory statistics
|
54
|
+
dir_stats = [
|
55
|
+
f"📁 {directory}/ [{count} file(s), {_format_size(size)}]"
|
56
|
+
for directory, (count, size) in (
|
57
|
+
(d, (dir_counts[d], dir_sizes[d]))
|
58
|
+
for d in sorted(dir_counts.keys())
|
59
|
+
)
|
60
|
+
]
|
61
|
+
|
62
|
+
# File type statistics
|
63
|
+
type_stats = [
|
64
|
+
f"📄 .{ext.lstrip('.')} [{count} file(s)]" if ext != 'no_ext' else f"📄 {ext} [{count} file(s)]"
|
65
|
+
for ext, count in sorted(file_types.items())
|
66
|
+
]
|
67
|
+
|
68
|
+
# Create grouped content with styled separators
|
69
|
+
content_sections = []
|
70
|
+
|
71
|
+
if paths:
|
72
|
+
# Group paths with their stats
|
73
|
+
path_stats = []
|
74
|
+
for path in sorted(set(paths)):
|
75
|
+
base_path = Path(path.rstrip("/*"))
|
76
|
+
total_files = sum(1 for d, count in dir_counts.items()
|
77
|
+
if Path(d).is_relative_to(base_path))
|
78
|
+
total_size = sum(size for d, size in dir_sizes.items()
|
79
|
+
if Path(d).is_relative_to(base_path))
|
80
|
+
path_stats.append(f"{path} [{total_files} file(s), {_format_size(total_size)}]")
|
81
|
+
|
82
|
+
content_sections.extend([
|
83
|
+
"[bold yellow]📌 Included Paths[/bold yellow]",
|
84
|
+
Rule(style="yellow"),
|
85
|
+
Columns(path_stats, equal=True, expand=True),
|
86
|
+
"\n"
|
87
|
+
])
|
88
|
+
|
89
|
+
# Add directory structure section only in verbose mode
|
90
|
+
if config.verbose:
|
91
|
+
content_sections.extend([
|
92
|
+
"[bold magenta]📂 Directory Structure[/bold magenta]",
|
93
|
+
Rule(style="magenta"),
|
94
|
+
Columns(dir_stats, equal=True, expand=True),
|
95
|
+
"\n"
|
96
|
+
])
|
97
|
+
|
98
|
+
# Always show file types section
|
99
|
+
content_sections.extend([
|
100
|
+
"[bold cyan]📑 File Types[/bold cyan]",
|
101
|
+
Rule(style="cyan"),
|
102
|
+
Columns(type_stats, equal=True, expand=True)
|
103
|
+
])
|
104
|
+
|
105
|
+
content = Group(*content_sections)
|
106
|
+
|
107
|
+
# Display workspace analysis in panel
|
108
|
+
console.print("\n")
|
109
|
+
console.print(Panel(
|
110
|
+
content,
|
111
|
+
title="[bold blue]Workspace Analysis[/bold blue]",
|
112
|
+
title_align="center"
|
113
|
+
))
|
114
|
+
|
115
|
+
def _format_size(size_bytes: int) -> str:
|
116
|
+
"""Format size in bytes to human readable format"""
|
117
|
+
for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
|
118
|
+
if size_bytes < 1024.0:
|
119
|
+
break
|
120
|
+
size_bytes /= 1024.0
|
121
|
+
return f"{size_bytes:.1f} {unit}"
|
@@ -0,0 +1,48 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
from typing import Dict, List, Optional, Set
|
3
|
+
from collections import defaultdict
|
4
|
+
|
5
|
+
class WorkspaceManager:
|
6
|
+
"""Manages workspace state and operations using singleton pattern."""
|
7
|
+
_instance = None
|
8
|
+
|
9
|
+
def __init__(self):
|
10
|
+
if WorkspaceManager._instance is not None:
|
11
|
+
raise RuntimeError("Use WorkspaceManager.get_instance() instead")
|
12
|
+
self.content: str = ""
|
13
|
+
self.scan_completed: bool = False
|
14
|
+
self._analyzed: bool = False
|
15
|
+
|
16
|
+
@classmethod
|
17
|
+
def get_instance(cls) -> "WorkspaceManager":
|
18
|
+
"""Get singleton instance of WorkspaceManager."""
|
19
|
+
if cls._instance is None:
|
20
|
+
cls._instance = cls()
|
21
|
+
return cls._instance
|
22
|
+
|
23
|
+
def collect_content(self, paths: List[Path]) -> None:
|
24
|
+
"""Collect and store content from specified paths."""
|
25
|
+
from .scan import _scan_paths
|
26
|
+
content_parts, _, _, _ = _scan_paths(paths)
|
27
|
+
self.content = "\n".join(content_parts)
|
28
|
+
self.scan_completed = True
|
29
|
+
self._analyzed = False
|
30
|
+
|
31
|
+
def analyze(self) -> None:
|
32
|
+
"""Analyze workspace content and update statistics."""
|
33
|
+
from .analysis import analyze_workspace_content
|
34
|
+
if not self.scan_completed:
|
35
|
+
return
|
36
|
+
if not self._analyzed and self.content:
|
37
|
+
analyze_workspace_content(self.content)
|
38
|
+
self._analyzed = True
|
39
|
+
|
40
|
+
def get_content(self) -> str:
|
41
|
+
"""Get collected workspace content."""
|
42
|
+
return self.content
|
43
|
+
|
44
|
+
def clear(self) -> None:
|
45
|
+
"""Clear workspace content and stats."""
|
46
|
+
self.content = ""
|
47
|
+
self.scan_completed = False
|
48
|
+
self._analyzed = False
|
janito/workspace/scan.py
ADDED
@@ -0,0 +1,232 @@
|
|
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
|
@@ -0,0 +1,185 @@
|
|
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
|
+
[](https://badge.fury.io/py/janito)
|
29
|
+
[](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)
|
@@ -0,0 +1,95 @@
|
|
1
|
+
janito/__init__.py,sha256=Svp3i5NGeapDV4xB-CDu9TjqEzvsnjQwSzdmDT9DDqc,47
|
2
|
+
janito/__main__.py,sha256=MChFWxE8OPLe8QsdG1nxS1l5s0oZkJoJUlX3IRqEklk,5069
|
3
|
+
janito/common.py,sha256=jGoA1RyglPBpTQJcDdCj6Ar-h8Eh6U7ZOlgJjvdm91M,2076
|
4
|
+
janito/config.py,sha256=B5S6mmnQi2HMA_F6FuGREdbHTj_vdi2cl1WzfFBaiJc,3134
|
5
|
+
janito/prompts.py,sha256=VNC_1orab53YbdKIfnDub_r2PGQbwLn3hUZL0TcSvJQ,201
|
6
|
+
janito/qa.py,sha256=4zXuEg48ogyXmL_jZpJWo4xT0cPY7LG4dUvF-_mvGIc,1959
|
7
|
+
janito/review.py,sha256=5Oc6BfxMGNmKbIeDP5_EiAKUDeQwVOD0YL7iqfgJLRE,471
|
8
|
+
janito/version.py,sha256=ylfPwGtdY8dEOFJ-DB9gKUQLggqRCvoLxhpnwjzCM94,739
|
9
|
+
janito/agents/__init__.py,sha256=I_N-11HvV76yUfDOV_43U5tXz5e5kSnQamCQJE9OXXQ,510
|
10
|
+
janito/agents/agent.py,sha256=3uGiUrvj9oCW6_oK-oMQQJ77K8jZFv7mAdXlIG1dxNY,751
|
11
|
+
janito/agents/claudeai.py,sha256=zPySDG70ATKWPvzB4CKpaK9E8pbmzOhCE1i8ESwaE0o,1908
|
12
|
+
janito/agents/openai.py,sha256=tNtlzFJMWFv38Z62opR23u6xXlZ9L4xX_mf2f3wjrLU,2082
|
13
|
+
janito/agents/test.py,sha256=xoN1q9DUSYpUbnvTP1qZsEfxYrZfocJlt9DkIuMDvvY,1552
|
14
|
+
janito/change/__init__.py,sha256=vGn9qudqiusp4LsR2zzr3mbb4dhXvcGBY2NNzl1JC5M,955
|
15
|
+
janito/change/__main__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
16
|
+
janito/change/core.py,sha256=TGlNQBZm2TdEPtWZSUV5kldxv1y4urL2YkeOn1wDxTk,4070
|
17
|
+
janito/change/history.py,sha256=rbprE1lnOl5We85c-VsDnHAiGphW0t2fA7zu4N9uulw,1405
|
18
|
+
janito/change/operations.py,sha256=5HeUvFa503Foemngu-ShomPn9W82XH-gLlTfXxAZoEw,165
|
19
|
+
janito/change/parser.py,sha256=hrwJXn318mfiw1zWiLN9Frdsp5BKpg83YToCkSyHtQw,11242
|
20
|
+
janito/change/play.py,sha256=ZzYp-eaIBn578EiPB9uVR-G-mxXiK_hD_GI15htb0DU,1835
|
21
|
+
janito/change/preview.py,sha256=xfwo0q7jAx-U92U3iEwDDlsZnzndj7fshq8OLgjv3Vw,2720
|
22
|
+
janito/change/prompts.py,sha256=e0uxXRRz1U1AJS-6pDcoOGJjothJNBhvixtGGYkPyWc,4372
|
23
|
+
janito/change/test.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
24
|
+
janito/change/validator.py,sha256=WxGruFjH9yAsHTOWWIJFpPurrWHNNXE69JogDmraF9A,10310
|
25
|
+
janito/change/analysis/__init__.py,sha256=yg9LhcC1nCvleGt4cnQMuJYCARXBZNvmXaT23S3szJo,593
|
26
|
+
janito/change/analysis/__main__.py,sha256=M6JmJnZfOKB0OB29EpzzfbNZu7cusKT92KQhmnaWd5c,196
|
27
|
+
janito/change/analysis/analyze.py,sha256=eAmqn492W_M9TwizMMlRETq3S9XiDSdXL9alKURgtBw,1655
|
28
|
+
janito/change/analysis/formatting.py,sha256=VqDL6fBzvVm5pdDN4fX9nQQP1wZwhlxDKyaMlDj9ju4,2602
|
29
|
+
janito/change/analysis/options.py,sha256=JNyXCFjOxS1Qm1oqqn8JSXrF4KQ9MotclfKMLD7dZFc,2957
|
30
|
+
janito/change/analysis/prompts.py,sha256=672fztoXENLMms2_wdjaMSczIOzJe9J7tMInDQQh7Bo,3303
|
31
|
+
janito/change/analysis/view/__init__.py,sha256=oQd_ClbL7sWmb8jbie74qRrn09CIUTxLH18b02PJFLk,204
|
32
|
+
janito/change/analysis/view/terminal.py,sha256=7_pjYehUgzo7kOY15AgNyf1JClEy85YtgCr54VxJiOg,6105
|
33
|
+
janito/change/applier/__init__.py,sha256=KdViLadNAWcSUbJcsfAt66cDJpaqYGIHdzSDKZjzsEw,183
|
34
|
+
janito/change/applier/file.py,sha256=NQnoywcVXAxLU0xipsByajs2SCRtsSqFPO8tlJl1J2w,2420
|
35
|
+
janito/change/applier/main.py,sha256=Ua-0tmOHYImSlYzmRPppz9stg50bGM1AFFUdQEFMxhY,6644
|
36
|
+
janito/change/applier/text.py,sha256=l4vmFh1UVnzvm0W09w_m3HrbfYHIIgcNrqUw7Xld2SI,10868
|
37
|
+
janito/change/applier/workspace_dir.py,sha256=whiuIIYyDHh2m-K_ZRMZGvMMQ1nh6lzXaWGhnFK1B1o,2443
|
38
|
+
janito/change/viewer/__init__.py,sha256=tj-rIVbuAm80B7-no-YgdxbtkDlwRK_0hqUfIDtDOAc,250
|
39
|
+
janito/change/viewer/content.py,sha256=SwYkLtsQXI0SCuHKD033XzxwnGQ_MmYckoDpnvJzA0s,2010
|
40
|
+
janito/change/viewer/diff.py,sha256=OzaocnrnH4RCdzZPjT05pEmFJLcd_EgpKhRiAXJrvpc,1815
|
41
|
+
janito/change/viewer/pager.py,sha256=oJFk1ulswrTs7Az09hHSS37SxyN2cvSVFZ_OxA2Z7os,1738
|
42
|
+
janito/change/viewer/panels.py,sha256=1T9gZTmNud_LI2QmtYLL2xYUNeb0CpzL8xJRusmrX5U,22421
|
43
|
+
janito/change/viewer/styling.py,sha256=vpOT0noD4Br-veZ8SuNihg_4RB55TZen-h3L7lX4IhM,3715
|
44
|
+
janito/change/viewer/themes.py,sha256=HISrK7-qR0m4dGZqFmC5loW2E1RSwAicP0mnpZvbSv0,1470
|
45
|
+
janito/clear_statement_parser/clear_statement_format.txt,sha256=_5iQylnCqgBaMVFVgx33QvEColV16oOwl1W4scu_-zc,13497
|
46
|
+
janito/clear_statement_parser/examples.txt,sha256=NnxDraTpCPZCjJjL2HvCMtA1NxBY7C0l3b5v61Y-5iY,8547
|
47
|
+
janito/clear_statement_parser/models.py,sha256=b7-lLHfQLI4q5dtaF4BVCwE9DI2xWwVQAHQqAl1c6s0,3640
|
48
|
+
janito/clear_statement_parser/parser.py,sha256=KO7Bj0pEl7iPH3FQ3knTGSLMGqKS_pXe3_eqfEHBtXw,20847
|
49
|
+
janito/cli/__init__.py,sha256=3gyMSaEAH2N4mXfZTLsHXKxXDxdhlYUeAPYQuhnOVBE,77
|
50
|
+
janito/cli/base.py,sha256=WKFSxX5FKk5Se5QUZ0X3_fAQVIWG6OEGKAXKebWiRaM,1188
|
51
|
+
janito/cli/commands.py,sha256=RP9VZYfW7vLK6xitLvp0kBQFdyEWfxHOCh91FaakRwI,1491
|
52
|
+
janito/cli/functions.py,sha256=7DSFQaxKJ7JvkA8-wPuifogGk9NvqTUqC24nU2UUbVM,4143
|
53
|
+
janito/cli/history.py,sha256=UIP_6UiGnJXmBke5hYqKXQVsOPrtKeyfBXW8vDxIoHQ,2114
|
54
|
+
janito/cli/registry.py,sha256=R1sI45YonxjMSLhAle7Dt18X_devrMsLt0ljb-rNza4,690
|
55
|
+
janito/cli/handlers/ask.py,sha256=jmD-XoP1-ldKlfNNu1WCja9NM7zJgPB3eOaglKOa7dE,829
|
56
|
+
janito/cli/handlers/demo.py,sha256=TCjN2nVUTivUDvlRxJDhx-rZnfQDR0H_6Yuteq-zkQ0,725
|
57
|
+
janito/cli/handlers/request.py,sha256=ytmmTSFzHoDPmOm-fWUEa6lkZRwL7e1VjcfJntoyvW8,1083
|
58
|
+
janito/cli/handlers/scan.py,sha256=zg4AVsFTYkNtUrLA5uCi1Gr6gxDD8PbnFP0eozPtRVw,296
|
59
|
+
janito/demo/__init__.py,sha256=vIGC6fs44yzuSK3-RgYnnanBtZwnW52ivdVbA8Yc8Lk,108
|
60
|
+
janito/demo/data.py,sha256=t208Kixcc3J6xbn43P2OSpqkpcRrrFoEXZ44oOQMcCE,415
|
61
|
+
janito/demo/mock_data.py,sha256=mBg-qF8GcRx7M40IGXGytQJOWihEUm51CJqcez1BKIk,695
|
62
|
+
janito/demo/operations.py,sha256=6QXFsrMVJqX30Y19nBD8fxTj5I6MJN4duQ2qwVG_lHY,1392
|
63
|
+
janito/demo/runner.py,sha256=Xv6v8QVlIkEnlSaNU-Jt6Yjl_NbNg_g8DMZnp8kiFGQ,2385
|
64
|
+
janito/demo/scenarios.py,sha256=xKW6cAL3tSHWP76fvbKHf2HyruhamAzHY1VaTVCkM8o,1060
|
65
|
+
janito/search_replace/README.md,sha256=Hat8Auac9dQY21Q1y3YVy97pqTwhBCaLTDZ64cI5hrg,3402
|
66
|
+
janito/search_replace/__init__.py,sha256=apCtIPg44VVzGOKxCievdYAma6nfDGMl1ky-ZBQqjUc,257
|
67
|
+
janito/search_replace/__main__.py,sha256=i0Wz387fqgiHq46QXCXpU9wnwdJYinjyY6HyoJJr_xw,520
|
68
|
+
janito/search_replace/core.py,sha256=6xy1K9aq3-rddXS2ae158cAXP_B2-QWRbrSalTkjscw,5811
|
69
|
+
janito/search_replace/parser.py,sha256=wFr5Dc7Im6oh4AcVUkpwKIc0Gf5oNBCYKT2lhexmyQg,2123
|
70
|
+
janito/search_replace/play.py,sha256=vmEqbna8CAEYk4B6Og0eETCRAYRmrBSnFTdiXopsH3M,1820
|
71
|
+
janito/search_replace/replacer.py,sha256=7MKO9PpbAMUqmnxDk1mqkZ_yWE13QwjDfvjT0uU4ypA,1556
|
72
|
+
janito/search_replace/searcher.py,sha256=M9vzcWlfQV7ZoKQ83tLQWUtW1KPP4MOlBIEBISEh_jU,13004
|
73
|
+
janito/shell/__init__.py,sha256=Rb6AKmUZpjsYT9bO5spU3_SdPoOoNInPKCl-Ei_qHUc,1272
|
74
|
+
janito/shell/bus.py,sha256=xRl2-zlJsa0KPL2aSVtVJy9xH6GLw_7tEplvMIY5eCk,1041
|
75
|
+
janito/shell/commands.py,sha256=b0J0lh2Q8HMcIldNThOnqIFAVUGAKwURThHBlP-K5tc,6797
|
76
|
+
janito/shell/handlers.py,sha256=aiS1ASNyhZNEuzUCkhqeH1_eIpBX6R3yKjYiBmG8jQI,3978
|
77
|
+
janito/shell/history.py,sha256=EMSbdW9ccHQ__WA-WM5VHjnu_ihTk-4sCpQXmi7e8iI,706
|
78
|
+
janito/shell/processor.py,sha256=ueT-x4bGM7XiblweaGD5KnnQEPc5kU9cHw8LWwHpZaI,1988
|
79
|
+
janito/tui/__init__.py,sha256=5u1TfGO99LmCtC8ilpXahkXWVJSWtBACqU_O8VtJewI,773
|
80
|
+
janito/tui/base.py,sha256=qU_8i0aQ3sWIkgnKQDUGr3bhypDzSdqYCkNGFfRbIos,692
|
81
|
+
janito/tui/flows/__init__.py,sha256=uI8MtTdIe2BWqbqcfJNnBdhxZcwc207wVjIWusGuDq4,161
|
82
|
+
janito/tui/flows/changes.py,sha256=e-zHzmY8kbrbPRyyfcecFW21mH0xnn9AXnr7gsYI82s,2153
|
83
|
+
janito/tui/flows/content.py,sha256=mAi4R0ncpC_PzjETqqH9kvy2n1xRKWLhNX5xqPgQjoM,4197
|
84
|
+
janito/tui/flows/selection.py,sha256=PcrcY8hTl3ljZOmLYA_I3r3CVRdqKfqFlRxZSgS9rDo,4031
|
85
|
+
janito/tui/screens/__init__.py,sha256=YZ8NVoXMs7c_aT1NuIBiZrkLfXCzaQ85yMKZ_Uh6wlE,69
|
86
|
+
janito/tui/screens/app.py,sha256=XEiADppmDOYkjR1p7sH6N7Zqqm7RWvXSd5_udlAdvbA,92
|
87
|
+
janito/workspace/__init__.py,sha256=vNDbXn36jSTp2OuMwLjil8eQJwXgorlBqHapOTPwnRg,260
|
88
|
+
janito/workspace/analysis.py,sha256=Ag4mbOZPyRtm2BvbP7b9c5ZuGLs0vUBWFjormKf-jRc,4088
|
89
|
+
janito/workspace/manager.py,sha256=GN3mGBO9-758vBsg9QBuEPu5N2QDkK4vZxD1dAGeBgg,1658
|
90
|
+
janito/workspace/scan.py,sha256=BCYoTjEErbH9nULwFDPCqSGOvR13H01l08ZgNq7jJiY,8804
|
91
|
+
janito-0.6.0.dist-info/METADATA,sha256=XH8_sffSXi-YqQxVeYgNoT1Yy8HtFIK26KM7AoxAiUE,4574
|
92
|
+
janito-0.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
93
|
+
janito-0.6.0.dist-info/entry_points.txt,sha256=wIo5zZxbmu4fC-ZMrsKD0T0vq7IqkOOLYhrqRGypkx4,48
|
94
|
+
janito-0.6.0.dist-info/licenses/LICENSE,sha256=xLIUXRPjtsgQml2zD1Pn4LpgiyZ49raw6jZDlO_gZdo,1062
|
95
|
+
janito-0.6.0.dist-info/RECORD,,
|