janito 0.10.1__py3-none-any.whl → 0.12.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- janito/__init__.py +1 -1
- janito/__main__.py +3 -147
- janito/callbacks.py +13 -109
- janito/cli/__init__.py +6 -0
- janito/cli/agent.py +287 -0
- janito/cli/app.py +86 -0
- janito/cli/commands.py +329 -0
- janito/cli/output.py +29 -0
- janito/cli/utils.py +22 -0
- janito/config.py +338 -63
- janito/data/instructions_template.txt +27 -0
- janito/token_report.py +124 -43
- janito/tools/__init__.py +29 -1
- janito/tools/bash/bash.py +82 -0
- janito/tools/bash/unix_persistent_bash.py +182 -0
- janito/tools/bash/win_persistent_bash.py +306 -0
- janito/tools/decorators.py +90 -84
- janito/tools/delete_file.py +65 -44
- janito/tools/fetch_webpage/__init__.py +34 -0
- janito/tools/fetch_webpage/chunking.py +76 -0
- janito/tools/fetch_webpage/core.py +155 -0
- janito/tools/fetch_webpage/extractors.py +276 -0
- janito/tools/fetch_webpage/news.py +137 -0
- janito/tools/fetch_webpage/utils.py +108 -0
- janito/tools/find_files.py +108 -42
- janito/tools/move_file.py +72 -0
- janito/tools/prompt_user.py +57 -0
- janito/tools/replace_file.py +63 -0
- janito/tools/rich_console.py +139 -0
- janito/tools/search_text.py +33 -21
- janito/tools/str_replace_editor/editor.py +55 -43
- janito/tools/str_replace_editor/handlers/__init__.py +16 -0
- janito/tools/str_replace_editor/handlers/create.py +60 -0
- janito/tools/str_replace_editor/handlers/insert.py +100 -0
- janito/tools/str_replace_editor/handlers/str_replace.py +92 -0
- janito/tools/str_replace_editor/handlers/undo.py +64 -0
- janito/tools/str_replace_editor/handlers/view.py +153 -0
- janito/tools/str_replace_editor/utils.py +7 -62
- janito/tools/usage_tracker.py +136 -0
- janito-0.12.0.dist-info/METADATA +203 -0
- janito-0.12.0.dist-info/RECORD +47 -0
- janito/cli.py +0 -202
- janito/data/instructions.txt +0 -4
- janito/tools/str_replace_editor/handlers.py +0 -338
- janito-0.10.1.dist-info/METADATA +0 -86
- janito-0.10.1.dist-info/RECORD +0 -23
- {janito-0.10.1.dist-info → janito-0.12.0.dist-info}/WHEEL +0 -0
- {janito-0.10.1.dist-info → janito-0.12.0.dist-info}/entry_points.txt +0 -0
- {janito-0.10.1.dist-info → janito-0.12.0.dist-info}/licenses/LICENSE +0 -0
janito/__init__.py
CHANGED
janito/__main__.py
CHANGED
@@ -1,151 +1,7 @@
|
|
1
1
|
"""
|
2
|
-
Main entry point for
|
2
|
+
Main entry point for Janito.
|
3
3
|
"""
|
4
|
-
|
5
|
-
import typer
|
6
|
-
import os
|
7
|
-
import sys
|
8
|
-
from pathlib import Path
|
9
|
-
from typing import Optional, Dict, Any, List
|
10
|
-
from rich.console import Console
|
11
|
-
from rich import print as rprint
|
12
|
-
from rich.markdown import Markdown
|
13
|
-
import claudine
|
14
|
-
from claudine.exceptions import MaxTokensExceededException, MaxRoundsExceededException
|
15
|
-
import locale
|
16
|
-
|
17
|
-
# Fix console encoding for Windows
|
18
|
-
if sys.platform == 'win32':
|
19
|
-
# Try to set UTF-8 mode for Windows 10 version 1903 or newer
|
20
|
-
os.system('chcp 65001 > NUL')
|
21
|
-
# Ensure stdout and stderr are using UTF-8
|
22
|
-
sys.stdout.reconfigure(encoding='utf-8')
|
23
|
-
sys.stderr.reconfigure(encoding='utf-8')
|
24
|
-
# Set locale to UTF-8
|
25
|
-
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
|
26
|
-
|
27
|
-
from janito.tools.str_replace_editor.editor import str_replace_editor
|
28
|
-
from janito.tools.find_files import find_files
|
29
|
-
from janito.tools.delete_file import delete_file
|
30
|
-
from janito.tools.search_text import search_text
|
31
|
-
from janito.config import get_config
|
32
|
-
from janito.callbacks import pre_tool_callback, post_tool_callback
|
33
|
-
from janito.token_report import generate_token_report
|
34
|
-
|
35
|
-
app = typer.Typer(help="Janito CLI tool")
|
36
|
-
|
37
|
-
@app.command()
|
38
|
-
def hello(name: str = typer.Argument("World", help="Name to greet")):
|
39
|
-
"""
|
40
|
-
Say hello to someone.
|
41
|
-
"""
|
42
|
-
rprint(f"[bold green]Hello {name}[/bold green]")
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
@app.callback(invoke_without_command=True)
|
47
|
-
def main(ctx: typer.Context,
|
48
|
-
query: Optional[str] = typer.Argument(None, help="Query to send to the claudine agent"),
|
49
|
-
debug: bool = typer.Option(False, "--debug", "-d", help="Enable debug mode"),
|
50
|
-
verbose: bool = typer.Option(False, "--verbose", "-v", help="Show detailed token usage and pricing information"),
|
51
|
-
workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Set the workspace directory")):
|
52
|
-
"""
|
53
|
-
Janito CLI tool. If a query is provided without a command, it will be sent to the claudine agent.
|
54
|
-
"""
|
55
|
-
console = Console()
|
56
|
-
|
57
|
-
# Set debug mode in config
|
58
|
-
get_config().debug_mode = debug
|
59
|
-
|
60
|
-
if workspace:
|
61
|
-
try:
|
62
|
-
print(f"Setting workspace directory to: {workspace}")
|
63
|
-
get_config().workspace_dir = workspace
|
64
|
-
print(f"Workspace directory set to: {get_config().workspace_dir}")
|
65
|
-
except ValueError as e:
|
66
|
-
console.print(f"[bold red]Error:[/bold red] {str(e)}")
|
67
|
-
sys.exit(1)
|
68
|
-
|
69
|
-
if ctx.invoked_subcommand is None:
|
70
|
-
# If no query provided in command line, read from stdin
|
71
|
-
if not query:
|
72
|
-
console.print("[bold blue]No query provided in command line. Reading from stdin...[/bold blue]")
|
73
|
-
query = sys.stdin.read().strip()
|
74
|
-
|
75
|
-
# Only proceed if we have a query (either from command line or stdin)
|
76
|
-
if query:
|
77
|
-
# Get API key from environment variable or ask the user
|
78
|
-
api_key = os.environ.get("ANTHROPIC_API_KEY")
|
79
|
-
if not api_key:
|
80
|
-
console.print("[bold yellow]Warning:[/bold yellow] ANTHROPIC_API_KEY environment variable not set.")
|
81
|
-
console.print("Please set it or provide your API key now:")
|
82
|
-
api_key = typer.prompt("Anthropic API Key", hide_input=True)
|
83
|
-
|
84
|
-
# Load instructions from file
|
85
|
-
import importlib.resources as pkg_resources
|
86
|
-
try:
|
87
|
-
# For Python 3.9+
|
88
|
-
try:
|
89
|
-
from importlib.resources import files
|
90
|
-
instructions = files('janito.data').joinpath('instructions.txt').read_text()
|
91
|
-
# Fallback for older Python versions
|
92
|
-
except (ImportError, AttributeError):
|
93
|
-
instructions = pkg_resources.read_text('janito.data', 'instructions.txt')
|
94
|
-
instructions = instructions.strip()
|
95
|
-
except Exception as e:
|
96
|
-
console.print(f"[bold yellow]Warning:[/bold yellow] Could not load instructions file: {str(e)}")
|
97
|
-
console.print("[dim]Using default instructions instead.[/dim]")
|
98
|
-
instructions = "You are a helpful AI assistant. Answer the user's questions to the best of your ability."
|
99
|
-
|
100
|
-
# Initialize the agent with the tools
|
101
|
-
agent = claudine.Agent(
|
102
|
-
api_key=api_key,
|
103
|
-
tools=[
|
104
|
-
delete_file,
|
105
|
-
find_files,
|
106
|
-
search_text
|
107
|
-
],
|
108
|
-
text_editor_tool=str_replace_editor,
|
109
|
-
tool_callbacks=(pre_tool_callback, post_tool_callback),
|
110
|
-
max_tokens=4096,
|
111
|
-
temperature=0.7,
|
112
|
-
instructions=instructions,
|
113
|
-
debug_mode=debug # Enable debug mode
|
114
|
-
)
|
115
|
-
|
116
|
-
# Process the query
|
117
|
-
console.print(f"[bold blue]Query:[/bold blue] {query}")
|
118
|
-
console.print("[bold blue]Generating response...[/bold blue]")
|
119
|
-
|
120
|
-
try:
|
121
|
-
response = agent.process_prompt(query)
|
122
|
-
|
123
|
-
console.print("\n[bold green]Janito:[/bold green]")
|
124
|
-
# Use rich's enhanced Markdown rendering for the response
|
125
|
-
console.print(Markdown(response, code_theme="monokai"))
|
126
|
-
|
127
|
-
except MaxTokensExceededException as e:
|
128
|
-
# Display the partial response if available
|
129
|
-
if e.response_text:
|
130
|
-
console.print("\n[bold green]Partial Janito:[/bold green]")
|
131
|
-
console.print(Markdown(e.response_text, code_theme="monokai"))
|
132
|
-
|
133
|
-
console.print("\n[bold red]Error:[/bold red] Response was truncated because it reached the maximum token limit.")
|
134
|
-
console.print("[dim]Consider increasing the max_tokens parameter or simplifying your query.[/dim]")
|
135
|
-
|
136
|
-
except MaxRoundsExceededException as e:
|
137
|
-
# Display the final response if available
|
138
|
-
if e.response_text:
|
139
|
-
console.print("\n[bold green]Janito:[/bold green]")
|
140
|
-
console.print(Markdown(e.response_text, code_theme="monokai"))
|
141
|
-
|
142
|
-
console.print(f"\n[bold red]Error:[/bold red] Maximum number of tool execution rounds ({e.rounds}) reached. Some tasks may be incomplete.")
|
143
|
-
console.print("[dim]Consider increasing the max_rounds parameter or breaking down your task into smaller steps.[/dim]")
|
144
|
-
|
145
|
-
# Show token usage report
|
146
|
-
generate_token_report(agent, verbose)
|
147
|
-
else:
|
148
|
-
console.print("[bold yellow]No query provided. Exiting.[/bold yellow]")
|
4
|
+
from janito.cli import app
|
149
5
|
|
150
6
|
if __name__ == "__main__":
|
151
|
-
app()
|
7
|
+
app()
|
janito/callbacks.py
CHANGED
@@ -2,129 +2,33 @@
|
|
2
2
|
Callback functions for tool execution in janito.
|
3
3
|
"""
|
4
4
|
|
5
|
-
from typing import Dict, Any, Tuple
|
6
5
|
from rich.console import Console
|
7
6
|
from rich.markdown import Markdown
|
8
7
|
|
9
8
|
from janito.config import get_config
|
10
|
-
from janito.tools import find_files
|
11
|
-
from janito.tools.str_replace_editor.editor import str_replace_editor
|
12
|
-
from janito.tools.delete_file import delete_file
|
13
|
-
from janito.tools.search_text import search_text
|
14
|
-
from janito.tools.decorators import format_tool_label
|
15
9
|
|
16
|
-
|
17
|
-
|
18
|
-
Callback function that runs before a tool is executed.
|
19
|
-
|
20
|
-
Args:
|
21
|
-
tool_name: Name of the tool being called
|
22
|
-
tool_input: Input parameters for the tool
|
23
|
-
preamble_text: Any text generated before the tool call
|
24
|
-
|
25
|
-
Returns:
|
26
|
-
Tuple of (modified tool input, whether to cancel the tool call)
|
27
|
-
"""
|
28
|
-
console = Console()
|
29
|
-
|
30
|
-
# Add debug counter only when debug mode is enabled
|
31
|
-
if get_config().debug_mode:
|
32
|
-
if not hasattr(pre_tool_callback, "counter"):
|
33
|
-
pre_tool_callback.counter = 1
|
34
|
-
console.print(f"[bold yellow]DEBUG: Starting tool call #{pre_tool_callback.counter}[/bold yellow]")
|
35
|
-
pre_tool_callback.counter += 1
|
36
|
-
|
37
|
-
# Print preamble text with enhanced markdown support if provided
|
38
|
-
if preamble_text:
|
39
|
-
# Use a single print statement to avoid extra newlines
|
40
|
-
console.print("[bold magenta]Janito:[/bold magenta] ", Markdown(preamble_text, code_theme="monokai"), end="")
|
41
|
-
|
42
|
-
# Try to find the tool function
|
43
|
-
tool_func = None
|
44
|
-
for tool in [find_files, str_replace_editor, delete_file, search_text]:
|
45
|
-
if tool.__name__ == tool_name:
|
46
|
-
tool_func = tool
|
47
|
-
break
|
48
|
-
|
49
|
-
# Create a copy of tool_input to modify for display
|
50
|
-
display_input = {}
|
51
|
-
|
52
|
-
# Maximum length for string values
|
53
|
-
max_length = 50
|
54
|
-
|
55
|
-
# Trim long string values for display
|
56
|
-
for key, value in tool_input.items():
|
57
|
-
if isinstance(value, str) and len(value) > max_length:
|
58
|
-
# For long strings, show first and last part with ellipsis in between
|
59
|
-
display_input[key] = f"{value[:20]}...{value[-20:]}" if len(value) > 45 else value[:max_length] + "..."
|
60
|
-
else:
|
61
|
-
display_input[key] = value
|
62
|
-
|
63
|
-
# If we found the tool and it has a tool_meta label, use that for display
|
64
|
-
if tool_func:
|
65
|
-
formatted_label = format_tool_label(tool_func, tool_input)
|
66
|
-
if formatted_label:
|
67
|
-
console.print("[bold cyan] Tool:[/bold cyan]", formatted_label, end=" → ")
|
68
|
-
else:
|
69
|
-
console.print("[bold cyan] Tool:[/bold cyan]", f"{tool_name} {display_input}", end=" → ")
|
70
|
-
|
71
|
-
return tool_input, True # Continue with the tool call
|
10
|
+
# Counter for pre-tool callbacks
|
11
|
+
pre_tool_callbacks = 0
|
72
12
|
|
73
|
-
def
|
13
|
+
def text_callback(text: str) -> None:
|
74
14
|
"""
|
75
|
-
Callback function that
|
15
|
+
Callback function that handles text output from the agent.
|
76
16
|
|
77
17
|
Args:
|
78
|
-
|
79
|
-
tool_input: Input parameters for the tool
|
80
|
-
result: Result of the tool call
|
18
|
+
text: Text output from the agent
|
81
19
|
|
82
20
|
Returns:
|
83
|
-
|
21
|
+
None
|
84
22
|
"""
|
85
23
|
console = Console()
|
86
24
|
|
87
25
|
# Add debug counter only when debug mode is enabled
|
88
26
|
if get_config().debug_mode:
|
89
|
-
if not hasattr(
|
90
|
-
|
91
|
-
console.print(f"[bold
|
92
|
-
|
27
|
+
if not hasattr(text_callback, "counter"):
|
28
|
+
text_callback.counter = 1
|
29
|
+
console.print(f"[bold blue]DEBUG: Text callback #{text_callback.counter}[/bold blue]")
|
30
|
+
text_callback.counter += 1
|
93
31
|
|
94
|
-
#
|
95
|
-
|
96
|
-
|
97
|
-
# Define prefix icon based on is_error
|
98
|
-
icon_prefix = "❌ " if is_error else "✅ "
|
99
|
-
|
100
|
-
if isinstance(content, str):
|
101
|
-
# For find_files, extract just the count from the last line
|
102
|
-
if tool_name == "find_files" and content.count("\n") > 0:
|
103
|
-
lines = content.strip().split('\n')
|
104
|
-
if lines and lines[-1].isdigit():
|
105
|
-
console.print(f"{icon_prefix}{lines[-1]}")
|
106
|
-
else:
|
107
|
-
# Get the last line
|
108
|
-
last_line = content.strip().split('\n')[-1]
|
109
|
-
console.print(f"{icon_prefix}{last_line}")
|
110
|
-
else:
|
111
|
-
# For other tools, just get the last line
|
112
|
-
if '\n' in content:
|
113
|
-
last_line = content.strip().split('\n')[-1]
|
114
|
-
console.print(f"{icon_prefix}{last_line}")
|
115
|
-
else:
|
116
|
-
console.print(f"{icon_prefix}{content}")
|
117
|
-
else:
|
118
|
-
console.print(f"{icon_prefix}{content}")
|
119
|
-
else:
|
120
|
-
# If result is not a tuple, convert to string and get the last line
|
121
|
-
result_str = str(result)
|
122
|
-
# Default to success icon when no error status is available
|
123
|
-
icon_prefix = "✅ "
|
124
|
-
if '\n' in result_str:
|
125
|
-
last_line = result_str.strip().split('\n')[-1]
|
126
|
-
console.print(f"{icon_prefix}{last_line}")
|
127
|
-
else:
|
128
|
-
console.print(f"{icon_prefix}{result_str}")
|
129
|
-
|
130
|
-
return result
|
32
|
+
# Print the text with markdown formatting
|
33
|
+
console.print(Markdown(text, code_theme="monokai"), end="")
|
34
|
+
|
janito/cli/__init__.py
ADDED
janito/cli/agent.py
ADDED
@@ -0,0 +1,287 @@
|
|
1
|
+
"""
|
2
|
+
Agent initialization and query handling for Janito CLI.
|
3
|
+
"""
|
4
|
+
import os
|
5
|
+
import sys
|
6
|
+
import json
|
7
|
+
import anthropic
|
8
|
+
import claudine
|
9
|
+
import typer
|
10
|
+
from rich.console import Console
|
11
|
+
from pathlib import Path
|
12
|
+
from jinja2 import Template
|
13
|
+
import importlib.resources as pkg_resources
|
14
|
+
|
15
|
+
from janito.config import get_config, Config
|
16
|
+
from janito.callbacks import text_callback
|
17
|
+
from janito.token_report import generate_token_report
|
18
|
+
from janito.tools import str_replace_editor
|
19
|
+
from janito.tools.bash.bash import bash_tool
|
20
|
+
from janito.cli.output import display_generation_params
|
21
|
+
|
22
|
+
console = Console()
|
23
|
+
|
24
|
+
def get_api_key() -> str:
|
25
|
+
"""
|
26
|
+
Get the API key from global config, environment variable, or user input.
|
27
|
+
|
28
|
+
Returns:
|
29
|
+
str: The API key
|
30
|
+
"""
|
31
|
+
# Get API key from global config, environment variable, or ask the user
|
32
|
+
api_key = Config.get_api_key()
|
33
|
+
|
34
|
+
# If not found in global config, try environment variable
|
35
|
+
if not api_key:
|
36
|
+
api_key = os.environ.get("ANTHROPIC_API_KEY")
|
37
|
+
|
38
|
+
# If still not found, prompt the user
|
39
|
+
if not api_key:
|
40
|
+
console.print("[bold yellow]⚠️ Warning:[/bold yellow] API key not found in global config or ANTHROPIC_API_KEY environment variable.")
|
41
|
+
console.print("🔑 Please set it using --set-api-key or provide your API key now:")
|
42
|
+
api_key = typer.prompt("Anthropic API Key", hide_input=True)
|
43
|
+
|
44
|
+
return api_key
|
45
|
+
|
46
|
+
def load_instructions() -> str:
|
47
|
+
"""
|
48
|
+
Load instructions template and render it with variables.
|
49
|
+
|
50
|
+
Returns:
|
51
|
+
str: The rendered instructions
|
52
|
+
"""
|
53
|
+
import platform
|
54
|
+
|
55
|
+
try:
|
56
|
+
# For Python 3.9+
|
57
|
+
try:
|
58
|
+
from importlib.resources import files
|
59
|
+
template_content = files('janito.data').joinpath('instructions_template.txt').read_text(encoding='utf-8')
|
60
|
+
# Fallback for older Python versions
|
61
|
+
except (ImportError, AttributeError):
|
62
|
+
template_content = pkg_resources.read_text('janito.data', 'instructions_template.txt', encoding='utf-8')
|
63
|
+
|
64
|
+
# Create template variables
|
65
|
+
template_variables = {
|
66
|
+
'platform': platform.system(),
|
67
|
+
'role': get_config().role,
|
68
|
+
# Add any other variables you want to pass to the template here
|
69
|
+
}
|
70
|
+
|
71
|
+
# Create template and render
|
72
|
+
template = Template(template_content)
|
73
|
+
instructions = template.render(**template_variables)
|
74
|
+
|
75
|
+
except Exception as e:
|
76
|
+
console.print(f"[bold red]❌ Error loading instructions template:[/bold red] {str(e)}")
|
77
|
+
# Try to fall back to regular instructions.txt
|
78
|
+
try:
|
79
|
+
# For Python 3.9+
|
80
|
+
try:
|
81
|
+
from importlib.resources import files
|
82
|
+
instructions = files('janito.data').joinpath('instructions.txt').read_text(encoding='utf-8')
|
83
|
+
# Fallback for older Python versions
|
84
|
+
except (ImportError, AttributeError):
|
85
|
+
instructions = pkg_resources.read_text('janito.data', 'instructions.txt', encoding='utf-8')
|
86
|
+
except Exception as e2:
|
87
|
+
console.print(f"[bold red]❌ Error loading fallback instructions:[/bold red] {str(e2)}")
|
88
|
+
instructions = "You are Janito, an AI assistant."
|
89
|
+
|
90
|
+
return instructions
|
91
|
+
|
92
|
+
def initialize_agent(temperature: float, verbose: bool) -> claudine.Agent:
|
93
|
+
"""
|
94
|
+
Initialize the Claude agent with tools and configuration.
|
95
|
+
|
96
|
+
Args:
|
97
|
+
temperature: Temperature value for model generation
|
98
|
+
verbose: Whether to enable verbose mode
|
99
|
+
|
100
|
+
Returns:
|
101
|
+
claudine.Agent: The initialized agent
|
102
|
+
"""
|
103
|
+
# Get API key
|
104
|
+
api_key = get_api_key()
|
105
|
+
|
106
|
+
# Load instructions
|
107
|
+
instructions = load_instructions()
|
108
|
+
|
109
|
+
# Get tools
|
110
|
+
from janito.tools import get_tools, reset_tracker
|
111
|
+
tools_list = get_tools()
|
112
|
+
|
113
|
+
# Reset usage tracker before each query
|
114
|
+
reset_tracker()
|
115
|
+
|
116
|
+
# Use command line parameters if provided (not default values), otherwise use config
|
117
|
+
temp_to_use = temperature if temperature != 0.0 else get_config().temperature
|
118
|
+
|
119
|
+
# Get profile parameters if a profile is set
|
120
|
+
config = get_config()
|
121
|
+
profile_data = None
|
122
|
+
if config.profile:
|
123
|
+
profile_data = config.get_available_profiles()[config.profile]
|
124
|
+
|
125
|
+
# Display generation parameters if verbose mode is enabled
|
126
|
+
if verbose:
|
127
|
+
display_generation_params(temp_to_use, profile_data, temperature)
|
128
|
+
|
129
|
+
# Create config_params dictionary with generation parameters
|
130
|
+
config_params = {
|
131
|
+
"temperature": temp_to_use
|
132
|
+
}
|
133
|
+
|
134
|
+
# Add top_k and top_p from profile if available
|
135
|
+
if profile_data:
|
136
|
+
if "top_k" in profile_data and profile_data["top_k"] != 0:
|
137
|
+
config_params["top_k"] = profile_data["top_k"]
|
138
|
+
if "top_p" in profile_data and profile_data["top_p"] != 0.0:
|
139
|
+
config_params["top_p"] = profile_data["top_p"]
|
140
|
+
|
141
|
+
# Initialize the agent
|
142
|
+
agent = claudine.Agent(
|
143
|
+
api_key=api_key,
|
144
|
+
system_prompt=instructions,
|
145
|
+
callbacks={"text": text_callback},
|
146
|
+
text_editor_tool=str_replace_editor,
|
147
|
+
bash_tool=bash_tool,
|
148
|
+
tools=tools_list,
|
149
|
+
verbose=verbose,
|
150
|
+
max_tokens=8126,
|
151
|
+
max_tool_rounds=100,
|
152
|
+
config_params=config_params,
|
153
|
+
)
|
154
|
+
|
155
|
+
return agent
|
156
|
+
|
157
|
+
def save_messages(agent):
|
158
|
+
"""
|
159
|
+
Save agent messages to .janito/last_message.json
|
160
|
+
|
161
|
+
Args:
|
162
|
+
agent: The claudine agent instance
|
163
|
+
"""
|
164
|
+
try:
|
165
|
+
# Get the workspace directory
|
166
|
+
workspace_dir = Path(get_config().workspace_dir)
|
167
|
+
|
168
|
+
# Create .janito directory if it doesn't exist
|
169
|
+
janito_dir = workspace_dir / ".janito"
|
170
|
+
janito_dir.mkdir(exist_ok=True)
|
171
|
+
|
172
|
+
# Get messages from the agent
|
173
|
+
messages = agent.get_messages()
|
174
|
+
|
175
|
+
# Save messages to file
|
176
|
+
with open(janito_dir / "last_message.json", "w", encoding="utf-8") as f:
|
177
|
+
json.dump(messages, f, ensure_ascii=False, indent=2)
|
178
|
+
|
179
|
+
if get_config().verbose:
|
180
|
+
console.print(f"[bold green]✅ Conversation saved to {janito_dir / 'last_message.json'}[/bold green]")
|
181
|
+
except Exception as e:
|
182
|
+
console.print(f"[bold red]❌ Error saving conversation:[/bold red] {str(e)}")
|
183
|
+
|
184
|
+
def load_messages():
|
185
|
+
"""
|
186
|
+
Load messages from .janito/last_message.json
|
187
|
+
|
188
|
+
Returns:
|
189
|
+
List of message dictionaries or None if file doesn't exist
|
190
|
+
"""
|
191
|
+
try:
|
192
|
+
# Get the workspace directory
|
193
|
+
workspace_dir = Path(get_config().workspace_dir)
|
194
|
+
|
195
|
+
# Check if file exists
|
196
|
+
messages_file = workspace_dir / ".janito" / "last_message.json"
|
197
|
+
if not messages_file.exists():
|
198
|
+
console.print("[bold yellow]⚠️ No previous conversation found[/bold yellow]")
|
199
|
+
return None
|
200
|
+
|
201
|
+
# Load messages from file
|
202
|
+
with open(messages_file, "r", encoding="utf-8") as f:
|
203
|
+
messages = json.load(f)
|
204
|
+
|
205
|
+
if get_config().verbose:
|
206
|
+
console.print(f"[bold green]✅ Loaded previous conversation from {messages_file}[/bold green]")
|
207
|
+
console.print(f"[dim]📝 Conversation has {len(messages)} messages[/dim]")
|
208
|
+
|
209
|
+
return messages
|
210
|
+
except Exception as e:
|
211
|
+
console.print(f"[bold red]❌ Error loading previous conversation:[/bold red] {str(e)}")
|
212
|
+
return None
|
213
|
+
|
214
|
+
def handle_query(query: str, temperature: float, verbose: bool, show_tokens: bool, continue_conversation: bool = False) -> None:
|
215
|
+
"""
|
216
|
+
Handle a query by initializing the agent and sending the query.
|
217
|
+
|
218
|
+
Args:
|
219
|
+
query: The query to send to the agent
|
220
|
+
temperature: Temperature value for model generation
|
221
|
+
verbose: Whether to enable verbose mode
|
222
|
+
show_tokens: Whether to show detailed token usage
|
223
|
+
continue_conversation: Whether to continue the previous conversation
|
224
|
+
"""
|
225
|
+
# Initialize the agent
|
226
|
+
agent = initialize_agent(temperature, verbose)
|
227
|
+
|
228
|
+
# Load previous messages if continuing conversation
|
229
|
+
if continue_conversation:
|
230
|
+
messages = load_messages()
|
231
|
+
if messages:
|
232
|
+
agent.set_messages(messages)
|
233
|
+
console.print("[bold blue]🔄 Continuing previous conversation[/bold blue]")
|
234
|
+
|
235
|
+
# Send the query to the agent
|
236
|
+
try:
|
237
|
+
agent.query(query)
|
238
|
+
|
239
|
+
# Save messages after successful query
|
240
|
+
save_messages(agent)
|
241
|
+
|
242
|
+
# Print token usage report
|
243
|
+
if show_tokens:
|
244
|
+
generate_token_report(agent, verbose=True, interrupted=False)
|
245
|
+
else:
|
246
|
+
# Show basic token usage
|
247
|
+
generate_token_report(agent, verbose=False, interrupted=False)
|
248
|
+
|
249
|
+
# Print tool usage statistics
|
250
|
+
from janito.tools import print_usage_stats
|
251
|
+
print_usage_stats()
|
252
|
+
|
253
|
+
except KeyboardInterrupt:
|
254
|
+
# Handle Ctrl+C by printing token and tool usage information
|
255
|
+
console.print("\n[bold yellow]⚠️ Query interrupted by user (Ctrl+C)[/bold yellow]")
|
256
|
+
|
257
|
+
# Save messages even if interrupted
|
258
|
+
save_messages(agent)
|
259
|
+
|
260
|
+
# Print token usage report (even if interrupted)
|
261
|
+
try:
|
262
|
+
if show_tokens:
|
263
|
+
generate_token_report(agent, verbose=True, interrupted=True)
|
264
|
+
else:
|
265
|
+
# Show basic token usage
|
266
|
+
generate_token_report(agent, verbose=False, interrupted=True)
|
267
|
+
|
268
|
+
# Print tool usage statistics
|
269
|
+
from janito.tools import print_usage_stats
|
270
|
+
print_usage_stats()
|
271
|
+
except Exception as e:
|
272
|
+
console.print(f"[bold red]❌ Error generating usage report:[/bold red] {str(e)}")
|
273
|
+
if verbose:
|
274
|
+
import traceback
|
275
|
+
console.print(traceback.format_exc())
|
276
|
+
|
277
|
+
# Exit with non-zero status to indicate interruption
|
278
|
+
sys.exit(130) # 130 is the standard exit code for SIGINT
|
279
|
+
|
280
|
+
except anthropic.APIError as e:
|
281
|
+
console.print(f"[bold red]❌ Anthropic API Error:[/bold red] {str(e)}")
|
282
|
+
|
283
|
+
except Exception as e:
|
284
|
+
console.print(f"[bold red]❌ Error:[/bold red] {str(e)}")
|
285
|
+
if verbose:
|
286
|
+
import traceback
|
287
|
+
console.print(traceback.format_exc())
|
janito/cli/app.py
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
"""
|
2
|
+
Main CLI application for Janito.
|
3
|
+
"""
|
4
|
+
import sys
|
5
|
+
from typing import Optional
|
6
|
+
import typer
|
7
|
+
from rich.console import Console
|
8
|
+
import importlib.metadata
|
9
|
+
|
10
|
+
from janito.config import get_config
|
11
|
+
from janito.cli.commands import handle_config_commands, validate_parameters
|
12
|
+
from janito.cli.agent import handle_query
|
13
|
+
from janito.cli.utils import get_stdin_termination_hint
|
14
|
+
|
15
|
+
app = typer.Typer()
|
16
|
+
console = Console()
|
17
|
+
|
18
|
+
@app.callback(invoke_without_command=True)
|
19
|
+
def main(ctx: typer.Context,
|
20
|
+
query: Optional[str] = typer.Argument(None, help="Query to send to the claudine agent"),
|
21
|
+
verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose mode with detailed output"),
|
22
|
+
show_tokens: bool = typer.Option(False, "--show-tokens", "-t", help="Show detailed token usage and pricing information"),
|
23
|
+
workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Set the workspace directory"),
|
24
|
+
config_str: Optional[str] = typer.Option(None, "--set-config", help="Configuration string in format 'key=value', e.g., 'temperature=0.7' or 'profile=technical'"),
|
25
|
+
show_config: bool = typer.Option(False, "--show-config", help="Show current configuration"),
|
26
|
+
reset_config: bool = typer.Option(False, "--reset-config", help="Reset configuration by removing the config file"),
|
27
|
+
set_api_key: Optional[str] = typer.Option(None, "--set-api-key", help="Set the Anthropic API key globally in the user's home directory"),
|
28
|
+
ask: bool = typer.Option(False, "--ask", help="Enable ask mode which disables tools that perform changes"),
|
29
|
+
temperature: float = typer.Option(0.0, "--temperature", help="Set the temperature for model generation (0.0 to 1.0)"),
|
30
|
+
profile: Optional[str] = typer.Option(None, "--profile", help="Use a predefined parameter profile (precise, balanced, conversational, creative, technical)"),
|
31
|
+
role: Optional[str] = typer.Option(None, "--role", help="Set the assistant's role (default: 'software engineer')"),
|
32
|
+
version: bool = typer.Option(False, "--version", help="Show the version and exit"),
|
33
|
+
continue_conversation: bool = typer.Option(False, "--continue", "-c", help="Continue the previous conversation")):
|
34
|
+
"""
|
35
|
+
Janito CLI tool. If a query is provided without a command, it will be sent to the claudine agent.
|
36
|
+
"""
|
37
|
+
# Set verbose mode in config
|
38
|
+
get_config().verbose = verbose
|
39
|
+
|
40
|
+
# Set ask mode in config
|
41
|
+
get_config().ask_mode = ask
|
42
|
+
|
43
|
+
# Show a message if ask mode is enabled
|
44
|
+
if ask:
|
45
|
+
console.print("[bold yellow]⚠️ Ask Mode enabled:[/bold yellow] 🔒 Tools that perform changes are disabled")
|
46
|
+
|
47
|
+
# Show version and exit if requested
|
48
|
+
if version:
|
49
|
+
try:
|
50
|
+
version_str = importlib.metadata.version("janito")
|
51
|
+
console.print(f"🚀 Janito version: {version_str}")
|
52
|
+
except importlib.metadata.PackageNotFoundError:
|
53
|
+
console.print("🚀 Janito version: [italic]development[/italic]")
|
54
|
+
sys.exit(0)
|
55
|
+
|
56
|
+
# Validate temperature
|
57
|
+
validate_parameters(temperature)
|
58
|
+
|
59
|
+
# Handle configuration-related commands
|
60
|
+
exit_after_config = handle_config_commands(
|
61
|
+
ctx,
|
62
|
+
reset_config,
|
63
|
+
workspace,
|
64
|
+
show_config,
|
65
|
+
profile,
|
66
|
+
role,
|
67
|
+
set_api_key,
|
68
|
+
config_str,
|
69
|
+
query,
|
70
|
+
continue_conversation
|
71
|
+
)
|
72
|
+
|
73
|
+
if exit_after_config:
|
74
|
+
sys.exit(0)
|
75
|
+
|
76
|
+
# Handle query if no subcommand was invoked
|
77
|
+
if ctx.invoked_subcommand is None:
|
78
|
+
# If no query provided in command line, read from stdin
|
79
|
+
if not query:
|
80
|
+
console.print("[bold blue]📝 No query provided in command line. Reading from stdin...[/bold blue]")
|
81
|
+
console.print(get_stdin_termination_hint())
|
82
|
+
query = sys.stdin.read().strip()
|
83
|
+
|
84
|
+
# Only proceed if we have a query (either from command line or stdin)
|
85
|
+
if query:
|
86
|
+
handle_query(query, temperature, verbose, show_tokens, continue_conversation)
|