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.
Files changed (106) hide show
  1. janito/__init__.py +0 -47
  2. janito/__main__.py +96 -15
  3. janito/agents/__init__.py +2 -8
  4. janito/agents/claudeai.py +3 -12
  5. janito/change/__init__.py +29 -16
  6. janito/change/__main__.py +0 -0
  7. janito/{analysis → change/analysis}/__init__.py +5 -15
  8. janito/change/analysis/__main__.py +7 -0
  9. janito/change/analysis/analyze.py +61 -0
  10. janito/change/analysis/formatting.py +78 -0
  11. janito/change/analysis/options.py +81 -0
  12. janito/{analysis → change/analysis}/prompts.py +35 -12
  13. janito/change/analysis/view/__init__.py +9 -0
  14. janito/change/analysis/view/terminal.py +171 -0
  15. janito/change/applier/__init__.py +5 -0
  16. janito/change/applier/file.py +58 -0
  17. janito/change/applier/main.py +156 -0
  18. janito/change/applier/text.py +245 -0
  19. janito/change/applier/workspace_dir.py +58 -0
  20. janito/change/core.py +131 -0
  21. janito/{changehistory.py → change/history.py} +12 -14
  22. janito/change/operations.py +7 -0
  23. janito/change/parser.py +289 -0
  24. janito/change/play.py +54 -0
  25. janito/change/preview.py +82 -0
  26. janito/change/prompts.py +126 -0
  27. janito/change/test.py +0 -0
  28. janito/change/validator.py +251 -0
  29. janito/{changeviewer → change/viewer}/__init__.py +3 -4
  30. janito/change/viewer/content.py +66 -0
  31. janito/{changeviewer → change/viewer}/diff.py +19 -4
  32. janito/change/viewer/pager.py +56 -0
  33. janito/change/viewer/panels.py +555 -0
  34. janito/change/viewer/styling.py +103 -0
  35. janito/{changeviewer → change/viewer}/themes.py +3 -5
  36. janito/clear_statement_parser/clear_statement_format.txt +328 -0
  37. janito/clear_statement_parser/examples.txt +326 -0
  38. janito/clear_statement_parser/models.py +104 -0
  39. janito/clear_statement_parser/parser.py +496 -0
  40. janito/cli/base.py +30 -0
  41. janito/cli/commands.py +30 -38
  42. janito/cli/functions.py +19 -194
  43. janito/cli/handlers/ask.py +22 -0
  44. janito/cli/handlers/demo.py +22 -0
  45. janito/cli/handlers/request.py +24 -0
  46. janito/cli/handlers/scan.py +9 -0
  47. janito/cli/history.py +61 -0
  48. janito/common.py +34 -3
  49. janito/config.py +71 -6
  50. janito/demo/__init__.py +4 -0
  51. janito/demo/data.py +13 -0
  52. janito/demo/mock_data.py +20 -0
  53. janito/demo/operations.py +45 -0
  54. janito/demo/runner.py +59 -0
  55. janito/demo/scenarios.py +32 -0
  56. janito/prompts.py +1 -80
  57. janito/qa.py +4 -3
  58. janito/search_replace/README.md +146 -0
  59. janito/search_replace/__init__.py +6 -0
  60. janito/search_replace/__main__.py +21 -0
  61. janito/search_replace/core.py +119 -0
  62. janito/search_replace/parser.py +52 -0
  63. janito/search_replace/play.py +61 -0
  64. janito/search_replace/replacer.py +36 -0
  65. janito/search_replace/searcher.py +299 -0
  66. janito/shell/__init__.py +39 -0
  67. janito/shell/bus.py +31 -0
  68. janito/shell/commands.py +195 -0
  69. janito/shell/handlers.py +122 -0
  70. janito/shell/history.py +20 -0
  71. janito/shell/processor.py +52 -0
  72. janito/tui/__init__.py +21 -0
  73. janito/tui/base.py +22 -0
  74. janito/tui/flows/__init__.py +5 -0
  75. janito/tui/flows/changes.py +65 -0
  76. janito/tui/flows/content.py +128 -0
  77. janito/tui/flows/selection.py +117 -0
  78. janito/tui/screens/__init__.py +3 -0
  79. janito/tui/screens/app.py +1 -0
  80. janito/workspace/__init__.py +7 -0
  81. janito/workspace/analysis.py +121 -0
  82. janito/workspace/manager.py +48 -0
  83. janito/workspace/scan.py +232 -0
  84. janito-0.6.0.dist-info/METADATA +185 -0
  85. janito-0.6.0.dist-info/RECORD +95 -0
  86. {janito-0.5.0.dist-info → janito-0.6.0.dist-info}/WHEEL +1 -1
  87. janito/_contextparser.py +0 -113
  88. janito/analysis/display.py +0 -149
  89. janito/analysis/options.py +0 -112
  90. janito/change/applier.py +0 -269
  91. janito/change/content.py +0 -62
  92. janito/change/indentation.py +0 -33
  93. janito/change/position.py +0 -169
  94. janito/changeviewer/panels.py +0 -268
  95. janito/changeviewer/styling.py +0 -59
  96. janito/console/__init__.py +0 -3
  97. janito/console/commands.py +0 -112
  98. janito/console/core.py +0 -62
  99. janito/console/display.py +0 -157
  100. janito/fileparser.py +0 -334
  101. janito/scan.py +0 -176
  102. janito/tests/test_fileparser.py +0 -26
  103. janito-0.5.0.dist-info/METADATA +0 -146
  104. janito-0.5.0.dist-info/RECORD +0 -45
  105. {janito-0.5.0.dist-info → janito-0.6.0.dist-info}/entry_points.txt +0 -0
  106. {janito-0.5.0.dist-info → janito-0.6.0.dist-info}/licenses/LICENSE +0 -0
