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.
- janito/__init__.py +1 -1
- janito/__main__.py +102 -326
- janito/agents/__init__.py +16 -0
- janito/agents/agent.py +21 -0
- janito/{claude.py → agents/claudeai.py} +13 -17
- janito/agents/openai.py +53 -0
- janito/agents/test.py +34 -0
- janito/change/__init__.py +32 -0
- janito/change/__main__.py +0 -0
- janito/change/analysis/__init__.py +23 -0
- 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/change/analysis/prompts.py +98 -0
- 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/change/history.py +44 -0
- 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/change/viewer/__init__.py +11 -0
- janito/change/viewer/content.py +66 -0
- janito/change/viewer/diff.py +43 -0
- janito/change/viewer/pager.py +56 -0
- janito/change/viewer/panels.py +555 -0
- janito/change/viewer/styling.py +103 -0
- janito/change/viewer/themes.py +55 -0
- 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/__init__.py +2 -0
- janito/cli/base.py +30 -0
- janito/cli/commands.py +45 -0
- janito/cli/functions.py +111 -0
- 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/cli/registry.py +26 -0
- janito/common.py +41 -10
- 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 -65
- janito/qa.py +8 -5
- janito/review.py +13 -0
- 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.4.0.dist-info → janito-0.6.0.dist-info}/WHEEL +1 -1
- janito/analysis.py +0 -281
- janito/changeapplier.py +0 -436
- janito/changeviewer.py +0 -350
- janito/console.py +0 -330
- janito/contentchange.py +0 -84
- janito/contextparser.py +0 -113
- janito/fileparser.py +0 -125
- janito/scan.py +0 -137
- janito-0.4.0.dist-info/METADATA +0 -164
- janito-0.4.0.dist-info/RECORD +0 -21
- {janito-0.4.0.dist-info → janito-0.6.0.dist-info}/entry_points.txt +0 -0
- {janito-0.4.0.dist-info → janito-0.6.0.dist-info}/licenses/LICENSE +0 -0
janito/changeviewer.py
DELETED
@@ -1,350 +0,0 @@
|
|
1
|
-
from pathlib import Path
|
2
|
-
from rich.console import Console
|
3
|
-
from rich.text import Text
|
4
|
-
from rich.panel import Panel
|
5
|
-
from rich.table import Table
|
6
|
-
from rich.rule import Rule # Add this import
|
7
|
-
from typing import List, Optional, Dict
|
8
|
-
from rich import box
|
9
|
-
from janito.fileparser import FileChange
|
10
|
-
from janito.analysis import AnalysisOption # Add this import
|
11
|
-
from rich.columns import Columns # Add this import at the top with other imports
|
12
|
-
|
13
|
-
MIN_PANEL_WIDTH = 40 # Minimum width for each panel
|
14
|
-
|
15
|
-
|
16
|
-
def format_sequence_preview(lines: List[str]) -> Text:
|
17
|
-
"""Format a sequence of prefixed lines into rich text with colors"""
|
18
|
-
text = Text()
|
19
|
-
last_was_empty = False
|
20
|
-
|
21
|
-
for line in lines:
|
22
|
-
if not line:
|
23
|
-
# Preserve empty lines but don't duplicate them
|
24
|
-
if not last_was_empty:
|
25
|
-
text.append("\n")
|
26
|
-
last_was_empty = True
|
27
|
-
continue
|
28
|
-
|
29
|
-
last_was_empty = False
|
30
|
-
prefix = line[0] if line[0] in ('=', '>', '<') else ' '
|
31
|
-
content = line[1:] if line[0] in ('=', '>', '<') else line
|
32
|
-
|
33
|
-
if prefix == '=':
|
34
|
-
text.append(f" {content}\n", style="dim")
|
35
|
-
elif prefix == '>':
|
36
|
-
text.append(f"+{content}\n", style="green")
|
37
|
-
elif prefix == '<':
|
38
|
-
text.append(f"-{content}\n", style="red")
|
39
|
-
else:
|
40
|
-
text.append(f" {content}\n", style="yellow dim")
|
41
|
-
|
42
|
-
return text
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
def show_changes_legend(console: Console) -> None:
|
50
|
-
"""Display a legend explaining the colors and symbols used in change previews in a horizontal layout"""
|
51
|
-
# Create a list of colored text objects
|
52
|
-
legend_items = [
|
53
|
-
Text("Unchanged", style="#98C379"),
|
54
|
-
Text(" • ", style="dim"),
|
55
|
-
Text("Removed", style="#E06C75"),
|
56
|
-
Text(" • ", style="dim"),
|
57
|
-
Text("Relocated", style="#61AFEF"),
|
58
|
-
Text(" • ", style="dim"),
|
59
|
-
Text("New", style="#C678DD")
|
60
|
-
]
|
61
|
-
|
62
|
-
# Combine all items into a single text object
|
63
|
-
legend_text = Text()
|
64
|
-
for item in legend_items:
|
65
|
-
legend_text.append_text(item)
|
66
|
-
|
67
|
-
# Create a simple panel with the horizontal legend
|
68
|
-
legend_panel = Panel(
|
69
|
-
legend_text,
|
70
|
-
title="Changes Legend",
|
71
|
-
title_align="left",
|
72
|
-
border_style="white",
|
73
|
-
box=box.ROUNDED,
|
74
|
-
padding=(0, 1)
|
75
|
-
)
|
76
|
-
|
77
|
-
# Center the legend panel horizontally
|
78
|
-
console.print(Columns([legend_panel], align="center"))
|
79
|
-
console.print() # Add extra line for spacing
|
80
|
-
|
81
|
-
|
82
|
-
def show_change_preview(console: Console, filepath: Path, change: FileChange) -> None:
|
83
|
-
"""Display a preview of changes for a single file with side-by-side comparison"""
|
84
|
-
# Show changes legend first
|
85
|
-
show_changes_legend(console)
|
86
|
-
|
87
|
-
# Create main file panel content
|
88
|
-
main_content = []
|
89
|
-
|
90
|
-
# Handle new file preview
|
91
|
-
if change.is_new_file:
|
92
|
-
new_file_panel = Panel(
|
93
|
-
Text(change.content),
|
94
|
-
title="New File Content",
|
95
|
-
title_align="left",
|
96
|
-
border_style="green",
|
97
|
-
box=box.ROUNDED
|
98
|
-
)
|
99
|
-
main_content.append(new_file_panel)
|
100
|
-
|
101
|
-
# Create and display main file panel
|
102
|
-
file_panel = Panel(
|
103
|
-
Columns(main_content),
|
104
|
-
title=str(filepath),
|
105
|
-
title_align="left",
|
106
|
-
border_style="white",
|
107
|
-
box=box.ROUNDED )
|
108
|
-
return
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
# For modifications, create side-by-side comparison for each change
|
113
|
-
for i, (search, replace, description) in enumerate(change.search_blocks, 1):
|
114
|
-
# Show change header with description
|
115
|
-
header = f"Change {i}"
|
116
|
-
if description:
|
117
|
-
header += f": {description}"
|
118
|
-
|
119
|
-
if replace is None:
|
120
|
-
# For deletions, show single panel with content to be deleted
|
121
|
-
change_panel = Panel(
|
122
|
-
Text(search, style="red"),
|
123
|
-
title=f"Content to Delete{' - ' + description if description else ''}",
|
124
|
-
title_align="left",
|
125
|
-
border_style="#E06C75", # Brighter red
|
126
|
-
box=box.ROUNDED
|
127
|
-
)
|
128
|
-
main_content.append(change_panel)
|
129
|
-
else:
|
130
|
-
# For replacements, show side-by-side panels
|
131
|
-
|
132
|
-
|
133
|
-
# Find common content between search and replace
|
134
|
-
search_lines = search.splitlines()
|
135
|
-
replace_lines = replace.splitlines()
|
136
|
-
|
137
|
-
# Find common lines from top
|
138
|
-
common_top = []
|
139
|
-
for s, r in zip(search_lines, replace_lines):
|
140
|
-
if s == r:
|
141
|
-
common_top.append(s)
|
142
|
-
else:
|
143
|
-
break
|
144
|
-
|
145
|
-
# Find common lines from bottom
|
146
|
-
search_remaining = search_lines[len(common_top):]
|
147
|
-
replace_remaining = replace_lines[len(common_top):]
|
148
|
-
|
149
|
-
common_bottom = []
|
150
|
-
for s, r in zip(reversed(search_remaining), reversed(replace_remaining)):
|
151
|
-
if s == r:
|
152
|
-
common_bottom.insert(0, s)
|
153
|
-
else:
|
154
|
-
break
|
155
|
-
|
156
|
-
# Get the unique middle sections
|
157
|
-
search_middle = search_remaining[:-len(common_bottom)] if common_bottom else search_remaining
|
158
|
-
replace_middle = replace_remaining[:-len(common_bottom)] if common_bottom else replace_remaining
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
# Format content with highlighting using consistent colors and line numbers
|
164
|
-
|
165
|
-
|
166
|
-
def format_content(lines: List[str], is_search: bool) -> Text:
|
167
|
-
text = Text()
|
168
|
-
|
169
|
-
COLORS = {
|
170
|
-
'unchanged': '#98C379', # Brighter green for unchanged lines
|
171
|
-
'removed': '#E06C75', # Clearer red for removed lines
|
172
|
-
'added': '#61AFEF', # Bright blue for added lines
|
173
|
-
'new': '#C678DD', # Purple for completely new lines
|
174
|
-
'relocated': '#61AFEF' # Use same blue for relocated lines
|
175
|
-
}
|
176
|
-
|
177
|
-
# Create sets of lines for comparison
|
178
|
-
search_set = set(search_lines)
|
179
|
-
replace_set = set(replace_lines)
|
180
|
-
common_lines = search_set & replace_set
|
181
|
-
new_lines = replace_set - search_set
|
182
|
-
relocated_lines = common_lines - set(common_top) - set(common_bottom)
|
183
|
-
|
184
|
-
def add_line(line: str, style: str, prefix: str = " "):
|
185
|
-
# Special handling for icons
|
186
|
-
if style == COLORS['relocated']:
|
187
|
-
prefix = "⇄"
|
188
|
-
elif style == COLORS['removed'] and prefix == "-":
|
189
|
-
prefix = "✕"
|
190
|
-
elif style == COLORS['new'] or (style == COLORS['added'] and prefix == "+"):
|
191
|
-
prefix = "✚"
|
192
|
-
text.append(prefix, style=style)
|
193
|
-
text.append(f" {line}\n", style=style)
|
194
|
-
|
195
|
-
# Format common top section
|
196
|
-
for line in common_top:
|
197
|
-
add_line(line, COLORS['unchanged'], "=")
|
198
|
-
|
199
|
-
# Format changed middle section
|
200
|
-
for line in (search_middle if is_search else replace_middle):
|
201
|
-
if line in relocated_lines:
|
202
|
-
add_line(line, COLORS['relocated'], "⇄")
|
203
|
-
elif not is_search and line in new_lines:
|
204
|
-
add_line(line, COLORS['new'], "+")
|
205
|
-
else:
|
206
|
-
style = COLORS['removed'] if is_search else COLORS['added']
|
207
|
-
prefix = "✕" if is_search else "+"
|
208
|
-
add_line(line, style, prefix)
|
209
|
-
|
210
|
-
# Format common bottom section
|
211
|
-
for line in common_bottom:
|
212
|
-
add_line(line, COLORS['unchanged'], "=")
|
213
|
-
|
214
|
-
return text
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
# Create panels for old and new content without width constraints
|
219
|
-
old_panel = Panel(
|
220
|
-
format_content(search_lines, True),
|
221
|
-
title="Current Content",
|
222
|
-
title_align="left",
|
223
|
-
border_style="#E06C75",
|
224
|
-
box=box.ROUNDED
|
225
|
-
)
|
226
|
-
|
227
|
-
new_panel = Panel(
|
228
|
-
format_content(replace_lines, False),
|
229
|
-
title="New Content",
|
230
|
-
title_align="left",
|
231
|
-
border_style="#61AFEF",
|
232
|
-
box=box.ROUNDED
|
233
|
-
)
|
234
|
-
|
235
|
-
# Add change panels to main content with auto-fitting columns
|
236
|
-
change_columns = Columns([old_panel, new_panel], equal=True, align="center")
|
237
|
-
change_panel = Panel(
|
238
|
-
change_columns,
|
239
|
-
title=header,
|
240
|
-
title_align="left",
|
241
|
-
border_style="cyan",
|
242
|
-
box=box.ROUNDED
|
243
|
-
)
|
244
|
-
main_content.append(change_panel)
|
245
|
-
|
246
|
-
# Create and display main file panel
|
247
|
-
file_panel = Panel(
|
248
|
-
Columns(main_content, align="center"),
|
249
|
-
title=f"Modifying {filepath}",
|
250
|
-
title_align="left",
|
251
|
-
border_style="white",
|
252
|
-
box=box.ROUNDED
|
253
|
-
)
|
254
|
-
console.print(file_panel)
|
255
|
-
console.print()
|
256
|
-
|
257
|
-
# Remove or comment out the unused unified panel code since we're using direct column display
|
258
|
-
|
259
|
-
def preview_all_changes(console: Console, changes: Dict[Path, FileChange]) -> None:
|
260
|
-
"""Show preview for all file changes"""
|
261
|
-
console.print("\n[bold blue]Change Preview[/bold blue]")
|
262
|
-
|
263
|
-
for filepath, change in changes.items():
|
264
|
-
show_change_preview(console, filepath, change)
|
265
|
-
|
266
|
-
|
267
|
-
def _display_options(options: Dict[str, AnalysisOption]) -> None:
|
268
|
-
"""Display available options in a centered, responsive layout with consistent spacing."""
|
269
|
-
console = Console()
|
270
|
-
|
271
|
-
# Display centered header with decorative rule
|
272
|
-
console.print()
|
273
|
-
console.print(Rule(" Available Options ", style="bold cyan", align="center"))
|
274
|
-
console.print()
|
275
|
-
|
276
|
-
# Safety check for empty options
|
277
|
-
if not options:
|
278
|
-
console.print(Panel("[yellow]No options available[/yellow]", border_style="yellow"))
|
279
|
-
return
|
280
|
-
|
281
|
-
# Calculate optimal layout dimensions based on terminal width
|
282
|
-
terminal_width = console.width or 100
|
283
|
-
panel_padding = (1, 2) # Consistent padding for all panels
|
284
|
-
available_width = terminal_width - 4 # Account for margins
|
285
|
-
|
286
|
-
# Determine optimal panel width and number of columns
|
287
|
-
min_panels_per_row = 1
|
288
|
-
max_panels_per_row = 3
|
289
|
-
optimal_panel_width = min(
|
290
|
-
available_width // max_panels_per_row,
|
291
|
-
available_width // min_panels_per_row
|
292
|
-
)
|
293
|
-
|
294
|
-
if optimal_panel_width < MIN_PANEL_WIDTH:
|
295
|
-
optimal_panel_width = MIN_PANEL_WIDTH
|
296
|
-
|
297
|
-
# Create panels with consistent styling and spacing
|
298
|
-
panels = []
|
299
|
-
for letter, option in options.items():
|
300
|
-
# Build content with consistent formatting
|
301
|
-
content = Text()
|
302
|
-
|
303
|
-
# Add description section
|
304
|
-
content.append("Description:\n", style="bold cyan")
|
305
|
-
for item in option.description_items:
|
306
|
-
content.append(f"• {item}\n", style="white")
|
307
|
-
content.append("\n")
|
308
|
-
|
309
|
-
# Add affected files section if present
|
310
|
-
if option.affected_files:
|
311
|
-
content.append("Affected files:\n", style="bold cyan")
|
312
|
-
for file in option.affected_files:
|
313
|
-
content.append(f"• {file}\n", style="yellow")
|
314
|
-
|
315
|
-
# Create panel with consistent styling
|
316
|
-
panel = Panel(
|
317
|
-
content,
|
318
|
-
box=box.ROUNDED,
|
319
|
-
border_style="cyan",
|
320
|
-
title=f"Option {letter}: {option.summary}",
|
321
|
-
title_align="center",
|
322
|
-
padding=panel_padding,
|
323
|
-
width=optimal_panel_width
|
324
|
-
)
|
325
|
-
panels.append(panel)
|
326
|
-
|
327
|
-
# Calculate optimal number of columns based on available width
|
328
|
-
num_columns = max(1, min(
|
329
|
-
len(panels), # Don't exceed number of panels
|
330
|
-
available_width // optimal_panel_width, # Width-based limit
|
331
|
-
max_panels_per_row # Maximum columns limit
|
332
|
-
))
|
333
|
-
|
334
|
-
# Create a centered container panel for all options
|
335
|
-
container = Panel(
|
336
|
-
Columns(
|
337
|
-
panels,
|
338
|
-
num_columns=num_columns,
|
339
|
-
equal=True,
|
340
|
-
align="center",
|
341
|
-
padding=(0, 2) # Consistent spacing between columns
|
342
|
-
),
|
343
|
-
box=box.SIMPLE,
|
344
|
-
padding=(1, 4), # Add padding around the columns for better centering
|
345
|
-
width=min(terminal_width - 4, num_columns * optimal_panel_width + (num_columns - 1) * 4)
|
346
|
-
)
|
347
|
-
|
348
|
-
# Display the centered container
|
349
|
-
console.print(Columns([container], align="center"))
|
350
|
-
console.print()
|
janito/console.py
DELETED
@@ -1,330 +0,0 @@
|
|
1
|
-
from prompt_toolkit import PromptSession
|
2
|
-
from prompt_toolkit.history import FileHistory
|
3
|
-
from prompt_toolkit.completion import WordCompleter, PathCompleter
|
4
|
-
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
|
5
|
-
from prompt_toolkit.formatted_text import HTML
|
6
|
-
from pathlib import Path
|
7
|
-
from rich.console import Console
|
8
|
-
from janito.claude import ClaudeAPIAgent
|
9
|
-
from janito.prompts import SYSTEM_PROMPT
|
10
|
-
from janito.analysis import build_request_analysis_prompt
|
11
|
-
from janito.scan import collect_files_content
|
12
|
-
from janito.__main__ import handle_option_selection
|
13
|
-
from rich.panel import Panel
|
14
|
-
from rich.align import Align
|
15
|
-
from janito.common import progress_send_message
|
16
|
-
from rich.table import Table
|
17
|
-
from rich.layout import Layout
|
18
|
-
from rich.live import Live
|
19
|
-
from typing import List, Optional
|
20
|
-
import shutil
|
21
|
-
|
22
|
-
def create_completer(workdir: Path) -> WordCompleter:
|
23
|
-
"""Create command completer with common commands and paths"""
|
24
|
-
commands = [
|
25
|
-
'ask', 'request', 'help', 'exit', 'quit',
|
26
|
-
'--raw', '--verbose', '--debug', '--test'
|
27
|
-
]
|
28
|
-
return WordCompleter(commands, ignore_case=True)
|
29
|
-
|
30
|
-
def format_prompt(workdir: Path) -> HTML:
|
31
|
-
"""Format the prompt with current directory"""
|
32
|
-
cwd = workdir.name
|
33
|
-
return HTML(f'<ansigreen>janito</ansigreen> <ansiblue>{cwd}</ansiblue>> ')
|
34
|
-
|
35
|
-
def display_help() -> None:
|
36
|
-
"""Display available commands, options and their descriptions"""
|
37
|
-
console = Console()
|
38
|
-
|
39
|
-
layout = Layout()
|
40
|
-
layout.split_column(
|
41
|
-
Layout(name="header"),
|
42
|
-
Layout(name="commands"),
|
43
|
-
Layout(name="options"),
|
44
|
-
Layout(name="examples")
|
45
|
-
)
|
46
|
-
|
47
|
-
# Header
|
48
|
-
header_table = Table(box=None, show_header=False)
|
49
|
-
header_table.add_row("[bold cyan]Janito Console Help[/bold cyan]")
|
50
|
-
header_table.add_row("[dim]Your AI-powered software development buddy[/dim]")
|
51
|
-
|
52
|
-
# Commands table
|
53
|
-
commands_table = Table(title="Available Commands", box=None)
|
54
|
-
commands_table.add_column("Command", style="cyan", width=20)
|
55
|
-
commands_table.add_column("Description", style="white")
|
56
|
-
|
57
|
-
|
58
|
-
commands_table.add_row(
|
59
|
-
"/ask <text> (/a)",
|
60
|
-
"Ask a question about the codebase without making changes"
|
61
|
-
)
|
62
|
-
commands_table.add_row(
|
63
|
-
"<text> or /request <text> (/r)",
|
64
|
-
"Request code modifications or improvements"
|
65
|
-
)
|
66
|
-
commands_table.add_row(
|
67
|
-
"/help (/h)",
|
68
|
-
"Display this help message"
|
69
|
-
)
|
70
|
-
commands_table.add_row(
|
71
|
-
"/quit or /exit (/q)",
|
72
|
-
"Exit the console session"
|
73
|
-
)
|
74
|
-
|
75
|
-
# Options table
|
76
|
-
options_table = Table(title="Common Options", box=None)
|
77
|
-
options_table.add_column("Option", style="cyan", width=20)
|
78
|
-
options_table.add_column("Description", style="white")
|
79
|
-
|
80
|
-
options_table.add_row(
|
81
|
-
"--raw",
|
82
|
-
"Display raw response without formatting"
|
83
|
-
)
|
84
|
-
options_table.add_row(
|
85
|
-
"--verbose",
|
86
|
-
"Show additional information during execution"
|
87
|
-
)
|
88
|
-
options_table.add_row(
|
89
|
-
"--debug",
|
90
|
-
"Display detailed debug information"
|
91
|
-
)
|
92
|
-
options_table.add_row(
|
93
|
-
"--test <cmd>",
|
94
|
-
"Run specified test command before applying changes"
|
95
|
-
)
|
96
|
-
|
97
|
-
# Examples panel
|
98
|
-
examples = Panel(
|
99
|
-
"\n".join([
|
100
|
-
"[dim]Basic Commands:[/dim]",
|
101
|
-
" ask how does the error handling work?",
|
102
|
-
" request add input validation to user functions",
|
103
|
-
"",
|
104
|
-
"[dim]Using Options:[/dim]",
|
105
|
-
" request update tests --verbose",
|
106
|
-
" ask explain auth flow --raw",
|
107
|
-
" request optimize code --test 'pytest'",
|
108
|
-
"",
|
109
|
-
"[dim]Complex Examples:[/dim]",
|
110
|
-
" request refactor login function --verbose --test 'python -m unittest'",
|
111
|
-
" ask code structure --raw --debug"
|
112
|
-
]),
|
113
|
-
title="Examples",
|
114
|
-
border_style="blue"
|
115
|
-
)
|
116
|
-
|
117
|
-
# Update layout
|
118
|
-
layout["header"].update(header_table)
|
119
|
-
layout["commands"].update(commands_table)
|
120
|
-
layout["options"].update(options_table)
|
121
|
-
layout["examples"].update(examples)
|
122
|
-
|
123
|
-
console.print(layout)
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
def process_command(command: str, args: str, workdir: Path, include: List[Path], claude: ClaudeAPIAgent) -> None:
|
128
|
-
"""Process console commands using CLI functions for consistent behavior"""
|
129
|
-
console = Console()
|
130
|
-
|
131
|
-
# Parse command options
|
132
|
-
raw = False
|
133
|
-
verbose = False
|
134
|
-
debug = False
|
135
|
-
test_cmd = None
|
136
|
-
|
137
|
-
# Extract options from args
|
138
|
-
words = args.split()
|
139
|
-
filtered_args = []
|
140
|
-
i = 0
|
141
|
-
while i < len(words):
|
142
|
-
if words[i] == '--raw':
|
143
|
-
raw = True
|
144
|
-
elif words[i] == '--verbose':
|
145
|
-
verbose = True
|
146
|
-
elif words[i] == '--debug':
|
147
|
-
debug = True
|
148
|
-
elif words[i] == '--test' and i + 1 < len(words):
|
149
|
-
test_cmd = words[i + 1]
|
150
|
-
i += 1
|
151
|
-
else:
|
152
|
-
filtered_args.append(words[i])
|
153
|
-
i += 1
|
154
|
-
|
155
|
-
args = ' '.join(filtered_args)
|
156
|
-
|
157
|
-
# Update config with command options
|
158
|
-
from janito.config import config
|
159
|
-
config.set_debug(debug)
|
160
|
-
config.set_verbose(verbose)
|
161
|
-
config.set_test_cmd(test_cmd)
|
162
|
-
|
163
|
-
# Remove leading slash if present
|
164
|
-
command = command.lstrip('/')
|
165
|
-
|
166
|
-
# Handle command aliases
|
167
|
-
command_aliases = {
|
168
|
-
'h': 'help',
|
169
|
-
'a': 'ask',
|
170
|
-
'r': 'request',
|
171
|
-
'q': 'quit',
|
172
|
-
'exit': 'quit'
|
173
|
-
}
|
174
|
-
command = command_aliases.get(command, command)
|
175
|
-
|
176
|
-
if command == "help":
|
177
|
-
display_help()
|
178
|
-
return
|
179
|
-
|
180
|
-
if command == "quit":
|
181
|
-
raise EOFError()
|
182
|
-
|
183
|
-
if command == "ask":
|
184
|
-
if not args:
|
185
|
-
console.print(Panel(
|
186
|
-
"[red]Ask command requires a question[/red]",
|
187
|
-
title="Error",
|
188
|
-
border_style="red"
|
189
|
-
))
|
190
|
-
return
|
191
|
-
|
192
|
-
# Use CLI question processing function
|
193
|
-
from janito.__main__ import process_question
|
194
|
-
process_question(args, workdir, include, raw, claude)
|
195
|
-
return
|
196
|
-
|
197
|
-
if command == "request":
|
198
|
-
if not args:
|
199
|
-
console.print(Panel(
|
200
|
-
"[red]Request command requires a description[/red]",
|
201
|
-
title="Error",
|
202
|
-
border_style="red"
|
203
|
-
))
|
204
|
-
return
|
205
|
-
|
206
|
-
paths_to_scan = [workdir] if workdir else []
|
207
|
-
if include:
|
208
|
-
paths_to_scan.extend(include)
|
209
|
-
files_content = collect_files_content(paths_to_scan, workdir)
|
210
|
-
|
211
|
-
# Use CLI request processing functions
|
212
|
-
initial_prompt = build_request_analysis_prompt(files_content, args)
|
213
|
-
initial_response = progress_send_message(claude, initial_prompt)
|
214
|
-
|
215
|
-
from janito.__main__ import save_to_file
|
216
|
-
save_to_file(initial_response, 'analysis', workdir)
|
217
|
-
|
218
|
-
from janito.analysis import format_analysis
|
219
|
-
format_analysis(initial_response, raw, claude)
|
220
|
-
handle_option_selection(claude, initial_response, args, raw, workdir, include)
|
221
|
-
return
|
222
|
-
|
223
|
-
console.print(Panel(
|
224
|
-
f"[red]Unknown command: /{command}[/red]\nType '/help' for available commands",
|
225
|
-
title="Error",
|
226
|
-
border_style="red"
|
227
|
-
))
|
228
|
-
|
229
|
-
def start_console_session(workdir: Path, include: Optional[List[Path]] = None) -> None:
|
230
|
-
"""Start an enhanced interactive console session"""
|
231
|
-
console = Console()
|
232
|
-
claude = ClaudeAPIAgent(system_prompt=SYSTEM_PROMPT)
|
233
|
-
|
234
|
-
# Setup history with persistence
|
235
|
-
history_file = workdir / '.janito' / 'console_history'
|
236
|
-
history_file.parent.mkdir(parents=True, exist_ok=True)
|
237
|
-
|
238
|
-
# Create session with history and completions
|
239
|
-
session = PromptSession(
|
240
|
-
history=FileHistory(str(history_file)),
|
241
|
-
completer=create_completer(workdir),
|
242
|
-
auto_suggest=AutoSuggestFromHistory(),
|
243
|
-
complete_while_typing=True
|
244
|
-
)
|
245
|
-
|
246
|
-
# Get version and terminal info
|
247
|
-
from importlib.metadata import version
|
248
|
-
try:
|
249
|
-
ver = version("janito")
|
250
|
-
except:
|
251
|
-
ver = "dev"
|
252
|
-
|
253
|
-
term_width = shutil.get_terminal_size().columns
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
# Create welcome message with consistent colors and enhanced information
|
258
|
-
COLORS = {
|
259
|
-
'primary': '#729FCF', # Soft blue for primary elements
|
260
|
-
'secondary': '#8AE234', # Bright green for actions/success
|
261
|
-
'accent': '#AD7FA8', # Purple for accents
|
262
|
-
'muted': '#7F9F7F', # Muted green for less important text
|
263
|
-
}
|
264
|
-
|
265
|
-
welcome_text = (
|
266
|
-
f"[bold {COLORS['primary']}]Welcome to Janito v{ver}[/bold {COLORS['primary']}]\n"
|
267
|
-
f"[{COLORS['muted']}]Your AI-Powered Software Development Buddy[/{COLORS['muted']}]\n\n"
|
268
|
-
f"[{COLORS['accent']}]Keyboard Shortcuts:[/{COLORS['accent']}]\n"
|
269
|
-
"• ↑↓ : Navigate command history\n"
|
270
|
-
"• Tab : Complete commands and paths\n"
|
271
|
-
"• Ctrl+D : Exit console\n"
|
272
|
-
"• Ctrl+C : Cancel current operation\n\n"
|
273
|
-
f"[{COLORS['accent']}]Available Commands:[/{COLORS['accent']}]\n"
|
274
|
-
"• /ask (or /a) : Ask questions about code\n"
|
275
|
-
"• /request (or /r) : Request code changes\n"
|
276
|
-
"• /help (or /h) : Show detailed help\n"
|
277
|
-
"• /quit (or /q) : Exit console\n\n"
|
278
|
-
f"[{COLORS['accent']}]Quick Tips:[/{COLORS['accent']}]\n"
|
279
|
-
"• Start typing and press Tab for suggestions\n"
|
280
|
-
"• Use --test to run tests before changes\n"
|
281
|
-
"• Add --verbose for detailed output\n"
|
282
|
-
"• Type a request directly without /request\n\n"
|
283
|
-
f"[{COLORS['secondary']}]Current Version:[/{COLORS['secondary']}] v{ver}\n"
|
284
|
-
f"[{COLORS['muted']}]Working Directory:[/{COLORS['muted']}] {workdir.absolute()}"
|
285
|
-
)
|
286
|
-
|
287
|
-
welcome_panel = Panel(
|
288
|
-
welcome_text,
|
289
|
-
width=min(80, term_width - 4),
|
290
|
-
border_style="blue",
|
291
|
-
title="Janito Console",
|
292
|
-
subtitle="Press Tab for completions"
|
293
|
-
)
|
294
|
-
|
295
|
-
console.print("\n")
|
296
|
-
console.print(welcome_panel)
|
297
|
-
console.print("\n[cyan]How can I help you with your code today?[/cyan]\n")
|
298
|
-
|
299
|
-
while True:
|
300
|
-
try:
|
301
|
-
# Get input with formatted prompt
|
302
|
-
user_input = session.prompt(
|
303
|
-
lambda: format_prompt(workdir),
|
304
|
-
complete_while_typing=True
|
305
|
-
).strip()
|
306
|
-
|
307
|
-
if not user_input:
|
308
|
-
continue
|
309
|
-
|
310
|
-
if user_input.lower() in ('exit', 'quit'):
|
311
|
-
console.print("\n[cyan]Goodbye! Have a great day![/cyan]\n")
|
312
|
-
break
|
313
|
-
|
314
|
-
# Split input into command and args
|
315
|
-
parts = user_input.split(maxsplit=1)
|
316
|
-
if parts[0].startswith('/'): # Handle /command format
|
317
|
-
command = parts[0][1:] # Remove the / prefix
|
318
|
-
else:
|
319
|
-
command = "request" # Default to request if no command specified
|
320
|
-
|
321
|
-
args = parts[1] if len(parts) > 1 else ""
|
322
|
-
|
323
|
-
# Process command with separated args
|
324
|
-
process_command(command, args, workdir, include, claude)
|
325
|
-
|
326
|
-
except KeyboardInterrupt:
|
327
|
-
continue
|
328
|
-
except EOFError:
|
329
|
-
console.print("\n[cyan]Goodbye! Have a great day![/cyan]\n")
|
330
|
-
break
|