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.
- janito/__main__.py +127 -134
- janito/agents/__init__.py +22 -16
- janito/agents/agent.py +24 -20
- janito/agents/claudeai.py +41 -55
- janito/agents/deepseekai.py +47 -0
- janito/change/applied_blocks.py +34 -0
- janito/change/applier.py +167 -0
- janito/change/edit_blocks.py +148 -0
- janito/change/finder.py +72 -0
- janito/change/request.py +144 -0
- janito/change/validator.py +87 -251
- janito/change/view/content.py +63 -0
- janito/change/{viewer → view}/diff.py +44 -43
- janito/change/view/panels.py +201 -0
- janito/change/view/sections.py +69 -0
- janito/change/view/styling.py +140 -0
- janito/change/view/summary.py +37 -0
- janito/change/{viewer → view}/themes.py +62 -55
- janito/change/view/viewer.py +59 -0
- janito/cli/__init__.py +1 -1
- janito/cli/commands.py +68 -45
- janito/cli/functions.py +66 -111
- janito/common.py +132 -53
- janito/config.py +99 -101
- janito/data/change_prompt.txt +81 -0
- janito/data/system_prompt.txt +3 -0
- janito/qa.py +56 -66
- janito/version.py +22 -22
- janito/workspace/__init__.py +8 -7
- janito/workspace/analysis.py +120 -120
- janito/workspace/models.py +97 -0
- janito/workspace/show.py +115 -0
- janito/workspace/stats.py +42 -0
- janito/workspace/workset.py +135 -0
- janito/workspace/workspace.py +335 -0
- janito-0.8.0.dist-info/METADATA +106 -0
- janito-0.8.0.dist-info/RECORD +40 -0
- {janito-0.6.0.dist-info → janito-0.8.0.dist-info}/licenses/LICENSE +20 -20
- janito/__init__.py +0 -2
- janito/agents/openai.py +0 -53
- janito/agents/test.py +0 -34
- janito/change/__init__.py +0 -32
- janito/change/__main__.py +0 -0
- janito/change/analysis/__init__.py +0 -23
- janito/change/analysis/__main__.py +0 -7
- janito/change/analysis/analyze.py +0 -61
- janito/change/analysis/formatting.py +0 -78
- janito/change/analysis/options.py +0 -81
- janito/change/analysis/prompts.py +0 -98
- janito/change/analysis/view/__init__.py +0 -9
- janito/change/analysis/view/terminal.py +0 -171
- janito/change/applier/__init__.py +0 -5
- janito/change/applier/file.py +0 -58
- janito/change/applier/main.py +0 -156
- janito/change/applier/text.py +0 -245
- janito/change/applier/workspace_dir.py +0 -58
- janito/change/core.py +0 -131
- janito/change/history.py +0 -44
- janito/change/operations.py +0 -7
- janito/change/parser.py +0 -289
- janito/change/play.py +0 -54
- janito/change/preview.py +0 -82
- janito/change/prompts.py +0 -126
- janito/change/test.py +0 -0
- janito/change/viewer/__init__.py +0 -11
- janito/change/viewer/content.py +0 -66
- janito/change/viewer/pager.py +0 -56
- janito/change/viewer/panels.py +0 -555
- janito/change/viewer/styling.py +0 -103
- janito/clear_statement_parser/clear_statement_format.txt +0 -328
- janito/clear_statement_parser/examples.txt +0 -326
- janito/clear_statement_parser/models.py +0 -104
- janito/clear_statement_parser/parser.py +0 -496
- janito/cli/base.py +0 -30
- 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/cli/history.py +0 -61
- janito/cli/registry.py +0 -26
- janito/demo/__init__.py +0 -4
- janito/demo/data.py +0 -13
- janito/demo/mock_data.py +0 -20
- janito/demo/operations.py +0 -45
- janito/demo/runner.py +0 -59
- janito/demo/scenarios.py +0 -32
- janito/prompts.py +0 -2
- janito/review.py +0 -13
- janito/search_replace/README.md +0 -146
- janito/search_replace/__init__.py +0 -6
- janito/search_replace/__main__.py +0 -21
- janito/search_replace/core.py +0 -119
- janito/search_replace/parser.py +0 -52
- janito/search_replace/play.py +0 -61
- janito/search_replace/replacer.py +0 -36
- janito/search_replace/searcher.py +0 -299
- janito/shell/__init__.py +0 -39
- janito/shell/bus.py +0 -31
- janito/shell/commands.py +0 -195
- janito/shell/handlers.py +0 -122
- janito/shell/history.py +0 -20
- janito/shell/processor.py +0 -52
- janito/tui/__init__.py +0 -21
- janito/tui/base.py +0 -22
- janito/tui/flows/__init__.py +0 -5
- janito/tui/flows/changes.py +0 -65
- janito/tui/flows/content.py +0 -128
- janito/tui/flows/selection.py +0 -117
- janito/tui/screens/__init__.py +0 -3
- janito/tui/screens/app.py +0 -1
- janito/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/RECORD +0 -95
- {janito-0.6.0.dist-info → janito-0.8.0.dist-info}/WHEEL +0 -0
- {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
|
3
|
-
from
|
4
|
-
|
5
|
-
from janito.
|
6
|
-
from janito.
|
7
|
-
from janito.
|
8
|
-
from janito.change.
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
2
|
-
import
|
3
|
-
from
|
4
|
-
from
|
5
|
-
from
|
6
|
-
|
7
|
-
import
|
8
|
-
from rich.
|
9
|
-
from rich.
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
"
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
"
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
2
|
-
from rich.
|
3
|
-
from rich.
|
4
|
-
from
|
5
|
-
from .
|
6
|
-
from
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|