janito 0.7.0__py3-none-any.whl → 0.9.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 +5 -2
- janito/__main__.py +151 -142
- janito/callbacks.py +130 -0
- janito/cli.py +202 -0
- janito/config.py +57 -96
- janito/data/instructions.txt +6 -0
- janito/test_file.py +4 -0
- janito/token_report.py +73 -0
- janito/tools/__init__.py +10 -0
- janito/tools/decorators.py +84 -0
- janito/tools/delete_file.py +44 -0
- janito/tools/find_files.py +154 -0
- janito/tools/search_text.py +197 -0
- janito/tools/str_replace_editor/__init__.py +6 -0
- janito/tools/str_replace_editor/editor.py +43 -0
- janito/tools/str_replace_editor/handlers.py +338 -0
- janito/tools/str_replace_editor/utils.py +88 -0
- {janito-0.7.0.dist-info/licenses → janito-0.9.0.dist-info}/LICENSE +20 -20
- janito-0.9.0.dist-info/METADATA +9 -0
- janito-0.9.0.dist-info/RECORD +23 -0
- {janito-0.7.0.dist-info → janito-0.9.0.dist-info}/WHEEL +2 -1
- janito-0.9.0.dist-info/entry_points.txt +2 -0
- janito-0.9.0.dist-info/top_level.txt +1 -0
- janito/agents/__init__.py +0 -22
- janito/agents/agent.py +0 -28
- janito/agents/claudeai.py +0 -45
- janito/agents/openai.py +0 -57
- janito/agents/test.py +0 -34
- janito/change/__init__.py +0 -32
- janito/change/__main__.py +0 -0
- janito/change/analysis/__init__.py +0 -23
- janito/change/analysis/__main__.py +0 -7
- janito/change/analysis/analyze.py +0 -62
- janito/change/analysis/formatting.py +0 -78
- janito/change/analysis/options.py +0 -81
- janito/change/analysis/prompts.py +0 -90
- janito/change/analysis/view/__init__.py +0 -9
- janito/change/analysis/view/terminal.py +0 -181
- janito/change/applier/__init__.py +0 -5
- janito/change/applier/file.py +0 -58
- janito/change/applier/main.py +0 -156
- janito/change/applier/text.py +0 -247
- janito/change/applier/workspace_dir.py +0 -58
- janito/change/core.py +0 -124
- janito/change/history.py +0 -44
- janito/change/operations.py +0 -7
- janito/change/parser.py +0 -287
- janito/change/play.py +0 -54
- janito/change/preview.py +0 -82
- janito/change/prompts.py +0 -121
- janito/change/test.py +0 -0
- janito/change/validator.py +0 -269
- janito/change/viewer/__init__.py +0 -11
- janito/change/viewer/content.py +0 -66
- janito/change/viewer/diff.py +0 -43
- janito/change/viewer/panels.py +0 -533
- janito/change/viewer/styling.py +0 -114
- janito/change/viewer/themes.py +0 -55
- janito/clear_statement_parser/clear_statement_format.txt +0 -328
- janito/clear_statement_parser/examples.txt +0 -326
- janito/clear_statement_parser/models.py +0 -104
- janito/clear_statement_parser/parser.py +0 -496
- janito/cli/__init__.py +0 -2
- janito/cli/base.py +0 -30
- janito/cli/commands.py +0 -88
- janito/cli/functions.py +0 -111
- janito/cli/history.py +0 -61
- janito/cli/registry.py +0 -26
- janito/common.py +0 -80
- janito/demo/__init__.py +0 -4
- janito/demo/data.py +0 -13
- janito/demo/mock_data.py +0 -20
- janito/demo/operations.py +0 -45
- janito/demo/runner.py +0 -59
- janito/demo/scenarios.py +0 -32
- janito/prompt.py +0 -36
- janito/qa.py +0 -57
- janito/review.py +0 -13
- janito/search_replace/README.md +0 -192
- janito/search_replace/__init__.py +0 -7
- janito/search_replace/__main__.py +0 -21
- janito/search_replace/core.py +0 -120
- janito/search_replace/logger.py +0 -35
- janito/search_replace/parser.py +0 -52
- janito/search_replace/play.py +0 -61
- janito/search_replace/replacer.py +0 -36
- janito/search_replace/searcher.py +0 -411
- janito/search_replace/strategy_result.py +0 -10
- janito/shell/__init__.py +0 -38
- janito/shell/bus.py +0 -31
- janito/shell/commands.py +0 -136
- janito/shell/history.py +0 -20
- janito/shell/processor.py +0 -32
- janito/shell/prompt.py +0 -48
- janito/shell/registry.py +0 -60
- janito/tui/__init__.py +0 -21
- janito/tui/base.py +0 -22
- janito/tui/flows/__init__.py +0 -5
- janito/tui/flows/changes.py +0 -65
- janito/tui/flows/content.py +0 -128
- janito/tui/flows/selection.py +0 -117
- janito/tui/screens/__init__.py +0 -3
- janito/tui/screens/app.py +0 -1
- janito/version.py +0 -23
- janito/workspace/__init__.py +0 -6
- janito/workspace/analysis.py +0 -121
- janito/workspace/show.py +0 -141
- janito/workspace/stats.py +0 -43
- janito/workspace/types.py +0 -98
- janito/workspace/workset.py +0 -108
- janito/workspace/workspace.py +0 -114
- janito-0.7.0.dist-info/METADATA +0 -167
- janito-0.7.0.dist-info/RECORD +0 -96
- janito-0.7.0.dist-info/entry_points.txt +0 -2
janito/shell/prompt.py
DELETED
@@ -1,48 +0,0 @@
|
|
1
|
-
"""Prompt creation and configuration for Janito shell."""
|
2
|
-
from typing import Dict, Any
|
3
|
-
from pathlib import Path
|
4
|
-
from prompt_toolkit import PromptSession
|
5
|
-
from prompt_toolkit.history import FileHistory
|
6
|
-
from prompt_toolkit.completion import NestedCompleter
|
7
|
-
from rich import print as rich_print
|
8
|
-
from janito.config import config
|
9
|
-
from .registry import CommandRegistry
|
10
|
-
|
11
|
-
def create_shell_completer(registry: CommandRegistry):
|
12
|
-
"""Create command completer for shell with nested completions."""
|
13
|
-
if config.debug:
|
14
|
-
rich_print("[yellow]Creating shell completer...[/yellow]")
|
15
|
-
|
16
|
-
commands = registry.get_commands()
|
17
|
-
|
18
|
-
if config.debug:
|
19
|
-
rich_print(f"[yellow]Found {len(commands)} commands for completion[/yellow]")
|
20
|
-
|
21
|
-
# Create nested completions for commands
|
22
|
-
completions: Dict[str, Any] = {}
|
23
|
-
|
24
|
-
for cmd_name, cmd in commands.items():
|
25
|
-
if config.debug:
|
26
|
-
rich_print(f"[yellow]Setting up completion for command: {cmd_name}[/yellow]")
|
27
|
-
completions[cmd_name] = cmd.completer
|
28
|
-
|
29
|
-
if config.debug:
|
30
|
-
rich_print("[yellow]Creating nested completer from completions dictionary[/yellow]")
|
31
|
-
return NestedCompleter.from_nested_dict(completions)
|
32
|
-
|
33
|
-
def create_shell_session(registry: CommandRegistry) -> PromptSession:
|
34
|
-
"""Create and configure the shell prompt session."""
|
35
|
-
if config.debug:
|
36
|
-
rich_print("[yellow]Creating shell session...[/yellow]")
|
37
|
-
|
38
|
-
history_file = Path.home() / ".janito_history"
|
39
|
-
if config.debug:
|
40
|
-
rich_print(f"[yellow]Using history file: {history_file}[/yellow]")
|
41
|
-
|
42
|
-
completer = create_shell_completer(registry)
|
43
|
-
|
44
|
-
return PromptSession(
|
45
|
-
history=FileHistory(str(history_file)),
|
46
|
-
completer=completer,
|
47
|
-
complete_while_typing=True
|
48
|
-
)
|
janito/shell/registry.py
DELETED
@@ -1,60 +0,0 @@
|
|
1
|
-
"""Command registry and validation system for Janito shell."""
|
2
|
-
from dataclasses import dataclass
|
3
|
-
from typing import Optional, Callable, Dict, Any
|
4
|
-
from pathlib import Path
|
5
|
-
from prompt_toolkit.completion import PathCompleter
|
6
|
-
|
7
|
-
@dataclass
|
8
|
-
class Command:
|
9
|
-
"""Command definition with handler and metadata."""
|
10
|
-
name: str
|
11
|
-
description: str
|
12
|
-
usage: Optional[str]
|
13
|
-
handler: Callable[[str], None]
|
14
|
-
completer: Optional[Any] = None
|
15
|
-
|
16
|
-
class CommandRegistry:
|
17
|
-
"""Centralized command registry with validation."""
|
18
|
-
def __init__(self):
|
19
|
-
"""Initialize registry."""
|
20
|
-
if not hasattr(self, '_commands'):
|
21
|
-
self._commands = {}
|
22
|
-
|
23
|
-
|
24
|
-
def register(self, command: Command) -> None:
|
25
|
-
"""Register a command with validation."""
|
26
|
-
if command.name in self._commands:
|
27
|
-
raise ValueError(f"Command '{command.name}' already registered")
|
28
|
-
if not callable(command.handler):
|
29
|
-
raise ValueError(f"Handler for command '{command.name}' must be callable")
|
30
|
-
self._commands[command.name] = command
|
31
|
-
|
32
|
-
def register_alias(self, alias: str, command_name: str) -> None:
|
33
|
-
"""Register an alias for an existing command."""
|
34
|
-
if alias in self._commands:
|
35
|
-
raise ValueError(f"Alias '{alias}' already registered")
|
36
|
-
if command := self.get_command(command_name):
|
37
|
-
self._commands[alias] = command
|
38
|
-
else:
|
39
|
-
raise ValueError(f"Command '{command_name}' not found")
|
40
|
-
|
41
|
-
def get_command(self, name: str) -> Optional[Command]:
|
42
|
-
"""Get a command by name."""
|
43
|
-
return self._commands.get(name)
|
44
|
-
|
45
|
-
def get_commands(self) -> Dict[str, Command]:
|
46
|
-
"""Get all registered commands."""
|
47
|
-
return self._commands.copy()
|
48
|
-
|
49
|
-
def validate_command(self, command: Command) -> None:
|
50
|
-
"""Validate command properties."""
|
51
|
-
if not command.name:
|
52
|
-
raise ValueError("Command name cannot be empty")
|
53
|
-
if not command.description:
|
54
|
-
raise ValueError(f"Command '{command.name}' must have a description")
|
55
|
-
if not callable(command.handler):
|
56
|
-
raise ValueError(f"Command '{command.name}' handler must be callable")
|
57
|
-
|
58
|
-
def get_path_completer(only_directories: bool = False) -> PathCompleter:
|
59
|
-
"""Get a configured path completer."""
|
60
|
-
return PathCompleter(only_directories=only_directories)
|
janito/tui/__init__.py
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
"""Terminal User Interface package for Janito."""
|
2
|
-
from .base import BaseTuiApp
|
3
|
-
from typing import Dict, Optional
|
4
|
-
from janito.change.analysis.options import AnalysisOption
|
5
|
-
|
6
|
-
class TuiApp(BaseTuiApp):
|
7
|
-
"""Main TUI application with flow-based navigation"""
|
8
|
-
|
9
|
-
def on_mount(self) -> None:
|
10
|
-
"""Initialize appropriate flow based on input"""
|
11
|
-
if self.options:
|
12
|
-
from .flows.selection import SelectionFlow
|
13
|
-
self.push_screen(SelectionFlow(self.options))
|
14
|
-
elif self.changes:
|
15
|
-
from .flows.changes import ChangesFlow
|
16
|
-
self.push_screen(ChangesFlow(self.changes))
|
17
|
-
elif self.content:
|
18
|
-
from .flows.content import ContentFlow
|
19
|
-
self.push_screen(ContentFlow(self.content))
|
20
|
-
|
21
|
-
__all__ = ['TuiApp']
|
janito/tui/base.py
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
from textual.app import App
|
2
|
-
from typing import List, Optional, Dict
|
3
|
-
from janito.change.parser import FileChange
|
4
|
-
from janito.change.analysis.options import AnalysisOption
|
5
|
-
|
6
|
-
class BaseTuiApp(App):
|
7
|
-
"""Base class for TUI applications with common functionality"""
|
8
|
-
CSS = """
|
9
|
-
Screen {
|
10
|
-
align: center middle;
|
11
|
-
}
|
12
|
-
"""
|
13
|
-
|
14
|
-
def __init__(self,
|
15
|
-
content: Optional[str] = None,
|
16
|
-
options: Optional[Dict[str, AnalysisOption]] = None,
|
17
|
-
changes: Optional[List[FileChange]] = None):
|
18
|
-
super().__init__()
|
19
|
-
self.content = content
|
20
|
-
self.options = options
|
21
|
-
self.changes = changes
|
22
|
-
self.selected_option = None
|
janito/tui/flows/__init__.py
DELETED
janito/tui/flows/changes.py
DELETED
@@ -1,65 +0,0 @@
|
|
1
|
-
from textual.app import ComposeResult
|
2
|
-
from textual.containers import ScrollableContainer
|
3
|
-
from textual.screen import Screen
|
4
|
-
from textual.binding import Binding
|
5
|
-
from textual.widgets import Header, Footer, Static
|
6
|
-
from typing import List
|
7
|
-
from ...change.viewer.styling import format_content, create_legend_items
|
8
|
-
from ...change.viewer.panels import create_change_panel, create_new_file_panel, create_replace_panel, create_remove_file_panel
|
9
|
-
from ...change.parser import FileChange
|
10
|
-
|
11
|
-
class ChangesFlow(Screen):
|
12
|
-
"""Screen for changes preview flow"""
|
13
|
-
CSS = """
|
14
|
-
ScrollableContainer {
|
15
|
-
width: 100%;
|
16
|
-
height: 100%;
|
17
|
-
border: solid green;
|
18
|
-
background: $surface;
|
19
|
-
color: $text;
|
20
|
-
padding: 1;
|
21
|
-
}
|
22
|
-
|
23
|
-
Container.panel {
|
24
|
-
margin: 1;
|
25
|
-
padding: 1;
|
26
|
-
border: solid $primary;
|
27
|
-
width: 100%;
|
28
|
-
}
|
29
|
-
"""
|
30
|
-
|
31
|
-
BINDINGS = [
|
32
|
-
Binding("q", "quit", "Quit"),
|
33
|
-
Binding("escape", "quit", "Quit"),
|
34
|
-
Binding("up", "previous", "Previous"),
|
35
|
-
Binding("down", "next", "Next"),
|
36
|
-
]
|
37
|
-
|
38
|
-
def __init__(self, changes: List[FileChange]):
|
39
|
-
super().__init__()
|
40
|
-
self.changes = changes
|
41
|
-
self.current_index = 0
|
42
|
-
|
43
|
-
def compose(self) -> ComposeResult:
|
44
|
-
yield Header()
|
45
|
-
with ScrollableContainer():
|
46
|
-
for change in self.changes:
|
47
|
-
if change.operation == 'create_file':
|
48
|
-
yield Static(create_new_file_panel(change.name, change.content))
|
49
|
-
elif change.operation == 'replace_file':
|
50
|
-
yield Static(create_replace_panel(change.name, change))
|
51
|
-
elif change.operation == 'remove_file':
|
52
|
-
yield Static(create_remove_file_panel(change.name))
|
53
|
-
elif change.operation == 'modify_file':
|
54
|
-
for mod in change.text_changes:
|
55
|
-
yield Static(create_change_panel(mod.search_content, mod.replace_content, change.description, 1))
|
56
|
-
yield Footer()
|
57
|
-
|
58
|
-
def action_quit(self):
|
59
|
-
self.app.exit()
|
60
|
-
|
61
|
-
def action_previous(self):
|
62
|
-
self.scroll_up()
|
63
|
-
|
64
|
-
def action_next(self):
|
65
|
-
self.scroll_down()
|
janito/tui/flows/content.py
DELETED
@@ -1,128 +0,0 @@
|
|
1
|
-
from textual.app import ComposeResult
|
2
|
-
from textual.containers import ScrollableContainer
|
3
|
-
from textual.screen import Screen
|
4
|
-
from textual.binding import Binding
|
5
|
-
from textual.widgets import Header, Footer, Static
|
6
|
-
from rich.panel import Panel
|
7
|
-
from rich.text import Text
|
8
|
-
from rich import box
|
9
|
-
from pathlib import Path
|
10
|
-
from typing import Dict, List
|
11
|
-
|
12
|
-
class ContentFlow(Screen):
|
13
|
-
"""Screen for content viewing flow with unified display format"""
|
14
|
-
CSS = """
|
15
|
-
ScrollableContainer {
|
16
|
-
width: 100%;
|
17
|
-
height: 100%;
|
18
|
-
border: solid green;
|
19
|
-
background: $surface;
|
20
|
-
color: $text;
|
21
|
-
padding: 1;
|
22
|
-
}
|
23
|
-
|
24
|
-
Container.panel {
|
25
|
-
margin: 1;
|
26
|
-
padding: 1;
|
27
|
-
border: solid $primary;
|
28
|
-
width: 100%;
|
29
|
-
}
|
30
|
-
"""
|
31
|
-
|
32
|
-
BINDINGS = [
|
33
|
-
Binding("q", "quit", "Quit"),
|
34
|
-
Binding("escape", "quit", "Quit"),
|
35
|
-
Binding("up", "previous", "Previous"),
|
36
|
-
Binding("down", "next", "Next"),
|
37
|
-
]
|
38
|
-
|
39
|
-
def __init__(self, content: str):
|
40
|
-
super().__init__()
|
41
|
-
self.content = content
|
42
|
-
self.files_by_type = self._organize_content()
|
43
|
-
|
44
|
-
def _organize_content(self) -> Dict[str, List[str]]:
|
45
|
-
"""Organize content into file groups"""
|
46
|
-
files = {
|
47
|
-
'Modified': [],
|
48
|
-
'New': [],
|
49
|
-
'Deleted': []
|
50
|
-
}
|
51
|
-
|
52
|
-
# Parse content to extract file information
|
53
|
-
for line in self.content.split('\n'):
|
54
|
-
if line.strip().startswith('- '):
|
55
|
-
file_path = line[2:].strip()
|
56
|
-
if '(new)' in file_path:
|
57
|
-
files['New'].append(file_path)
|
58
|
-
elif '(removed)' in file_path:
|
59
|
-
files['Deleted'].append(file_path)
|
60
|
-
else:
|
61
|
-
files['Modified'].append(file_path)
|
62
|
-
|
63
|
-
return files
|
64
|
-
|
65
|
-
def _format_files_group(self, group_name: str, files: List[str], style: str) -> Text:
|
66
|
-
"""Format a group of files with consistent styling"""
|
67
|
-
content = Text()
|
68
|
-
if files:
|
69
|
-
content.append(Text(f"\n─── {group_name} ───\n", style="cyan"))
|
70
|
-
|
71
|
-
# Group files by directory
|
72
|
-
files_by_dir = {}
|
73
|
-
for file_path in files:
|
74
|
-
clean_path = file_path.split(' (')[0]
|
75
|
-
path = Path(clean_path)
|
76
|
-
dir_path = str(path.parent)
|
77
|
-
if dir_path not in files_by_dir:
|
78
|
-
files_by_dir[dir_path] = []
|
79
|
-
files_by_dir[dir_path].append(path)
|
80
|
-
|
81
|
-
# Display files by directory
|
82
|
-
for dir_path, paths in sorted(files_by_dir.items()):
|
83
|
-
first_in_dir = True
|
84
|
-
for path in sorted(paths):
|
85
|
-
if first_in_dir:
|
86
|
-
display_path = dir_path
|
87
|
-
else:
|
88
|
-
pad_left = (len(dir_path) - 3) // 2
|
89
|
-
pad_right = len(dir_path) - 3 - pad_left
|
90
|
-
display_path = " " * pad_left + "..." + " " * pad_right
|
91
|
-
|
92
|
-
content.append(Text(f"• {display_path}/{path.name}\n", style=style))
|
93
|
-
first_in_dir = False
|
94
|
-
|
95
|
-
return content
|
96
|
-
|
97
|
-
def compose(self) -> ComposeResult:
|
98
|
-
yield Header()
|
99
|
-
with ScrollableContainer():
|
100
|
-
# Format content with consistent styling
|
101
|
-
content = Text()
|
102
|
-
|
103
|
-
# Add each file group with appropriate styling
|
104
|
-
content.append(self._format_files_group("Modified", self.files_by_type['Modified'], "yellow"))
|
105
|
-
content.append(self._format_files_group("New", self.files_by_type['New'], "green"))
|
106
|
-
content.append(self._format_files_group("Deleted", self.files_by_type['Deleted'], "red"))
|
107
|
-
|
108
|
-
# Create panel with formatted content
|
109
|
-
panel = Panel(
|
110
|
-
content,
|
111
|
-
box=box.ROUNDED,
|
112
|
-
border_style="cyan",
|
113
|
-
title="Content Changes",
|
114
|
-
title_align="center",
|
115
|
-
padding=(1, 2)
|
116
|
-
)
|
117
|
-
|
118
|
-
yield Static(panel)
|
119
|
-
yield Footer()
|
120
|
-
|
121
|
-
def action_quit(self):
|
122
|
-
self.app.exit()
|
123
|
-
|
124
|
-
def action_previous(self):
|
125
|
-
self.scroll_up()
|
126
|
-
|
127
|
-
def action_next(self):
|
128
|
-
self.scroll_down()
|
janito/tui/flows/selection.py
DELETED
@@ -1,117 +0,0 @@
|
|
1
|
-
from textual.app import ComposeResult
|
2
|
-
from textual.containers import Container
|
3
|
-
from textual.screen import Screen
|
4
|
-
from textual.binding import Binding
|
5
|
-
from textual.widgets import Header, Footer, Static
|
6
|
-
from typing import Dict, Optional
|
7
|
-
from rich.panel import Panel
|
8
|
-
from rich import box
|
9
|
-
from janito.change.analysis.options import AnalysisOption
|
10
|
-
from janito.agents import agent
|
11
|
-
from janito.common import progress_send_message
|
12
|
-
from janito.change.parser import parse_response
|
13
|
-
from .changes import ChangesFlow
|
14
|
-
|
15
|
-
class SelectionFlow(Screen):
|
16
|
-
"""Selection screen with direct navigation to changes preview"""
|
17
|
-
|
18
|
-
CSS = """
|
19
|
-
#options-container {
|
20
|
-
layout: horizontal;
|
21
|
-
height: 100%;
|
22
|
-
margin: 1;
|
23
|
-
align: center middle;
|
24
|
-
}
|
25
|
-
|
26
|
-
.option-panel {
|
27
|
-
width: 1fr;
|
28
|
-
height: 100%;
|
29
|
-
border: solid $primary;
|
30
|
-
margin: 0 1;
|
31
|
-
padding: 1;
|
32
|
-
}
|
33
|
-
|
34
|
-
.option-panel.selected {
|
35
|
-
border: double $secondary;
|
36
|
-
background: $boost;
|
37
|
-
}
|
38
|
-
"""
|
39
|
-
|
40
|
-
BINDINGS = [
|
41
|
-
Binding("left", "previous", "Previous"),
|
42
|
-
Binding("right", "next", "Next"),
|
43
|
-
Binding("enter", "select", "Select"),
|
44
|
-
Binding("escape", "quit", "Quit"),
|
45
|
-
]
|
46
|
-
|
47
|
-
def __init__(self, options: Dict[str, AnalysisOption]):
|
48
|
-
super().__init__()
|
49
|
-
self.options = options
|
50
|
-
self.current_index = 0
|
51
|
-
self.panels = []
|
52
|
-
|
53
|
-
def compose(self) -> ComposeResult:
|
54
|
-
yield Header()
|
55
|
-
with Container(id="options-container"):
|
56
|
-
for letter, option in self.options.items():
|
57
|
-
panel = Static(self._format_option(letter, option), classes="option-panel")
|
58
|
-
self.panels.append(panel)
|
59
|
-
yield panel
|
60
|
-
yield Footer()
|
61
|
-
# Set initial selection
|
62
|
-
if self.panels:
|
63
|
-
self.panels[0].add_class("selected")
|
64
|
-
|
65
|
-
def _format_option(self, letter: str, option: AnalysisOption) -> str:
|
66
|
-
"""Format option content"""
|
67
|
-
content = [f"Option {letter}: {option.summary}\n"]
|
68
|
-
content.append("\nDescription:")
|
69
|
-
for item in option.description_items:
|
70
|
-
content.append(f"• {item}")
|
71
|
-
content.append("\nAffected files:")
|
72
|
-
for file in option.affected_files:
|
73
|
-
content.append(f"• {file}")
|
74
|
-
return "\n".join(content)
|
75
|
-
|
76
|
-
def action_previous(self) -> None:
|
77
|
-
"""Handle left arrow key"""
|
78
|
-
if self.panels:
|
79
|
-
self.panels[self.current_index].remove_class("selected")
|
80
|
-
self.current_index = (self.current_index - 1) % len(self.panels)
|
81
|
-
self.panels[self.current_index].add_class("selected")
|
82
|
-
|
83
|
-
def action_next(self) -> None:
|
84
|
-
"""Handle right arrow key"""
|
85
|
-
if self.panels:
|
86
|
-
self.panels[self.current_index].remove_class("selected")
|
87
|
-
self.current_index = (self.current_index + 1) % len(self.panels)
|
88
|
-
self.panels[self.current_index].add_class("selected")
|
89
|
-
|
90
|
-
def action_select(self) -> None:
|
91
|
-
"""Handle enter key - request changes and show preview"""
|
92
|
-
if self.panels:
|
93
|
-
letter = list(self.options.keys())[self.current_index]
|
94
|
-
option = self.options[letter]
|
95
|
-
|
96
|
-
# Build and send change request
|
97
|
-
from janito.change import build_change_request_prompt
|
98
|
-
from janito.workspace import collect_files_content
|
99
|
-
|
100
|
-
files_content = collect_files_content([option.get_clean_path(f) for f in option.affected_files])
|
101
|
-
prompt = build_change_request_prompt(option.format_option_text(), "", files_content)
|
102
|
-
response = progress_send_message(prompt)
|
103
|
-
|
104
|
-
if response:
|
105
|
-
changes = parse_response(response)
|
106
|
-
if changes:
|
107
|
-
# Show changes preview
|
108
|
-
self.app.push_screen(ChangesFlow(changes))
|
109
|
-
return
|
110
|
-
|
111
|
-
self.app.selected_option = option
|
112
|
-
self.app.exit()
|
113
|
-
|
114
|
-
def action_quit(self) -> None:
|
115
|
-
"""Handle escape key"""
|
116
|
-
self.app.selected_option = None
|
117
|
-
self.app.exit()
|
janito/tui/screens/__init__.py
DELETED
janito/tui/screens/app.py
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
# This file is intentionally left empty as functionality is moved to base.py and __init__.py
|
janito/version.py
DELETED
@@ -1,23 +0,0 @@
|
|
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"
|
janito/workspace/__init__.py
DELETED
janito/workspace/analysis.py
DELETED
@@ -1,121 +0,0 @@
|
|
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 workset analysis in panel
|
108
|
-
console.print("\n")
|
109
|
-
console.print(Panel(
|
110
|
-
content,
|
111
|
-
title="[bold blue]Workset 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}"
|