janito/cli/commands.py CHANGED
@@ -3,51 +3,43 @@ from typing import Optional, List
3
3
  from rich.console import Console
4
4
 
5
5
  from janito.agents import AIAgent
6
- from janito.scan import preview_scan, is_dir_empty
6
+ from janito.workspace.analysis import analyze_workspace_content
7
7
  from janito.config import config
8
+ from janito.change.core import process_change_request
9
+ from janito.change.play import play_saved_changes
10
+ from janito.cli.history import save_to_history
11
+ from janito.agents import agent
8
12
 
9
- from .functions import (
10
- process_question, replay_saved_file, ensure_workdir,
11
- review_text, save_to_file, collect_files_content,
12
- build_request_analysis_prompt, progress_send_message,
13
- format_analysis, handle_option_selection, read_stdin
14
- )
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
18
 
17
- def handle_ask(question: str, workdir: Path, include: List[Path], raw: bool, agent: AIAgent):
19
+ def handle_ask(question: str):
18
20
  """Ask a question about the codebase"""
19
- workdir = ensure_workdir(workdir)
20
- if question == ".":
21
- question = read_stdin()
22
- process_question(question, workdir, include, raw, agent)
21
+ handler = AskHandler()
22
+ handler.handle(question)
23
23
 
24
- def handle_scan(paths_to_scan: List[Path], workdir: Path):
24
+ def handle_scan(paths_to_scan: List[Path]):
25
25
  """Preview files that would be analyzed"""
26
- workdir = ensure_workdir(workdir)
27
- preview_scan(paths_to_scan, workdir)
26
+ from janito.workspace import collect_files_content, preview_scan
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)
28
31
 
29
- def handle_play(filepath: Path, workdir: Path, raw: bool):
30
- """Replay a saved prompt file"""
31
- workdir = ensure_workdir(workdir)
32
- replay_saved_file(filepath, workdir, raw)
32
+ def handle_play(filepath: Path):
33
+ """Replay a saved changes or debug file"""
34
+ play_saved_changes(filepath)
33
35
 
34
- def handle_request(request: str, workdir: Path, include: List[Path], raw: bool, agent: AIAgent):
36
+ def handle_request(request: str, preview_only: bool = False):
35
37
  """Process modification request"""
