janito 0.6.0__py3-none-any.whl → 0.8.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 (116) hide show
  1. janito/__main__.py +127 -134
  2. janito/agents/__init__.py +22 -16
  3. janito/agents/agent.py +24 -20
  4. janito/agents/claudeai.py +41 -55
  5. janito/agents/deepseekai.py +47 -0
  6. janito/change/applied_blocks.py +34 -0
  7. janito/change/applier.py +167 -0
  8. janito/change/edit_blocks.py +148 -0
  9. janito/change/finder.py +72 -0
  10. janito/change/request.py +144 -0
  11. janito/change/validator.py +87 -251
  12. janito/change/view/content.py +63 -0
  13. janito/change/{viewer → view}/diff.py +44 -43
  14. janito/change/view/panels.py +201 -0
  15. janito/change/view/sections.py +69 -0
  16. janito/change/view/styling.py +140 -0
  17. janito/change/view/summary.py +37 -0
  18. janito/change/{viewer → view}/themes.py +62 -55
  19. janito/change/view/viewer.py +59 -0
  20. janito/cli/__init__.py +1 -1
  21. janito/cli/commands.py +68 -45
  22. janito/cli/functions.py +66 -111
  23. janito/common.py +132 -53
  24. janito/config.py +99 -101
  25. janito/data/change_prompt.txt +81 -0
  26. janito/data/system_prompt.txt +3 -0
  27. janito/qa.py +56 -66
  28. janito/version.py +22 -22
  29. janito/workspace/__init__.py +8 -7
  30. janito/workspace/analysis.py +120 -120
  31. janito/workspace/models.py +97 -0
  32. janito/workspace/show.py +115 -0
  33. janito/workspace/stats.py +42 -0
  34. janito/workspace/workset.py +135 -0
  35. janito/workspace/workspace.py +335 -0
  36. janito-0.8.0.dist-info/METADATA +106 -0
  37. janito-0.8.0.dist-info/RECORD +40 -0
  38. {janito-0.6.0.dist-info → janito-0.8.0.dist-info}/licenses/LICENSE +20 -20
  39. janito/__init__.py +0 -2
  40. janito/agents/openai.py +0 -53
  41. janito/agents/test.py +0 -34
  42. janito/change/__init__.py +0 -32
  43. janito/change/__main__.py +0 -0
  44. janito/change/analysis/__init__.py +0 -23
  45. janito/change/analysis/__main__.py +0 -7
  46. janito/change/analysis/analyze.py +0 -61
  47. janito/change/analysis/formatting.py +0 -78
  48. janito/change/analysis/options.py +0 -81
  49. janito/change/analysis/prompts.py +0 -98
  50. janito/change/analysis/view/__init__.py +0 -9
  51. janito/change/analysis/view/terminal.py +0 -171
  52. janito/change/applier/__init__.py +0 -5
  53. janito/change/applier/file.py +0 -58
  54. janito/change/applier/main.py +0 -156
  55. janito/change/applier/text.py +0 -245
  56. janito/change/applier/workspace_dir.py +0 -58
  57. janito/change/core.py +0 -131
  58. janito/change/history.py +0 -44
  59. janito/change/operations.py +0 -7
  60. janito/change/parser.py +0 -289
  61. janito/change/play.py +0 -54
  62. janito/change/preview.py +0 -82
  63. janito/change/prompts.py +0 -126
  64. janito/change/test.py +0 -0
  65. janito/change/viewer/__init__.py +0 -11
  66. janito/change/viewer/content.py +0 -66
  67. janito/change/viewer/pager.py +0 -56
  68. janito/change/viewer/panels.py +0 -555
  69. janito/change/viewer/styling.py +0 -103
  70. janito/clear_statement_parser/clear_statement_format.txt +0 -328
  71. janito/clear_statement_parser/examples.txt +0 -326
  72. janito/clear_statement_parser/models.py +0 -104
  73. janito/clear_statement_parser/parser.py +0 -496
  74. janito/cli/base.py +0 -30
  75. janito/cli/handlers/ask.py +0 -22
  76. janito/cli/handlers/demo.py +0 -22
  77. janito/cli/handlers/request.py +0 -24
  78. janito/cli/handlers/scan.py +0 -9
  79. janito/cli/history.py +0 -61
  80. janito/cli/registry.py +0 -26
  81. janito/demo/__init__.py +0 -4
  82. janito/demo/data.py +0 -13
  83. janito/demo/mock_data.py +0 -20
  84. janito/demo/operations.py +0 -45
  85. janito/demo/runner.py +0 -59
  86. janito/demo/scenarios.py +0 -32
  87. janito/prompts.py +0 -2
  88. janito/review.py +0 -13
  89. janito/search_replace/README.md +0 -146
  90. janito/search_replace/__init__.py +0 -6
  91. janito/search_replace/__main__.py +0 -21
  92. janito/search_replace/core.py +0 -119
  93. janito/search_replace/parser.py +0 -52
  94. janito/search_replace/play.py +0 -61
  95. janito/search_replace/replacer.py +0 -36
  96. janito/search_replace/searcher.py +0 -299
  97. janito/shell/__init__.py +0 -39
  98. janito/shell/bus.py +0 -31
  99. janito/shell/commands.py +0 -195
  100. janito/shell/handlers.py +0 -122
  101. janito/shell/history.py +0 -20
  102. janito/shell/processor.py +0 -52
  103. janito/tui/__init__.py +0 -21
  104. janito/tui/base.py +0 -22
  105. janito/tui/flows/__init__.py +0 -5
  106. janito/tui/flows/changes.py +0 -65
  107. janito/tui/flows/content.py +0 -128
  108. janito/tui/flows/selection.py +0 -117
  109. janito/tui/screens/__init__.py +0 -3
  110. janito/tui/screens/app.py +0 -1
  111. janito/workspace/manager.py +0 -48
  112. janito/workspace/scan.py +0 -232
  113. janito-0.6.0.dist-info/METADATA +0 -185
  114. janito-0.6.0.dist-info/RECORD +0 -95
  115. {janito-0.6.0.dist-info → janito-0.8.0.dist-info}/WHEEL +0 -0
  116. {janito-0.6.0.dist-info → janito-0.8.0.dist-info}/entry_points.txt +0 -0
