janito 0.6.0__py3-none-any.whl → 0.7.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/__main__.py +37 -30
- janito/agents/__init__.py +8 -2
- janito/agents/agent.py +10 -3
- janito/agents/claudeai.py +13 -23
- janito/agents/openai.py +5 -1
- janito/change/analysis/analyze.py +8 -7
- janito/change/analysis/prompts.py +4 -12
- janito/change/analysis/view/terminal.py +21 -11
- janito/change/applier/text.py +7 -5
- janito/change/core.py +22 -29
- janito/change/parser.py +0 -2
- janito/change/prompts.py +16 -21
- janito/change/validator.py +27 -9
- janito/change/viewer/content.py +1 -1
- janito/change/viewer/panels.py +93 -115
- janito/change/viewer/styling.py +15 -4
- janito/cli/commands.py +63 -20
- janito/common.py +44 -18
- janito/config.py +44 -44
- janito/prompt.py +36 -0
- janito/qa.py +5 -14
- janito/search_replace/README.md +63 -17
- janito/search_replace/__init__.py +2 -1
- janito/search_replace/core.py +15 -14
- janito/search_replace/logger.py +35 -0
- janito/search_replace/searcher.py +160 -48
- janito/search_replace/strategy_result.py +10 -0
- janito/shell/__init__.py +15 -16
- janito/shell/commands.py +38 -97
- janito/shell/processor.py +7 -27
- janito/shell/prompt.py +48 -0
- janito/shell/registry.py +60 -0
- janito/workspace/__init__.py +4 -5
- janito/workspace/analysis.py +2 -2
- janito/workspace/show.py +141 -0
- janito/workspace/stats.py +43 -0
- janito/workspace/types.py +98 -0
- janito/workspace/workset.py +108 -0
- janito/workspace/workspace.py +114 -0
- janito-0.7.0.dist-info/METADATA +167 -0
- {janito-0.6.0.dist-info → janito-0.7.0.dist-info}/RECORD +44 -43
- janito/change/viewer/pager.py +0 -56
- janito/cli/handlers/ask.py +0 -22
- janito/cli/handlers/demo.py +0 -22
- janito/cli/handlers/request.py +0 -24
- janito/cli/handlers/scan.py +0 -9
- janito/prompts.py +0 -2
- janito/shell/handlers.py +0 -122
- janito/workspace/manager.py +0 -48
- janito/workspace/scan.py +0 -232
- janito-0.6.0.dist-info/METADATA +0 -185
- {janito-0.6.0.dist-info → janito-0.7.0.dist-info}/WHEEL +0 -0
- {janito-0.6.0.dist-info → janito-0.7.0.dist-info}/entry_points.txt +0 -0
- {janito-0.6.0.dist-info → janito-0.7.0.dist-info}/licenses/LICENSE +0 -0
janito/change/viewer/panels.py
CHANGED
@@ -3,7 +3,8 @@ from rich.panel import Panel
|
|
3
3
|
from rich.columns import Columns
|
4
4
|
from rich import box
|
5
5
|
from rich.text import Text
|
6
|
-
from
|
6
|
+
from rich.syntax import Syntax
|
7
|
+
from typing import List, Union
|
7
8
|
from ..parser import FileChange, ChangeOperation
|
8
9
|
from .styling import format_content, create_legend_items
|
9
10
|
from .content import create_content_preview
|
@@ -11,9 +12,8 @@ from rich.rule import Rule
|
|
11
12
|
import shutil
|
12
13
|
import sys
|
13
14
|
from rich.live import Live
|
14
|
-
from
|
15
|
+
from pathlib import Path
|
15
16
|
|
16
|
-
# Remove clear_last_line, wait_for_space, and check_pager functions since they've been moved
|
17
17
|
|
18
18
|
def preview_all_changes(console: Console, changes: List[FileChange]) -> None:
|
19
19
|
"""Show a summary of all changes with side-by-side comparison and continuous flow."""
|
@@ -38,47 +38,28 @@ def preview_all_changes(console: Console, changes: List[FileChange]) -> None:
|
|
38
38
|
grouped_changes[change.operation] = []
|
39
39
|
grouped_changes[change.operation].append(change)
|
40
40
|
|
41
|
-
# Track content height
|
42
|
-
current_height = 2 # Account for legend and newline
|
43
|
-
|
44
|
-
# Show file operations with rule lines and track height
|
45
|
-
current_height = _show_file_operations(console, grouped_changes)
|
46
|
-
|
47
41
|
# Then show side-by-side panels for replacements
|
48
42
|
console.print("\n[bold blue]File Changes:[/bold blue]")
|
49
|
-
current_height += 2
|
50
43
|
|
51
44
|
for i, change in enumerate(changes):
|
52
45
|
if change.operation in (ChangeOperation.REPLACE_FILE, ChangeOperation.MODIFY_FILE):
|
53
46
|
show_side_by_side_diff(console, change, i, total_changes)
|
54
47
|
|
55
|
-
def _show_file_operations(console: Console, grouped_changes: dict) ->
|
56
|
-
"""Display file operation summaries with content preview for new files.
|
57
|
-
|
58
|
-
Tracks current file being displayed and manages continuous flow.
|
59
|
-
"""
|
48
|
+
def _show_file_operations(console: Console, grouped_changes: dict) -> None:
|
60
49
|
"""Display file operation summaries with content preview for new files."""
|
61
|
-
height = 0
|
62
50
|
for operation, group in grouped_changes.items():
|
63
51
|
for change in group:
|
64
52
|
if operation == ChangeOperation.CREATE_FILE:
|
65
53
|
console.print(Rule(f"[green]Creating new file: {change.name}[/green]", style="green"))
|
66
|
-
height += 1
|
67
54
|
if change.content:
|
68
|
-
preview = create_content_preview(change.name, change.content)
|
55
|
+
preview = create_content_preview(Path(change.name), change.content)
|
69
56
|
console.print(preview)
|
70
|
-
height += len(change.content.splitlines()) + 4 # Account for panel borders
|
71
57
|
elif operation == ChangeOperation.REMOVE_FILE:
|
72
58
|
console.print(Rule(f"[red]Removing file: {change.name}[/red]", style="red"))
|
73
|
-
height += 1
|
74
59
|
elif operation == ChangeOperation.RENAME_FILE:
|
75
60
|
console.print(Rule(f"[yellow]Renaming file: {change.name} → {change.target}[/yellow]", style="yellow"))
|
76
|
-
height += 1
|
77
61
|
elif operation == ChangeOperation.MOVE_FILE:
|
78
62
|
console.print(Rule(f"[blue]Moving file: {change.name} → {change.target}[/blue]", style="blue"))
|
79
|
-
height += 1
|
80
|
-
height = check_pager(console, height)
|
81
|
-
return height
|
82
63
|
|
83
64
|
def show_side_by_side_diff(console: Console, change: FileChange, change_index: int = 0, total_changes: int = 1) -> None:
|
84
65
|
"""Show side-by-side diff panels for a file change with continuous flow.
|
@@ -98,14 +79,9 @@ def show_side_by_side_diff(console: Console, change: FileChange, change_index: i
|
|
98
79
|
total_changes: Total number of changes
|
99
80
|
"""
|
100
81
|
# Track current file name to prevent unnecessary paging
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
# Only update paging state for different files
|
106
|
-
if current_file != new_file:
|
107
|
-
set_current_file(new_file)
|
108
|
-
# Handle delete operations with special formatting
|
82
|
+
# Get terminal dimensions for layout
|
83
|
+
term_width = console.width or 120
|
84
|
+
min_panel_width = 60 # Minimum width for readable content
|
109
85
|
if change.operation == ChangeOperation.REMOVE_FILE:
|
110
86
|
show_delete_panel(console, change, change_index, total_changes)
|
111
87
|
return
|
@@ -122,33 +98,30 @@ def show_side_by_side_diff(console: Console, change: FileChange, change_index: i
|
|
122
98
|
original_lines = original.splitlines()
|
123
99
|
new_lines = new_content.splitlines()
|
124
100
|
|
125
|
-
#
|
126
|
-
current_height = 0
|
127
|
-
|
128
|
-
# Track content height
|
129
|
-
current_height += 1
|
130
|
-
|
131
|
-
# Check if we need to page before showing header
|
132
|
-
header_height = 3 # Account for panel borders and content
|
133
|
-
current_height = check_pager(console, current_height, header_height)
|
134
|
-
|
101
|
+
# Show the header with reason and progress
|
135
102
|
# Show the header with reason and progress
|
136
103
|
operation = change.operation.name.replace('_', ' ').title()
|
137
|
-
|
138
|
-
# Create centered
|
139
|
-
reason_text = Text()
|
140
|
-
if change.reason:
|
141
|
-
reason_text.append("\n")
|
142
|
-
reason_text.append(change.reason, style="italic")
|
143
|
-
# Build header with operation and progress
|
104
|
+
|
105
|
+
# Create centered header with file info and progress
|
144
106
|
header = Text()
|
145
107
|
header.append(f"{operation}:", style="bold cyan")
|
146
|
-
header.append(f" {change.name} ")
|
147
|
-
|
148
|
-
|
108
|
+
header.append(f" {change.name}\n\n", style="white")
|
109
|
+
|
110
|
+
# Create centered progress indicator
|
111
|
+
progress_text = Text()
|
112
|
+
progress_text.append("Change ", style="bold blue")
|
113
|
+
progress_text.append(f"{change_index + 1}", style="bold yellow")
|
114
|
+
progress_text.append("/", style="white")
|
115
|
+
progress_text.append(f"{total_changes}", style="bold white")
|
116
|
+
|
117
|
+
# Add reason if present
|
118
|
+
if change.reason:
|
119
|
+
header.append(f"{progress_text}\n\n", style="white")
|
120
|
+
header.append(change.reason, style="italic")
|
121
|
+
else:
|
122
|
+
header.append(progress_text)
|
149
123
|
# Display panel with centered content
|
150
124
|
console.print(Panel(header, box=box.HEAVY, style="cyan", title_align="center"))
|
151
|
-
current_height += header_height
|
152
125
|
|
153
126
|
# Show layout mode indicator
|
154
127
|
if not can_do_side_by_side:
|
@@ -178,34 +151,41 @@ def show_side_by_side_diff(console: Console, change: FileChange, change_index: i
|
|
178
151
|
left_panel = format_content(orig_section, orig_section, new_section, True)
|
179
152
|
right_panel = format_content(new_section, orig_section, new_section, False)
|
180
153
|
|
181
|
-
# Calculate upcoming content height
|
182
|
-
content_height = len(orig_section) + len(new_section) + 4 # Account for panel borders and padding
|
183
154
|
|
184
|
-
#
|
185
|
-
current_height = check_pager(console, current_height, content_height)
|
186
|
-
|
187
|
-
# Create panels with adaptive width
|
155
|
+
# Calculate optimal panel widths based on content
|
188
156
|
if can_do_side_by_side:
|
189
|
-
#
|
190
|
-
|
157
|
+
# Get max line lengths for each panel
|
158
|
+
left_max_width = max((len(line) for line in str(left_panel).splitlines()), default=0)
|
159
|
+
right_max_width = max((len(line) for line in str(right_panel).splitlines()), default=0)
|
160
|
+
|
161
|
+
# Add padding and margins
|
162
|
+
left_width = min(left_max_width + 4, (term_width - 4) // 2)
|
163
|
+
right_width = min(right_max_width + 4, (term_width - 4) // 2)
|
164
|
+
|
165
|
+
# Ensure minimum width
|
166
|
+
min_width = 30
|
167
|
+
left_width = max(left_width, min_width)
|
168
|
+
right_width = max(right_width, min_width)
|
169
|
+
|
170
|
+
# Create panels with content-aware widths
|
191
171
|
panels = [
|
192
172
|
Panel(
|
193
173
|
left_panel or "",
|
194
|
-
title="[red]Original
|
174
|
+
title="[red]Original[/red]",
|
195
175
|
title_align="center",
|
196
176
|
subtitle=str(change.name),
|
197
177
|
subtitle_align="center",
|
198
178
|
padding=(0, 1),
|
199
|
-
width=
|
179
|
+
width=left_width
|
200
180
|
),
|
201
181
|
Panel(
|
202
182
|
right_panel or "",
|
203
|
-
title="[green]Modified
|
183
|
+
title="[green]Modified[/green]",
|
204
184
|
title_align="center",
|
205
185
|
subtitle=str(change.name),
|
206
186
|
subtitle_align="center",
|
207
187
|
padding=(0, 1),
|
208
|
-
width=
|
188
|
+
width=right_width
|
209
189
|
)
|
210
190
|
]
|
211
191
|
|
@@ -248,7 +228,6 @@ def show_side_by_side_diff(console: Console, change: FileChange, change_index: i
|
|
248
228
|
console.print(Rule(" Section Break ", style="cyan dim", align="center"))
|
249
229
|
|
250
230
|
# Update height after displaying content
|
251
|
-
current_height += content_height
|
252
231
|
else:
|
253
232
|
# For non-text changes, show full content side by side
|
254
233
|
sections = find_modified_sections(original_lines, new_lines)
|
@@ -256,10 +235,6 @@ def show_side_by_side_diff(console: Console, change: FileChange, change_index: i
|
|
256
235
|
left_panel = format_content(orig_section, orig_section, new_section, True)
|
257
236
|
right_panel = format_content(new_section, orig_section, new_section, False)
|
258
237
|
|
259
|
-
# Calculate content height for full file diff
|
260
|
-
content_height = len(orig_section) + len(new_section) + 4 # Account for panels
|
261
|
-
current_height = check_pager(console, current_height, content_height)
|
262
|
-
|
263
238
|
# Format content with appropriate width
|
264
239
|
left_panel = format_content(orig_section, orig_section, new_section, True)
|
265
240
|
right_panel = format_content(new_section, orig_section, new_section, False)
|
@@ -275,37 +250,28 @@ def show_side_by_side_diff(console: Console, change: FileChange, change_index: i
|
|
275
250
|
console.print("[yellow]Terminal width is limited. Using vertical layout for better readability.[/yellow]")
|
276
251
|
console.print(f"[dim]Recommended terminal width: {min_panel_width * 2 + 4} or greater[/dim]")
|
277
252
|
|
278
|
-
# Create
|
279
|
-
header_text = Text()
|
280
|
-
header_text.append("[red]Original[/red]", style="bold")
|
281
|
-
header_text.append(" vs ")
|
282
|
-
header_text.append("[green]Modified[/green]", style="bold")
|
283
|
-
header_text.append(f" - {change.name}")
|
284
|
-
|
285
|
-
header_panel = Panel(
|
286
|
-
header_text,
|
287
|
-
box=box.HEAVY,
|
288
|
-
style="cyan",
|
289
|
-
padding=(0, 1)
|
290
|
-
)
|
291
|
-
|
292
|
-
# Create content panels without individual titles
|
253
|
+
# Create content panels with consistent titles
|
293
254
|
panels = [
|
294
255
|
Panel(
|
295
256
|
left_panel,
|
257
|
+
title="[red]Original[/red]",
|
258
|
+
title_align="center",
|
259
|
+
subtitle=str(change.name),
|
260
|
+
subtitle_align="center",
|
296
261
|
padding=(0, 1),
|
297
262
|
width=None if can_do_side_by_side else term_width - 2
|
298
263
|
),
|
299
264
|
Panel(
|
300
265
|
right_panel,
|
266
|
+
title="[green]Modified[/green]",
|
267
|
+
title_align="center",
|
268
|
+
subtitle=str(change.name),
|
269
|
+
subtitle_align="center",
|
301
270
|
padding=(0, 1),
|
302
271
|
width=None if can_do_side_by_side else term_width - 2
|
303
272
|
)
|
304
273
|
]
|
305
274
|
|
306
|
-
# Display unified header
|
307
|
-
console.print(header_panel, justify="center")
|
308
|
-
|
309
275
|
# Render panels based on layout
|
310
276
|
if can_do_side_by_side:
|
311
277
|
# Create centered columns with fixed width
|
@@ -326,7 +292,6 @@ def show_side_by_side_diff(console: Console, change: FileChange, change_index: i
|
|
326
292
|
console.print(Rule(style="dim"))
|
327
293
|
|
328
294
|
# Update height after displaying content
|
329
|
-
current_height += content_height
|
330
295
|
|
331
296
|
# Add final separator and success message
|
332
297
|
console.print(Rule(title="End Of Changes", style="bold blue"))
|
@@ -427,7 +392,18 @@ def create_replace_panel(name: Text, change: FileChange) -> Panel:
|
|
427
392
|
new_content = change.content or ""
|
428
393
|
|
429
394
|
term_width = Console().width or 120
|
430
|
-
|
395
|
+
|
396
|
+
# Calculate content-based widths
|
397
|
+
orig_lines = original.splitlines()
|
398
|
+
new_lines = new_content.splitlines()
|
399
|
+
|
400
|
+
orig_width = max((len(line) for line in orig_lines), default=0)
|
401
|
+
new_width = max((len(line) for line in new_lines), default=0)
|
402
|
+
|
403
|
+
# Add padding and ensure minimum width
|
404
|
+
min_width = 30
|
405
|
+
left_width = max(min_width, min(orig_width + 4, (term_width - 10) // 2))
|
406
|
+
right_width = max(min_width, min(new_width + 4, (term_width - 10) // 2))
|
431
407
|
|
432
408
|
panels = [
|
433
409
|
Panel(
|
@@ -484,7 +460,7 @@ def create_change_panel(search_content: Text, replace_content: Text, description
|
|
484
460
|
box=box.HEAVY
|
485
461
|
)
|
486
462
|
def show_delete_panel(console: Console, change: FileChange, change_index: int = 0, total_changes: int = 1) -> None:
|
487
|
-
"""Show a
|
463
|
+
"""Show a simplified panel for file deletion operations
|
488
464
|
|
489
465
|
Args:
|
490
466
|
console: Rich console instance
|
@@ -492,49 +468,51 @@ def show_delete_panel(console: Console, change: FileChange, change_index: int =
|
|
492
468
|
change_index: Current change number (0-based)
|
493
469
|
total_changes: Total number of changes
|
494
470
|
"""
|
495
|
-
# Track content height for panel display
|
496
|
-
current_height = 0
|
497
|
-
|
498
471
|
# Show the header with reason and progress
|
499
472
|
operation = change.operation.name.replace('_', ' ').title()
|
500
473
|
progress = f"Change {change_index + 1}/{total_changes}"
|
501
474
|
|
502
|
-
# Create
|
503
|
-
reason_text = Text()
|
504
|
-
if change.reason:
|
505
|
-
reason_text.append("\n")
|
506
|
-
reason_text.append(change.reason, style="italic")
|
507
|
-
|
508
|
-
# Build header with operation and progress
|
475
|
+
# Create header text
|
509
476
|
header = Text()
|
510
477
|
header.append(f"{operation}:", style="bold red")
|
511
478
|
header.append(f" {change.name} ")
|
512
479
|
header.append(f"({progress})", style="dim")
|
513
|
-
header.append(reason_text)
|
514
480
|
|
515
|
-
#
|
516
|
-
|
481
|
+
# Add reason if present
|
482
|
+
if change.reason:
|
483
|
+
header.append("\n")
|
484
|
+
header.append(change.reason, style="italic")
|
485
|
+
|
486
|
+
# Create content text
|
487
|
+
content = Text()
|
488
|
+
content.append("This file will be removed", style="bold red")
|
517
489
|
|
518
|
-
#
|
519
|
-
delete_text = Text()
|
520
|
-
delete_text.append("This file will be removed", style="bold red")
|
490
|
+
# Show file preview if content exists
|
521
491
|
if change.original_content:
|
522
|
-
|
523
|
-
|
492
|
+
content.append("\n\n")
|
493
|
+
content.append("Original Content:", style="dim red")
|
494
|
+
content.append("\n")
|
495
|
+
syntax = Syntax(
|
496
|
+
change.original_content,
|
497
|
+
"python",
|
498
|
+
theme="monokai",
|
499
|
+
line_numbers=True,
|
500
|
+
word_wrap=True,
|
501
|
+
background_color="red"
|
502
|
+
)
|
503
|
+
content.append(syntax)
|
524
504
|
|
505
|
+
# Display panels
|
506
|
+
console.print(Panel(header, box=box.HEAVY, style="red", title_align="center"))
|
525
507
|
console.print(Panel(
|
526
|
-
|
527
|
-
title="[red]File Deletion[/red]",
|
508
|
+
content,
|
509
|
+
title="[red]File Deletion Preview[/red]",
|
528
510
|
title_align="center",
|
529
511
|
border_style="red",
|
530
512
|
padding=(1, 2)
|
531
513
|
))
|
532
|
-
|
533
|
-
# Add final separator
|
534
514
|
console.print(Rule(title="End Of Changes", style="bold red"))
|
535
515
|
console.print()
|
536
|
-
import os
|
537
|
-
from typing import Union
|
538
516
|
|
539
517
|
def get_human_size(size_bytes: int) -> str:
|
540
518
|
"""Convert bytes to human readable format"""
|
janito/change/viewer/styling.py
CHANGED
@@ -13,16 +13,27 @@ def set_theme(theme: ColorTheme) -> None:
|
|
13
13
|
current_theme = theme
|
14
14
|
|
15
15
|
def format_content(lines: List[str], search_lines: List[str], replace_lines: List[str], is_search: bool, width: int = 80, is_delete: bool = False) -> Text:
|
16
|
-
"""Format content with unified highlighting and indicators
|
16
|
+
"""Format content with unified highlighting and indicators with full-width padding
|
17
|
+
|
18
|
+
Args:
|
19
|
+
lines: Lines to format
|
20
|
+
search_lines: Original content lines for comparison
|
21
|
+
replace_lines: New content lines for comparison
|
22
|
+
is_search: Whether this is search content (vs replace content)
|
23
|
+
width: Target width for padding
|
24
|
+
is_delete: Whether this is a deletion operation
|
25
|
+
"""
|
17
26
|
text = Text()
|
18
27
|
|
19
|
-
# For delete operations, show all lines as deleted
|
28
|
+
# For delete operations, show all lines as deleted with full-width padding
|
20
29
|
if is_delete:
|
21
30
|
for line in lines:
|
22
31
|
bg_color = current_theme.line_backgrounds['deleted']
|
23
32
|
style = f"{current_theme.text_color} on {bg_color}"
|
24
|
-
|
25
|
-
|
33
|
+
# Calculate padding to fill width
|
34
|
+
content_width = len(f"✕ {line}")
|
35
|
+
padding = " " * max(0, width - content_width)
|
36
|
+
# Add content with consistent background
|
26
37
|
text.append("✕ ", style=style)
|
27
38
|
text.append(line, style=style)
|
28
39
|
text.append(padding, style=style)
|
janito/cli/commands.py
CHANGED
@@ -1,45 +1,88 @@
|
|
1
1
|
from pathlib import Path
|
2
2
|
from typing import Optional, List
|
3
3
|
from rich.console import Console
|
4
|
+
from rich.text import Text
|
4
5
|
|
5
|
-
from janito.agents import AIAgent
|
6
|
-
from janito.workspace
|
6
|
+
from janito.agents import AIAgent, agent
|
7
|
+
from janito.workspace import workset
|
7
8
|
from janito.config import config
|
8
9
|
from janito.change.core import process_change_request
|
9
10
|
from janito.change.play import play_saved_changes
|
10
11
|
from janito.cli.history import save_to_history
|
11
|
-
from janito.
|
12
|
+
from janito.qa import ask_question, display_answer
|
13
|
+
from janito.demo import DemoRunner
|
14
|
+
from janito.demo.data import get_demo_scenarios
|
12
15
|
|
13
|
-
|
14
|
-
from .handlers.ask import AskHandler
|
15
|
-
from .handlers.request import RequestHandler
|
16
|
-
from .handlers.scan import ScanHandler
|
17
|
-
from janito.change.play import play_saved_changes
|
16
|
+
console = Console()
|
18
17
|
|
19
18
|
def handle_ask(question: str):
|
20
|
-
"""
|
21
|
-
|
22
|
-
|
19
|
+
"""Process a question about the codebase"""
|
20
|
+
|
21
|
+
if config.tui:
|
22
|
+
answer = ask_question(question)
|
23
|
+
from janito.tui import TuiApp
|
24
|
+
app = TuiApp(content=answer)
|
25
|
+
app.run()
|
26
|
+
else:
|
27
|
+
answer = ask_question(question)
|
28
|
+
display_answer(answer)
|
23
29
|
|
24
|
-
def handle_scan(
|
30
|
+
def handle_scan():
|
25
31
|
"""Preview files that would be analyzed"""
|
26
|
-
|
27
|
-
from janito.workspace.analysis import analyze_workspace_content
|
28
|
-
preview_scan(paths_to_scan)
|
29
|
-
files_content = collect_files_content(paths_to_scan)
|
30
|
-
analyze_workspace_content(files_content)
|
32
|
+
workset.show()
|
31
33
|
|
32
34
|
def handle_play(filepath: Path):
|
33
35
|
"""Replay a saved changes or debug file"""
|
34
36
|
play_saved_changes(filepath)
|
35
37
|
|
38
|
+
def is_dir_empty(path: Path) -> bool:
|
39
|
+
"""Check if directory is empty or only contains empty directories."""
|
40
|
+
if not path.is_dir():
|
41
|
+
return False
|
42
|
+
|
43
|
+
for item in path.iterdir():
|
44
|
+
if item.name.startswith(('.', '__pycache__')):
|
45
|
+
continue
|
46
|
+
if item.is_file():
|
47
|
+
return False
|
48
|
+
if item.is_dir() and not is_dir_empty(item):
|
49
|
+
return False
|
50
|
+
return True
|
51
|
+
|
36
52
|
def handle_request(request: str, preview_only: bool = False):
|
37
53
|
"""Process modification request"""
|
54
|
+
is_empty = is_dir_empty(config.workspace_dir)
|
55
|
+
if is_empty and not config.include:
|
56
|
+
console.print("\n[bold blue]Empty directory - will create new files as needed[/bold blue]")
|
38
57
|
|
58
|
+
success, history_file = process_change_request(request, preview_only)
|
39
59
|
|
40
|
-
|
41
|
-
|
60
|
+
if success and history_file and config.verbose:
|
61
|
+
try:
|
62
|
+
rel_path = history_file.relative_to(config.workspace_dir)
|
63
|
+
console.print(f"\nChanges saved to: ./{rel_path}")
|
64
|
+
except ValueError:
|
65
|
+
console.print(f"\nChanges saved to: {history_file}")
|
66
|
+
elif not success:
|
67
|
+
console.print("[red]Failed to process change request[/red]")
|
42
68
|
|
43
69
|
# Save request and response to history
|
44
70
|
if agent.last_response:
|
45
|
-
save_to_history(request, agent.last_response)
|
71
|
+
save_to_history(request, agent.last_response)
|
72
|
+
|
73
|
+
def handle_demo():
|
74
|
+
"""Run demo scenarios"""
|
75
|
+
runner = DemoRunner()
|
76
|
+
|
77
|
+
# Add predefined scenarios
|
78
|
+
for scenario in get_demo_scenarios():
|
79
|
+
runner.add_scenario(scenario)
|
80
|
+
|
81
|
+
# Preview and run scenarios
|
82
|
+
console.print("\n[bold cyan]Demo Scenarios Preview:[/bold cyan]")
|
83
|
+
runner.preview_changes()
|
84
|
+
|
85
|
+
console.print("\n[bold cyan]Running Demo Scenarios:[/bold cyan]")
|
86
|
+
runner.run_all()
|
87
|
+
|
88
|
+
console.print("\n[green]Demo completed successfully![/green]")
|
janito/common.py
CHANGED
@@ -4,21 +4,38 @@ from rich.rule import Rule
|
|
4
4
|
from janito.agents import agent
|
5
5
|
from .config import config
|
6
6
|
from rich import print
|
7
|
+
from threading import Event
|
8
|
+
|
9
|
+
""" CACHE USAGE SUMMARY
|
10
|
+
https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching
|
11
|
+
cache_creation_input_tokens: Number of tokens written to the cache when creating a new entry.
|
12
|
+
cache_read_input_tokens: Number of tokens retrieved from the cache for this request.
|
13
|
+
input_tokens: Number of input tokens which were not read from or used to create a cache.
|
14
|
+
"""
|
15
|
+
|
16
|
+
from janito.prompt import build_system_prompt
|
7
17
|
|
8
18
|
console = Console()
|
9
19
|
|
10
20
|
def progress_send_message(message: str) -> str:
|
11
|
-
"""
|
12
|
-
|
21
|
+
"""Send a message to the AI agent with progress indication.
|
22
|
+
|
23
|
+
Displays a progress spinner while waiting for the agent's response and shows
|
24
|
+
token usage statistics after receiving the response.
|
13
25
|
|
14
26
|
Args:
|
15
|
-
message: The message to send
|
27
|
+
message: The message to send to the AI agent
|
16
28
|
|
17
29
|
Returns:
|
18
|
-
The response from the AI agent
|
30
|
+
str: The response text from the AI agent
|
31
|
+
|
32
|
+
Note:
|
33
|
+
If the request fails or is canceled, returns the error message as a string
|
19
34
|
"""
|
35
|
+
system_message = build_system_prompt()
|
20
36
|
if config.debug:
|
21
37
|
console.print("[yellow]======= Sending message[/yellow]")
|
38
|
+
print(system_message)
|
22
39
|
print(message)
|
23
40
|
console.print("[yellow]======= End of message[/yellow]")
|
24
41
|
|
@@ -28,27 +45,36 @@ def progress_send_message(message: str) -> str:
|
|
28
45
|
TimeElapsedColumn(),
|
29
46
|
) as progress:
|
30
47
|
task = progress.add_task("Waiting for response from AI agent...", total=None)
|
31
|
-
response = agent.send_message(message)
|
48
|
+
response = agent.send_message(message, system_message=system_message)
|
32
49
|
progress.update(task, completed=True)
|
33
50
|
|
34
51
|
if config.debug:
|
35
52
|
console.print("[yellow]======= Received response[/yellow]")
|
36
53
|
print(response)
|
37
54
|
console.print("[yellow]======= End of response[/yellow]")
|
38
|
-
response_text = response.content[0].text
|
39
55
|
|
40
|
-
|
41
|
-
usage = response.usage
|
56
|
+
response_text = response.content[0].text if hasattr(response, 'content') else str(response)
|
42
57
|
|
43
|
-
#
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
58
|
+
# Add token usage summary with detailed cache info
|
59
|
+
if hasattr(response, 'usage'):
|
60
|
+
usage = response.usage
|
61
|
+
|
62
|
+
direct_input = usage.input_tokens
|
63
|
+
cache_create = usage.cache_creation_input_tokens or 0
|
64
|
+
cache_read = usage.cache_read_input_tokens or 0
|
65
|
+
total_input = direct_input + cache_create + cache_read
|
66
|
+
|
67
|
+
# Calculate percentages relative to total input
|
68
|
+
create_pct = (cache_create / total_input * 100) if cache_create else 0
|
69
|
+
read_pct = (cache_read / total_input * 100) if cache_read else 0
|
70
|
+
direct_pct = (direct_input / total_input * 100) if direct_input else 0
|
71
|
+
output_ratio = (usage.output_tokens / total_input * 100)
|
72
|
+
|
73
|
+
# Compact single-line token usage summary
|
74
|
+
usage_text = f"[cyan]In: [/][bold green]{total_input:,} - direct: {direct_input} ({direct_pct:.1f}%))[/] [cyan]Out:[/] [bold yellow]{usage.output_tokens:,}[/][dim]({output_ratio:.1f}%)[/]"
|
75
|
+
if cache_create or cache_read:
|
76
|
+
cache_text = f" [magenta]Input Cache:[/] [blue]Write:{cache_create:,}[/][dim]({create_pct:.1f}%)[/] [green]Read:{cache_read:,}[/][dim]({read_pct:.1f}%)[/]"
|
77
|
+
usage_text += cache_text
|
78
|
+
console.print(Rule(usage_text, style="cyan"))
|
53
79
|
|
54
80
|
return response_text
|