36
- workdir = ensure_workdir(workdir)
37
- paths_to_scan = include if include else [workdir]
38
-
39
- is_empty = is_dir_empty(workdir)
40
- if is_empty and not include:
41
- console = Console()
42
- console.print("\n[bold blue]Empty directory - will create new files as needed[/bold blue]")
43
- files_content = ""
44
- else:
45
- files_content = collect_files_content(paths_to_scan, workdir)
46
-
47
- initial_prompt = build_request_analysis_prompt(files_content, request)
48
- initial_response = progress_send_message(initial_prompt)
49
- save_to_file(initial_response, 'analysis', workdir)
50
-
51
- format_analysis(initial_response, raw, agent)
52
-
53
- handle_option_selection(initial_response, request, raw, workdir, include)
38
+
39
+
40
+ handler = RequestHandler()
41
+ handler.handle(request, preview_only)
42
+
43
+ # Save request and response to history
44
+ if agent.last_response:
45
+ save_to_history(request, agent.last_response)
janito/cli/functions.py CHANGED
@@ -1,27 +1,22 @@
1
- from typing import Optional, List
1
+ import sys
2
+ import tempfile
3
+ from datetime import datetime, timezone
2
4
  from pathlib import Path
5
+ from typing import List, Optional
6
+
7
+ import typer
3
8
  from rich.console import Console
4
- from rich.prompt import Prompt, Confirm
9
+ from rich.markdown import Markdown
5
10
  from rich.panel import Panel
11
+ from rich.prompt import Confirm, Prompt
6
12
  from rich.text import Text
7
- from rich.markdown import Markdown
8
- from datetime import datetime, timezone
9
- import tempfile
10
- import typer
11
- import sys
12
13
 
13
14
  from janito.agents import AIAgent
15
+ from janito.common import progress_send_message
14
16
  from janito.config import config
15
- from janito.scan import collect_files_content, show_content_stats
16
- from janito.analysis import (
17
- format_analysis, build_request_analysis_prompt,
18
- parse_analysis_options, get_history_file_type, AnalysisOption
19
- )
20
17
  from janito.qa import ask_question, display_answer
21
- from janito.common import progress_send_message
22
- from janito.prompts import build_selected_option_prompt
23
- from janito.fileparser import parse_block_changes
24
- from janito.change.applier import preview_and_apply_changes
18
+ from janito.workspace import collect_files_content
19
+
25
20
 
26
21
  def prompt_user(message: str, choices: List[str] = None) -> str:
27
22
  """Display a simple user prompt with optional choices"""
@@ -48,9 +43,9 @@ def get_option_selection() -> str:
48
43
 
49
44
  console.print("[red]Please enter a valid letter or 'M'[/red]")
50
45
 
51
- def get_change_history_path(workdir: Path) -> Path:
46
+ def get_change_history_path() -> Path:
52
47
  """Create and return the changes history directory path"""
53
- changes_history_dir = workdir / '.janito' / 'change_history'
48
+ changes_history_dir = config.workspace_dir / '.janito' / 'change_history'
54
49
  changes_history_dir.mkdir(parents=True, exist_ok=True)
55
50
  return changes_history_dir
56
51
 
@@ -65,9 +60,9 @@ def save_prompt_to_file(prompt: str) -> Path:
65
60
  temp_path.write_text(prompt)
66
61
  return temp_path
67
62
 
68
- def save_to_file(content: str, prefix: str, workdir: Path) -> Path:
63
+ def save_to_file(content: str, prefix: str) -> Path:
69
64
  """Save content to a timestamped file in changes history directory"""
70
- changes_history_dir = get_change_history_path(workdir)
65
+ changes_history_dir = get_change_history_path()
71
66
  timestamp = get_timestamp()
72
67
  filename = f"{timestamp}_{prefix}.txt"
73
68
  file_path = changes_history_dir / filename
@@ -100,107 +95,6 @@ def modify_request(request: str) -> str:
100
95
  console.print("\n[yellow]Modification cancelled, keeping original request[/yellow]")
101
96
  return request
102
97
 