janito/cli/commands.py CHANGED
@@ -1,45 +1,68 @@
1
- from pathlib import Path
2
- from typing import Optional, List
3
- from rich.console import Console
4
-
5
- from janito.agents import AIAgent
6
- from janito.workspace.analysis import analyze_workspace_content
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
12
-
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
18
-
19
- def handle_ask(question: str):
20
- """Ask a question about the codebase"""
21
- handler = AskHandler()
22
- handler.handle(question)
23
-
24
- def handle_scan(paths_to_scan: List[Path]):
25
- """Preview files that would be analyzed"""
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)
31
-
32
- def handle_play(filepath: Path):
33
- """Replay a saved changes or debug file"""
34
- play_saved_changes(filepath)
35
-
36
- def handle_request(request: str, preview_only: bool = False):
37
- """Process modification request"""
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)
1
+ from pathlib import Path
2
+ from rich.console import Console
3
+ from janito.agents import agent
4
+
5
+ from janito.workspace import workset
6
+ from janito.config import config
7
+ from janito.qa import ask_question, display_answer
8
+ from janito.change.request import request_change, replay_saved_response
9
+
10
+
11
+ console = Console()
12
+
13
+ def handle_ask(question: str):
14
+ """Process a question about the codebase
15
+
16
+ Args:
17
+ question: The question to ask about the codebase
18
+ workset: Optional Workset instance for scoped operations
19
+ """
20
+ answer = ask_question(question)
21
+ display_answer(answer)
22
+
23
+ def handle_scan():
24
+ """Preview files that would be analyzed"""
25
+ workset.show()
26
+
27
+
28
+
29
+ def is_dir_empty(path: Path) -> bool:
30
+ """Check if directory is empty or only contains empty directories."""
31
+ if not path.is_dir():
32
+ return False
33
+
34
+ for item in path.iterdir():
35
+ if item.name.startswith(('.', '__pycache__')):
36
+ continue
37
+ if item.is_file():
38
+ return False
39
+ if item.is_dir() and not is_dir_empty(item):
40
+ return False
41
+ return True
42
+
43
+ def handle_request(request: str = None, replay: bool = False):
44
+ """Process modification request
45
+
46
+ Args:
47
+ request: The modification request to process
48
+ replay: If True, triggers the replay response flow
49
+ """
50
+ if not request and not replay:
51
+ return
52
+
53
+ is_empty = is_dir_empty(config.workspace_dir)
54
+ if is_empty:
55
+ console.print("\n[bold blue]Empty directory - will create new files as needed[/bold blue]")
56
+
57
+ if replay:
58
+ replay_saved_response()
59
+ else:
60
+ request_change(request)
61
+
62
+
63
+ # Command handler functions
64
+ COMMANDS = {
65
+ 'ask': handle_ask,
66
+ 'scan': handle_scan,
67
+ 'request': handle_request
68
+ }
janito/cli/functions.py CHANGED
@@ -1,111 +1,66 @@
1
- import sys
2
- import tempfile
3
- from datetime import datetime, timezone
4
- from pathlib import Path
5
- from typing import List, Optional
6
-
7
- import typer
8
- from rich.console import Console
9
- from rich.markdown import Markdown
10
- from rich.panel import Panel
11
- from rich.prompt import Confirm, Prompt
12
- from rich.text import Text
13
-
14
- from janito.agents import AIAgent
15
- from janito.common import progress_send_message
16
- from janito.config import config
17
- from janito.qa import ask_question, display_answer
18
- from janito.workspace import collect_files_content
19
-
20
-
21
- def prompt_user(message: str, choices: List[str] = None) -> str:
22
- """Display a simple user prompt with optional choices"""
23
- console = Console()
24
-
25
- if choices:
26
- console.print(f"\n[cyan]Options: {', '.join(choices)}[/cyan]")
27
-
28
- return Prompt.ask(f"[bold cyan]> {message}[/bold cyan]")
29
-
30
- def validate_option_letter(letter: str, options: dict) -> bool:
31
- """Validate if the given letter is a valid option or 'M' for modify"""
32
- return letter.upper() in options or letter.upper() == 'M'
33
-
34
- def get_option_selection() -> str:
35
- """Get user input for option selection"""
36
- console = Console()
37
- console.print("\n[cyan]Enter option letter or 'M' to modify request[/cyan]")
38
-
39
- while True:
40
- letter = Prompt.ask("[bold cyan]Select option[/bold cyan]").strip().upper()
41
- if letter == 'M' or (letter.isalpha() and len(letter) == 1):
42
- return letter
43
-
44
- console.print("[red]Please enter a valid letter or 'M'[/red]")
45
-
46
- def get_change_history_path() -> Path:
47
- """Create and return the changes history directory path"""
48
- changes_history_dir = config.workspace_dir / '.janito' / 'change_history'
49
- changes_history_dir.mkdir(parents=True, exist_ok=True)
50
- return changes_history_dir
51
-
52
- def get_timestamp() -> str:
53
- """Get current UTC timestamp in YMD_HMS format with leading zeros"""
54
- return datetime.now(timezone.utc).strftime('%Y%m%d_%H%M%S')
55
-
56
- def save_prompt_to_file(prompt: str) -> Path:
57
- """Save prompt to a named temporary file that won't be deleted"""
58
- temp_file = tempfile.NamedTemporaryFile(prefix='selected_', suffix='.txt', delete=False)
59
- temp_path = Path(temp_file.name)
60
- temp_path.write_text(prompt)
61
- return temp_path
62
-
63
- def save_to_file(content: str, prefix: str) -> Path:
64
- """Save content to a timestamped file in changes history directory"""
65
- changes_history_dir = get_change_history_path()
66
- timestamp = get_timestamp()
67
- filename = f"{timestamp}_{prefix}.txt"
68
- file_path = changes_history_dir / filename
69
- file_path.write_text(content)
70
- return file_path
71
-
72
- def modify_request(request: str) -> str:
73
- """Display current request and get modified version with improved formatting"""
74
- console = Console()
75
-
76
- # Display current request in a panel with clear formatting
77
- console.print("\n[bold cyan]Current Request:[/bold cyan]")
78
- console.print(Panel(
79
- Text(request, style="white"),
80
- border_style="blue",
81
- title="Previous Request",
82
- padding=(1, 2)
83
- ))
84
-
85
- # Get modified request with clear prompt
86
- console.print("\n[bold cyan]Enter modified request below:[/bold cyan]")
87
- console.print("[dim](Press Enter to submit, Ctrl+C to cancel)[/dim]")
88
- try:
89
- new_request = prompt_user("Modified request")
90
- if not new_request.strip():
91
- console.print("[yellow]No changes made, keeping original request[/yellow]")
92
- return request
93
- return new_request
94
- except KeyboardInterrupt:
95
- console.print("\n[yellow]Modification cancelled, keeping original request[/yellow]")
96
- return request
97
-
98
-
99
- def read_stdin() -> str:
100
- """Read input from stdin until EOF"""
101
- console = Console()
102
- console.print("[dim]Enter your input (press Ctrl+D when finished):[/dim]")
103
- return sys.stdin.read().strip()
104
-
105
- def process_question(question: str) -> None:
106
- """Process a question about the codebase"""
107
- paths_to_scan = [config.workspace_dir] if not config.include else config.include
108
- files_content = collect_files_content(paths_to_scan)
109
- answer = ask_question(question, files_content)
110
- display_answer(answer)
111
-
1
+ import tempfile
2
+ from datetime import datetime, timezone
3
+ from pathlib import Path
4
+ from typing import List, Optional
5
+ from janito.shell.user_prompt import prompt_user
6
+ from rich.console import Console
7
+ from rich.prompt import Prompt
8
+ from rich.panel import Panel
9
+ from rich.text import Text
10
+
11
+ console = Console()
12
+
13
+ from janito.config import config
14
+
15
+
16
+ def get_change_history_path() -> Path:
17
+ """Create and return the changes history directory path"""
18
+ changes_history_dir = config.workspace_dir / '.janito' / 'change_history'
19
+ changes_history_dir.mkdir(parents=True, exist_ok=True)
20
+ return changes_history_dir
21
+
22
+ def get_timestamp() -> str:
23
+ """Get current UTC timestamp in YMD_HMS format with leading zeros"""
24
+ return datetime.now(timezone.utc).strftime('%Y%m%d_%H%M%S')
25
+
26
+ def save_prompt_to_file(prompt: str) -> Path:
27
+ """Save prompt to a named temporary file that won't be deleted"""
28
+ temp_file = tempfile.NamedTemporaryFile(prefix='selected_', suffix='.txt', delete=False)
29
+ temp_path = Path(temp_file.name)
30
+ temp_path.write_text(prompt, encoding='utf-8')
31
+ return temp_path
32
+
33
+ def save_to_file(content: str, prefix: str) -> Path:
34
+ """Save content to a timestamped file in changes history directory"""
35
+ changes_history_dir = get_change_history_path()
36
+ timestamp = get_timestamp()
37
+ filename = f"{timestamp}_{prefix}.txt"
38
+ file_path = changes_history_dir / filename
39
+ file_path.write_text(content)
40
+ return file_path
41
+
42
+ def modify_request(request: str) -> str:
43
+ """Display current request and get modified version with improved formatting"""
44
+ console = Console()
45
+
46
+ # Display current request in a panel with clear formatting
47
+ console.print("\n[bold cyan]Current Request:[/bold cyan]")
48
+ console.print(Panel(
49
+ Text(request, style="white"),
50
+ border_style="blue",
51
+ title="Previous Request",
52
+ padding=(1, 2)
53
+ ))
54
+
55
+ # Get modified request with clear prompt
56
+ console.print("\n[bold cyan]Enter modified request below:[/bold cyan]")
57
+ console.print("[dim](Press Enter to submit, Ctrl+C to cancel)[/dim]")
58
+ try:
59
+ new_request = prompt_user("Modified request")
60
+ if not new_request.strip():
61
+ console.print("[yellow]No changes made, keeping original request[/yellow]")
62
+ return request
63
+ return new_request
64
+ except KeyboardInterrupt:
65
+ console.print("\n[yellow]Modification cancelled, keeping original request[/yellow]")
66
+ return request
janito/common.py CHANGED
@@ -1,54 +1,133 @@
1
- from rich.progress import Progress, SpinnerColumn, TextColumn, TimeElapsedColumn
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()
9
-
10
- def progress_send_message(message: str) -> str:
11
- """
12
- Send a message to the AI agent with a progress indicator and elapsed time.
13
-
14
- Args:
15
- message: The message to send
16
-
17
- Returns:
18
- The response from the AI agent
19
- """
20
- if config.debug:
21
- console.print("[yellow]======= Sending message[/yellow]")
22
- print(message)
23
- console.print("[yellow]======= End of message[/yellow]")
24
-
25
- with Progress(
26
- SpinnerColumn(),
27
- TextColumn("[progress.description]{task.description}", justify="center"),
28
- TimeElapsedColumn(),
29
- ) as progress:
30
- task = progress.add_task("Waiting for response from AI agent...", total=None)
31
- response = agent.send_message(message)
32
- progress.update(task, completed=True)
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
-
1
+ from datetime import datetime
2
+ from rich.live import Live
3
+ from rich.text import Text
4
+ from rich.console import Console
5
+ from rich.rule import Rule
6
+ from threading import Thread
7
+ from janito.agents import agent
8
+ from .config import config
9
+ from typing import Optional, List
10
+ import importlib.resources
11
+ from pathlib import Path
12
+
13
+
14
+ console = Console()
15
+
16
+
17
+ def _get_system_prompt() -> str:
18
+ """Get the system prompt from the package data or local file."""
19
+ try:
20
+ # First try to read from package data
21
+ with importlib.resources.files('janito.data').joinpath('system_prompt.txt').open('r') as f:
22
+ return f.read()
23
+ except Exception:
24
+ # Fallback to local file for development
25
+ local_path = Path(__file__).parent / 'data' / 'system_prompt.txt'
26
+ if local_path.exists():
27
+ return local_path.read_text()
28
+ raise FileNotFoundError("Could not find system_prompt.txt")
29
+
30
+
31
+ def progress_send_message(message: str) -> Optional[str]:
32
+ """Send a message to the AI agent with progress indication.
33
+
34
+ Displays a progress spinner while waiting for the agent's response and shows
35
+ token usage statistics after receiving the response. Uses a background thread
36
+ to update the elapsed time display.
37
+
38
+ Args:
39
+ system_message: The system message to send to the AI agent
40
+ message: The message to send to the AI agent
41
+
42
+ Returns:
43
+ Optional[str]: The response text from the AI agent, or None if interrupted
44
+
45
+ Note:
46
+ - Returns None if the operation is cancelled via Ctrl+C
47
+ - If the request fails, raises the original exception
48
+ """
49
+ system_message = _get_system_prompt()
50
+
51
+ if config.debug:
52
+ console.print(f"[yellow]======= Sending message via {agent.__class__.__name__.replace('AIAgent', '')}[/yellow]")
53
+ print(system_message)
54
+ print(message)
55
+ console.print("[yellow]======= End of message[/yellow]")
56
+
57
+ start_time = datetime.now()
58
+
59
+
60
+ response = None
61
+ error = None
62
+
63
+ def agent_thread():
64
+ nonlocal response, error
65
+ try:
66
+ response = agent.send_message(system_message=system_message, message=message)
67
+ except Exception as e:
68
+ error = e
69
+
70
+ agent_thread = Thread(target=agent_thread, daemon=True)
71
+ agent_thread.start()
72
+
73
+ try:
74
+ with Live(Text("Waiting for response from AI agent...", justify="center"), refresh_per_second=4) as live:
75
+ while agent_thread.is_alive():
76
+ elapsed = datetime.now() - start_time
77
+ elapsed_seconds = elapsed.seconds
78
+ elapsed_minutes = elapsed_seconds // 60
79
+ remaining_seconds = elapsed_seconds % 60
80
+ time_str = f"{elapsed_minutes}m{remaining_seconds}s" if elapsed_minutes > 0 else f"{elapsed_seconds}s"
81
+ live.update(Text.assemble(
82
+ f"Waiting for {agent.friendly_name} response... (",
83
+ (time_str, "magenta"),
84
+ ")",
85
+ justify="center"
86
+ ))
87
+ agent_thread.join(timeout=0.25)
88
+
89
+ # Calculate final stats
90
+ elapsed = datetime.now() - start_time
91
+ elapsed_seconds = elapsed.seconds
92
+ elapsed_minutes = elapsed_seconds // 60
93
+ remaining_seconds = elapsed_seconds % 60
94
+ time_str = f"{elapsed_minutes}m{remaining_seconds}s" if elapsed_minutes > 0 else f"{elapsed_seconds}s"
95
+
96
+ if hasattr(response, 'usage'):
97
+ usage = response.usage
98
+ # Get total input tokens including cache if available
99
+ total_input = (
100
+ getattr(usage, 'input_tokens', 0) +
101
+ getattr(usage, 'cache_creation_input_tokens', 0) +
102
+ getattr(usage, 'cache_read_input_tokens', 0)
103
+ )
104
+ output_tokens = getattr(usage, 'output_tokens', 0)
105
+
106
+ # Update final message with stats
107
+ stats_text = f"Got response from {agent.friendly_name} after {time_str} • [cyan]In:[/] [bold green]{total_input:,}[/] [cyan]Out:[/] [bold yellow]{output_tokens:,}[/]"
108
+ live.update(Rule(stats_text))
109
+
110
+ except KeyboardInterrupt:
111
+ console.print("\n[yellow]Operation cancelled[/yellow]")
112
+ return None
113
+
114
+ if error:
115
+ if isinstance(error, KeyboardInterrupt):
116
+ console.print("\n[yellow]Operation cancelled[/yellow]")
117
+ return None
118
+ raise error
119
+
120
+ if config.debug:
121
+ console.print("[yellow]======= Received response[/yellow]")
122
+ print(response.content[0].text)
123
+ console.print("[yellow]======= End of response[/yellow]")
124
+
125
+ # Extract response text based on response type
126
+ if hasattr(response, 'choices'):
127
+ response_text = response.choices[0].message.content
128
+ elif hasattr(response, 'content'):
129
+ response_text = response.content[0].text
130
+ else:
131
+ response_text = str(response)
132
+
54
133
  return response_text