103
- def format_option_text(option: AnalysisOption) -> str:
104
- """Format an AnalysisOption into a string representation"""
105
- option_text = f"Option {option.letter}:\n"
106
- option_text += f"Summary: {option.summary}\n\n"
107
- option_text += "Description:\n"
108
- for item in option.description_items:
109
- option_text += f"- {item}\n"
110
- option_text += "\nAffected files:\n"
111
- for file in option.affected_files:
112
- option_text += f"- {file}\n"
113
- return option_text
114
-
115
- def handle_option_selection(initial_response: str, request: str, raw: bool = False, workdir: Optional[Path] = None, include: Optional[List[Path]] = None) -> None:
116
- """Handle option selection and implementation details"""
117
- options = parse_analysis_options(initial_response)
118
- if not options:
119
- console = Console()
120
- console.print("[red]No valid options found in the response[/red]")
121
- return
122
-
123
- while True:
124
- option = get_option_selection()
125
-
126
- if option == 'M':
127
- # Use the new modify_request function for better UX
128
- new_request = modify_request(request)
129
- if new_request == request:
130
- continue
131
-
132
- # Rerun analysis with new request
133
- # Only scan files listed in the selected option's affected_files
134
- selected_option = options[option]
135
- files_to_scan = selected_option.affected_files
136
-
137
- # Convert relative paths to absolute paths
138
- absolute_paths = []
139
- for file_path in files_to_scan:
140
- # Remove (new) suffix if present
141
- clean_path = file_path.split(' (')[0].strip()
142
- path = workdir / clean_path if workdir else Path(clean_path)
143
- if path.exists():
144
- absolute_paths.append(path)
145
-
146
- files_content = collect_files_content(absolute_paths, workdir) if absolute_paths else ""
147
- show_content_stats(files_content)
148
- initial_prompt = build_request_analysis_prompt(files_content, new_request)
149
- initial_response = progress_send_message(initial_prompt)
150
- save_to_file(initial_response, 'analysis', workdir)
151
-
152
- format_analysis(initial_response, raw)
153
- options = parse_analysis_options(initial_response)
154
- if not options:
155
- console = Console()
156
- console.print("[red]No valid options found in the response[/red]")
157
- return
158
- continue
159
-
160
- if not validate_option_letter(option, options):
161
- console = Console()
162
- console.print(f"[red]Invalid option '{option}'. Valid options are: {', '.join(options.keys())} or 'M' to modify[/red]")
163
- continue
164
-
165
- break
166
-
167
- # Only scan files listed in the selected option's affected_files
168
- selected_option = options[option]
169
- files_to_scan = selected_option.affected_files
170
-
171
- # Convert relative paths to absolute paths
172
- absolute_paths = []
173
- for file_path in files_to_scan:
174
- # Remove (new) suffix if present
175
- clean_path = file_path.split(' (')[0].strip()
176
- path = workdir / clean_path if workdir else Path(clean_path)
177
- if path.exists():
178
- absolute_paths.append(path)
179
-
180
- files_content = collect_files_content(absolute_paths, workdir) if absolute_paths else ""
181
- show_content_stats(files_content)
182
-
183
- # Format the selected option before building prompt
184
- selected_option = options[option]
185
- option_text = format_option_text(selected_option)
186
-
187
- selected_prompt = build_selected_option_prompt(option_text, request, files_content)
188
- prompt_file = save_to_file(selected_prompt, 'selected', workdir)
189
- if config.verbose:
190
- print(f"\nSelected prompt saved to: {prompt_file}")
191
-
192
- selected_response = progress_send_message(selected_prompt)
193
- changes_file = save_to_file(selected_response, 'changes', workdir)
194
-
195
- if config.verbose:
196
- try:
197
- rel_path = changes_file.relative_to(workdir)
198
- print(f"\nChanges saved to: ./{rel_path}")
199
- except ValueError:
200
- print(f"\nChanges saved to: {changes_file}")
201
-
202
- changes = parse_block_changes(selected_response)
203
- preview_and_apply_changes(changes, workdir, config.test_cmd)
204
98
 
205
99
  def read_stdin() -> str:
206
100
  """Read input from stdin until EOF"""
@@ -208,79 +102,10 @@ def read_stdin() -> str:
208
102
  console.print("[dim]Enter your input (press Ctrl+D when finished):[/dim]")
209
103
  return sys.stdin.read().strip()
210
104
 
211
- def replay_saved_file(filepath: Path, workdir: Path, raw: bool = False) -> None:
212
- """Process a saved prompt file and display the response"""
213
- if not filepath.exists():
214
- raise FileNotFoundError(f"File {filepath} not found")
215
-
216
- content = filepath.read_text()
217
-
218
- # Add debug output of file content
219
- if config.debug:
220
- console = Console()
221
- console.print("\n[bold blue]Debug: File Content[/bold blue]")
222
- console.print(Panel(
223
- content,
224
- title=f"Content of {filepath.name}",
225
- border_style="blue",
226
- padding=(1, 2)
227
- ))
228
- console.print()
229
-
230
- file_type = get_history_file_type(filepath)
231
-
232
- if file_type == 'changes':
233
- changes = parse_block_changes(content)
234
- success = preview_and_apply_changes(changes, workdir, config.test_cmd)
235
- if not success:
236
- raise typer.Exit(1)
237
- elif file_type == 'analysis':
238
- format_analysis(content, raw)
239
- handle_option_selection(content, content, raw, workdir)
240
- elif file_type == 'selected':
241
- if raw:
242
- console = Console()
243
- console.print("\n=== Prompt Content ===")
244
- console.print(content)
245
- console.print("=== End Prompt Content ===\n")
246
-
247
- response = progress_send_message(content)
248
- changes_file = save_to_file(response, 'changes_', workdir)
249
- print(f"\nChanges saved to: {changes_file}")
250
-
251
- changes = parse_block_changes(response)
252
- preview_and_apply_changes(changes, workdir, config.test_cmd)
253
- else:
254
- response = progress_send_message(content)
255
- format_analysis(response, raw)
256
-
257
- def process_question(question: str, workdir: Path, include: List[Path], raw: bool, agent: AIAgent) -> None:
105
+ def process_question(question: str) -> None:
258
106
  """Process a question about the codebase"""
259
- paths_to_scan = [workdir] if workdir else []
260
- if include:
261
- paths_to_scan.extend(include)
262
- files_content = collect_files_content(paths_to_scan, workdir)
107
+ paths_to_scan = [config.workspace_dir] if not config.include else config.include
108
+ files_content = collect_files_content(paths_to_scan)
263
109
  answer = ask_question(question, files_content)
264
- display_answer(answer, raw)
110
+ display_answer(answer)
265
111
 
266
- def ensure_workdir(workdir: Path) -> Path:
267
- """Ensure working directory exists, prompt for creation if it doesn't"""
268
- if workdir.exists():
269
- return workdir.absolute()
270
-
271
- console = Console()
272
- console.print(f"\n[yellow]Directory does not exist:[/yellow] {workdir}")
273
- if Confirm.ask("Create directory?"):
274
- workdir.mkdir(parents=True)
275
- console.print(f"[green]Created directory:[/green] {workdir}")
276
- return workdir.absolute()
277
- raise typer.Exit(1)
278
-
279
- def review_text(text: str, raw: bool = False) -> None:
280
- """Review the provided text using Claude"""
281
- console = Console()
282
- response = progress_send_message(f"Please review this text and provide feedback:\n\n{text}")
283
- if raw:
284
- console.print(response)
285
- else:
286
- console.print(Markdown(response))
@@ -0,0 +1,22 @@
1
+ from pathlib import Path
2
+ from typing import Optional
3
+ from rich.console import Console
4
+ from janito.config import config
5
+ from janito.qa import ask_question, display_answer
6
+ from janito.workspace.scan import collect_files_content
7
+ from ..base import BaseCLIHandler
8
+
9
+ class AskHandler(BaseCLIHandler):
10
+ def handle(self, question: str):
11
+ """Process a question about the codebase"""
12
+ paths_to_scan = [config.workspace_dir] if not config.include else config.include
13
+ files_content = collect_files_content(paths_to_scan)
14
+
15
+ if config.tui:
16
+ answer = ask_question(question, files_content)
17
+ from janito.tui import TuiApp
18
+ app = TuiApp(content=answer)
19
+ app.run()
20
+ else:
21
+ answer = ask_question(question, files_content)
22
+ display_answer(answer)
@@ -0,0 +1,22 @@
1
+ from rich.console import Console
2
+ from janito.demo import DemoRunner
3
+ from janito.demo.data import get_demo_scenarios
4
+ from ..base import BaseCLIHandler
5
+
6
+ class DemoHandler(BaseCLIHandler):
7
+ def handle(self):
8
+ """Run demo scenarios"""
9
+ runner = DemoRunner()
10
+
11
+ # Add predefined scenarios
12
+ for scenario in get_demo_scenarios():
13
+ runner.add_scenario(scenario)
14
+
15
+ # Preview and run scenarios
16
+ self.console.print("\n[bold cyan]Demo Scenarios Preview:[/bold cyan]")
17
+ runner.preview_changes()
18
+
19
+ self.console.print("\n[bold cyan]Running Demo Scenarios:[/bold cyan]")
20
+ runner.run_all()
21
+
22
+ self.console.print("\n[green]Demo completed successfully![/green]")
@@ -0,0 +1,24 @@
1
+ from pathlib import Path
2
+ from rich.console import Console
3
+ from janito.config import config
4
+ from janito.workspace import is_dir_empty
5
+ from janito.change.core import process_change_request
6
+ from ..base import BaseCLIHandler
7
+
8
+ class RequestHandler(BaseCLIHandler):
9
+ def handle(self, request: str, preview_only: bool = False):
10
+ """Process a modification request"""
11
+ is_empty = is_dir_empty(config.workspace_dir)
12
+ if is_empty and not config.include:
13
+ self.console.print("\n[bold blue]Empty directory - will create new files as needed[/bold blue]")
14
+
15
+ success, history_file = process_change_request(request, preview_only)
16
+
17
+ if success and history_file and config.verbose:
18
+ try:
19
+ rel_path = history_file.relative_to(config.workspace_dir)
20
+ self.console.print(f"\nChanges saved to: ./{rel_path}")
21
+ except ValueError:
22
+ self.console.print(f"\nChanges saved to: {history_file}")
23
+ elif not success:
24
+ self.console.print("[red]Failed to process change request[/red]")
@@ -0,0 +1,9 @@
1
+ from pathlib import Path
2
+ from typing import List
3
+ from janito.workspace import preview_scan
4
+ from ..base import BaseCLIHandler
5
+
6
+ class ScanHandler(BaseCLIHandler):
7
+ def handle(self, paths_to_scan: List[Path]):
8
+ """Preview files that would be analyzed"""
9
+ preview_scan(paths_to_scan)
janito/cli/history.py ADDED
@@ -0,0 +1,61 @@
1
+ from pathlib import Path
2
+ from datetime import datetime, timezone
3
+ from typing import List
4
+ from rich.console import Console
5
+ from rich.table import Table
6
+ from janito.config import config
7
+
8
+ def get_history_path() -> Path:
9
+ """Get the path to the history directory"""
10
+ history_dir = config.workspace_dir / '.janito' / 'history'
11
+ history_dir.mkdir(parents=True, exist_ok=True)
12
+ return history_dir
13
+
14
+ def save_to_history(request: str, response: str) -> None:
15
+ """Save a request and its response to the history file"""
16
+ history_dir = get_history_path()
17
+ timestamp = datetime.now(timezone.utc).strftime('%Y-%m-%d_%H%M%S')
18
+ history_file = history_dir / f"{timestamp}_request.txt"
19
+
20
+ content = f"""Request: {request}
21
+ Timestamp: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')}
22
+
23
+ Response:
24
+ {response}
25
+ """
26
+ history_file.write_text(content)
27
+
28
+ def display_history() -> None:
29
+ """Display the history of requests"""
30
+ console = Console()
31
+ history_dir = get_history_path()
32
+
33
+ if not history_dir.exists():
34
+ console.print("[yellow]No history found[/yellow]")
35
+ return
36
+
37
+ table = Table(title="Request History")
38
+ table.add_column("Timestamp", style="cyan")
39
+ table.add_column("Request", style="white")
40
+ table.add_column("File", style="dim")
41
+
42
+ history_files = sorted(history_dir.glob("*_request.txt"), reverse=True)
43
+
44
+ if not history_files:
45
+ console.print("[yellow]No history found[/yellow]")
46
+ return
47
+
48
+ for history_file in history_files:
49
+ try:
50
+ content = history_file.read_text()
51
+ request_line = next(line for line in content.splitlines() if line.startswith("Request:"))
52
+ timestamp_line = next(line for line in content.splitlines() if line.startswith("Timestamp:"))
53
+
54
+ request = request_line.replace("Request:", "").strip()
55
+ timestamp = timestamp_line.replace("Timestamp:", "").strip()
56
+
57
+ table.add_row(timestamp, request, history_file.name)
58
+ except Exception as e:
59
+ console.print(f"[red]Error reading {history_file}: {e}[/red]")
60
+
61
+ console.print(table)
janito/common.py CHANGED
@@ -1,5 +1,11 @@
1
1
  from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
2
- from janito.agents import AgentSingleton
2
+ from rich.console import Console
3
+ from rich.rule import Rule
4
+ from janito.agents import agent
5
+ from .config import config
6
+ from rich import print
7
+
8
+ console = Console()
3
9
 
4
10
  def progress_send_message(message: str) -> str:
5
11
  """
@@ -11,7 +17,11 @@ def progress_send_message(message: str) -> str:
11
17
  Returns:
12
18
  The response from the AI agent
13
19
  """
14
- agent = AgentSingleton.get_agent()
20
+ if config.debug:
21
+ console.print("[yellow]======= Sending message[/yellow]")
22
+ print(message)
23
+ console.print("[yellow]======= End of message[/yellow]")
24
+
15
25
  with Progress(
16
26
  SpinnerColumn(),
17
27
  TextColumn("[progress.description]{task.description}", justify="center"),
@@ -20,4 +30,25 @@ def progress_send_message(message: str) -> str:
20
30
  task = progress.add_task("Waiting for response from AI agent...", total=None)
21
31
  response = agent.send_message(message)
22
32
  progress.update(task, completed=True)
23
- return response
33
+
34
+ if config.debug:
35
+ console.print("[yellow]======= Received response[/yellow]")
36
+ print(response)
37
+ console.print("[yellow]======= End of response[/yellow]")
38
+ response_text = response.content[0].text
39
+
40
+ # Add token usage summary with detailed cache info
41
+ usage = response.usage
42
+
43
+ # Format cache info
44
+ cache_str = "(no cache used)"
45
+ if usage.cache_creation_input_tokens or usage.cache_read_input_tokens:
46
+ create_pct = (usage.cache_creation_input_tokens / usage.input_tokens) * 100
47
+ read_pct = (usage.cache_read_input_tokens / usage.input_tokens) * 100
48
+ cache_str = f"(cached in/out: {usage.cache_creation_input_tokens}[{create_pct:.1f}%]/{usage.cache_read_input_tokens}[{read_pct:.1f}%])"
49
+
50
+ percentage = (usage.output_tokens / usage.input_tokens) * 100
51
+ usage_text = f"Tokens: {usage.input_tokens} sent {cache_str}, {usage.output_tokens} received ({percentage:.1f}% ratio)"
52
+ console.print(Rule(usage_text, style="blue", align="center"))
53
+
54
+ return response_text
janito/config.py CHANGED
@@ -1,30 +1,38 @@
1
- from typing import Optional
1
+ from typing import Optional, List
2
2
  import os
3
+ from pathlib import Path
3
4
 
4
5
  class ConfigManager:
5
6
  _instance = None
6
-
7
+
7
8
  def __init__(self):
8
9
  self.debug = False
9
10
  self.verbose = False
10
11
  self.debug_line = None
11
12
  self.test_cmd = os.getenv('JANITO_TEST_CMD')
12
-
13
+ self.workspace_dir = Path.cwd()
14
+ self.raw = False
15
+ self.include: List[Path] = []
16
+ self.recursive: List[Path] = []
17
+ self.auto_apply: bool = False
18
+ self.tui: bool = False
19
+ self.skipwork: bool = False
20
+
13
21
  @classmethod
14
22
  def get_instance(cls) -> "ConfigManager":
15
23
  if cls._instance is None:
16
24
  cls._instance = cls()
17
25
  return cls._instance
18
-
26
+
19
27
  def set_debug(self, enabled: bool) -> None:
20
28
  self.debug = enabled
21
29
 
22
30
  def set_verbose(self, enabled: bool) -> None:
23
31
  self.verbose = enabled
24
-
32
+
25
33
  def set_debug_line(self, line: Optional[int]) -> None:
26
34
  self.debug_line = line
27
-
35
+
28
36
  def should_debug_line(self, line: int) -> bool:
29
37
  """Return True if we should show debug for this line number"""
30
38
  return self.debug and (self.debug_line is None or self.debug_line == line)
@@ -33,5 +41,62 @@ class ConfigManager:
33
41
  """Set the test command, overriding environment variable"""
34
42
  self.test_cmd = cmd if cmd is not None else os.getenv('JANITO_TEST_CMD')
35
43
 
44
+ def set_workspace_dir(self, path: Optional[Path]) -> None:
45
+ """Set the workspace directory"""
46
+ self.workspace_dir = path if path is not None else Path.cwd()
47
+
48
+ def set_raw(self, enabled: bool) -> None:
49
+ """Set raw output mode"""
50
+ self.raw = enabled
51
+
52
+ def set_include(self, paths: Optional[List[Path]]) -> None:
53
+ """
54
+ Set additional paths to include.
55
+
56
+ Args:
57
+ paths: List of paths to include
58
+
59
+ Raises:
60
+ ValueError: If duplicate paths are provided
61
+ """
62
+ if paths is None:
63
+ self.include = []
64
+ return
65
+
66
+ # Convert paths to absolute and resolve symlinks
67
+ resolved_paths = [p.absolute().resolve() for p in paths]
68
+
69
+ # Check for duplicates
70
+ seen_paths = set()
71
+ unique_paths = []
72
+
73
+ for path in resolved_paths:
74
+ if path in seen_paths:
75
+ raise ValueError(f"Duplicate path provided: {path}")
76
+ seen_paths.add(path)
77
+ unique_paths.append(path)
78
+
79
+ self.include = unique_paths
80
+
81
+ def set_auto_apply(self, enabled: bool) -> None:
82
+ """Set auto apply mode"""
83
+ self.auto_apply = enabled
84
+
85
+ def set_tui(self, enabled: bool) -> None:
86
+ """Set TUI mode"""
87
+ self.tui = enabled
88
+
89
+ def set_recursive(self, paths: Optional[List[Path]]) -> None:
90
+ """Set paths to scan recursively
91
+
92
+ Args:
93
+ paths: List of directory paths to scan recursively, or None to disable recursive scanning
94
+ """
95
+ self.recursive = paths
96
+
97
+ def set_skipwork(self, enabled: bool) -> None:
98
+ """Set skipwork flag to skip scanning workspace_dir"""
99
+ self.skipwork = enabled
100
+
36
101
  # Create a singleton instance
37
102
  config = ConfigManager.get_instance()
@@ -0,0 +1,4 @@
1
+ from .runner import DemoRunner
2
+ from .scenarios import DemoScenario
3
+
4
+ __all__ = ['DemoRunner', 'DemoScenario']
janito/demo/data.py ADDED
@@ -0,0 +1,13 @@
1
+ from typing import Dict, List
2
+ from .scenarios import DemoScenario
3
+ from .mock_data import get_mock_changes
4
+
5
+ def get_demo_scenarios() -> List[DemoScenario]:
6
+ """Get list of predefined demo scenarios"""
7
+ return [
8
+ DemoScenario(
9
+ name="File Operations Demo",
10
+ description="Demonstrate various file operations with change viewer",
11
+ changes=get_mock_changes()
12
+ )
13
+ ]