janito 0.13.0__py3-none-any.whl → 0.14.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 CHANGED
@@ -2,4 +2,4 @@
2
2
  Janito package.
3
3
  """
4
4
 
5
- __version__ = "0.13.0"
5
+ __version__ = "0.14.0"
@@ -0,0 +1,7 @@
1
+ """
2
+ Agent initialization and query handling for Janito CLI.
3
+ """
4
+ from janito.cli.agent.query import handle_query
5
+ from janito.cli.agent.conversation import load_messages, save_messages
6
+
7
+ __all__ = ["handle_query", "load_messages", "save_messages"]
@@ -0,0 +1,149 @@
1
+ """
2
+ Conversation management functionality for Janito CLI.
3
+ """
4
+ import json
5
+ import datetime
6
+ import sys
7
+ from typing import Optional, List, Dict, Any
8
+ from rich.console import Console
9
+ from pathlib import Path
10
+ import claudine
11
+
12
+ from janito.config import get_config
13
+
14
+ console = Console()
15
+
16
+ def generate_message_id() -> str:
17
+ """
18
+ Generate a message ID based on timestamp with seconds granularity
19
+
20
+ Returns:
21
+ str: A timestamp-based message ID
22
+ """
23
+ timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
24
+ return timestamp
25
+
26
+ def save_messages(agent: claudine.Agent) -> Optional[str]:
27
+ """
28
+ Save agent messages to .janito/last_messages/{message_id}.json
29
+
30
+ Args:
31
+ agent: The claudine agent instance
32
+
33
+ Returns:
34
+ str: The message ID used for saving, or None if saving failed
35
+ """
36
+ try:
37
+ # Get the workspace directory
38
+ workspace_dir = Path(get_config().workspace_dir)
39
+
40
+ # Create .janito directory if it doesn't exist
41
+ janito_dir = workspace_dir / ".janito"
42
+ janito_dir.mkdir(exist_ok=True)
43
+
44
+ # Create last_messages directory if it doesn't exist
45
+ messages_dir = janito_dir / "last_messages"
46
+ messages_dir.mkdir(exist_ok=True)
47
+
48
+ # Generate a unique message ID
49
+ message_id = generate_message_id()
50
+
51
+ # Get messages from the agent
52
+ messages = agent.get_messages()
53
+
54
+ # Create a message object with metadata
55
+ message_object = {
56
+ "id": message_id,
57
+ "timestamp": datetime.datetime.now().isoformat(),
58
+ "messages": messages
59
+ }
60
+
61
+ # Save messages to file
62
+ message_file = messages_dir / f"{message_id}.json"
63
+ with open(message_file, "w", encoding="utf-8") as f:
64
+ json.dump(message_object, f, ensure_ascii=False, indent=2)
65
+
66
+ if get_config().verbose:
67
+ console.print(f"[bold green]✅ Conversation saved to {message_file}[/bold green]")
68
+
69
+ return message_id
70
+ except Exception as e:
71
+ console.print(f"[bold red]❌ Error saving conversation:[/bold red] {str(e)}")
72
+ return None
73
+
74
+ def load_messages(message_id: Optional[str] = None) -> Optional[List[Dict[str, Any]]]:
75
+ """
76
+ Load messages from .janito/last_messages/{message_id}.json or the latest message file
77
+
78
+ Args:
79
+ message_id: Optional message ID to load specific conversation
80
+
81
+ Returns:
82
+ List of message dictionaries or None if file doesn't exist
83
+ """
84
+ try:
85
+ # Get the workspace directory
86
+ workspace_dir = Path(get_config().workspace_dir)
87
+ janito_dir = workspace_dir / ".janito"
88
+ messages_dir = janito_dir / "last_messages"
89
+
90
+ # If message_id is provided, try to load that specific conversation
91
+ if message_id:
92
+ # Check if the message ID is a file name or just the ID
93
+ if message_id.endswith('.json'):
94
+ message_file = messages_dir / message_id
95
+ else:
96
+ message_file = messages_dir / f"{message_id}.json"
97
+
98
+ if not message_file.exists():
99
+ console.print(f"[bold yellow]⚠️ No conversation found with ID {message_id}[/bold yellow]")
100
+ return None
101
+
102
+ # Load messages from file
103
+ with open(message_file, "r", encoding="utf-8") as f:
104
+ message_object = json.load(f)
105
+
106
+ # Extract messages from the message object
107
+ if isinstance(message_object, dict) and "messages" in message_object:
108
+ messages = message_object["messages"]
109
+ else:
110
+ # Handle legacy format
111
+ messages = message_object
112
+
113
+ if get_config().verbose:
114
+ console.print(f"[bold green]✅ Loaded conversation from {message_file}[/bold green]")
115
+ console.print(f"[dim]📝 Conversation has {len(messages)} messages[/dim]")
116
+
117
+ return messages
118
+
119
+ # If no message_id is provided, try to load the latest message from last_messages directory
120
+ if not messages_dir.exists() or not any(messages_dir.iterdir()):
121
+ console.print("[bold yellow]⚠️ No previous conversation found[/bold yellow]")
122
+ return None
123
+
124
+ # Find the latest message file (based on filename which is a timestamp)
125
+ latest_file = max(
126
+ [f for f in messages_dir.iterdir() if f.is_file() and f.suffix == '.json'],
127
+ key=lambda x: x.stem
128
+ )
129
+
130
+ # Load messages from the latest file
131
+ with open(latest_file, "r", encoding="utf-8") as f:
132
+ message_object = json.load(f)
133
+
134
+ # Extract messages from the message object
135
+ if isinstance(message_object, dict) and "messages" in message_object:
136
+ messages = message_object["messages"]
137
+ else:
138
+ # Handle legacy format
139
+ messages = message_object
140
+
141
+ if get_config().verbose:
142
+ console.print(f"[bold green]✅ Loaded latest conversation from {latest_file}[/bold green]")
143
+ console.print(f"[dim]📝 Conversation has {len(messages)} messages[/dim]")
144
+
145
+ return messages
146
+ except Exception as e:
147
+ console.print(f"[bold red]❌ Error loading conversation:[/bold red] {str(e)}")
148
+ return None
149
+
@@ -0,0 +1,172 @@
1
+ """
2
+ Agent initialization functionality for Janito CLI.
3
+ """
4
+ import os
5
+ import platform
6
+ import typer
7
+ from typing import Optional, Dict, Any
8
+ from rich.console import Console
9
+ from jinja2 import Template
10
+ import importlib.resources as pkg_resources
11
+ import claudine
12
+
13
+ from janito.config import get_config, Config
14
+ from janito.callbacks import text_callback
15
+ from janito.tools import str_replace_editor, get_tools, reset_tracker
16
+ from janito.tools.bash.bash import bash_tool
17
+ from janito.cli.output import display_generation_params
18
+
19
+ console = Console()
20
+
21
+ def get_api_key() -> str:
22
+ """
23
+ Get the API key from global config, environment variable, or user input.
24
+
25
+ Returns:
26
+ str: The API key
27
+ """
28
+ # Get API key from global config, environment variable, or ask the user
29
+ api_key = Config.get_api_key()
30
+
31
+ # If not found in global config, try environment variable
32
+ if not api_key:
33
+ api_key = os.environ.get("ANTHROPIC_API_KEY")
34
+
35
+ # If still not found, prompt the user
36
+ if not api_key:
37
+ console.print("[bold yellow]⚠️ Warning:[/bold yellow] API key not found in global config or ANTHROPIC_API_KEY environment variable.")
38
+ console.print("🔑 Please set it using --set-api-key or provide your API key now:")
39
+ api_key = typer.prompt("Anthropic API Key", hide_input=True)
40
+
41
+ return api_key
42
+
43
+ def load_instructions() -> str:
44
+ """
45
+ Load instructions template and render it with variables.
46
+
47
+ Returns:
48
+ str: The rendered instructions
49
+ """
50
+ try:
51
+ # For Python 3.9+
52
+ try:
53
+ from importlib.resources import files
54
+ template_content = files('janito.data').joinpath('instructions_template.txt').read_text(encoding='utf-8')
55
+ # Fallback for older Python versions
56
+ except (ImportError, AttributeError):
57
+ template_content = pkg_resources.read_text('janito.data', 'instructions_template.txt', encoding='utf-8')
58
+
59
+ # Create template variables
60
+ template_variables = {
61
+ 'platform': platform.system(),
62
+ 'role': get_config().role,
63
+ # Add any other variables you want to pass to the template here
64
+ }
65
+
66
+ # Create template and render
67
+ template = Template(template_content)
68
+ instructions = template.render(**template_variables)
69
+
70
+ except Exception as e:
71
+ console.print(f"[bold red]❌ Error loading instructions template:[/bold red] {str(e)}")
72
+ # Try to fall back to regular instructions.txt
73
+ try:
74
+ # For Python 3.9+
75
+ try:
76
+ from importlib.resources import files
77
+ instructions = files('janito.data').joinpath('instructions.txt').read_text(encoding='utf-8')
78
+ # Fallback for older Python versions
79
+ except (ImportError, AttributeError):
80
+ instructions = pkg_resources.read_text('janito.data', 'instructions.txt', encoding='utf-8')
81
+ except Exception as e2:
82
+ console.print(f"[bold red]❌ Error loading fallback instructions:[/bold red] {str(e2)}")
83
+ instructions = "You are Janito, an AI assistant."
84
+
85
+ return instructions
86
+
87
+ def initialize_agent(temperature: float, verbose: bool, system_instructions: Optional[str] = None) -> claudine.Agent:
88
+ """
89
+ Initialize the Claude agent with tools and configuration.
90
+
91
+ Args:
92
+ temperature: Temperature value for model generation
93
+ verbose: Whether to enable verbose mode
94
+ system_instructions: Optional custom system instructions to use instead of loading from file
95
+
96
+ Returns:
97
+ claudine.Agent: The initialized agent
98
+ """
99
+ # Get API key
100
+ api_key = get_api_key()
101
+
102
+ # Load instructions or use provided system instructions
103
+ if system_instructions:
104
+ instructions = system_instructions
105
+ if verbose:
106
+ console.print("[bold blue]🔄 Using custom system instructions provided via --system parameter[/bold blue]")
107
+ # Print the first 50 characters of the instructions for verification
108
+ preview = system_instructions[:50] + "..." if len(system_instructions) > 50 else system_instructions
109
+ console.print(f"[dim]System instructions preview: {preview}[/dim]")
110
+ else:
111
+ instructions = load_instructions()
112
+
113
+ # Get tools
114
+ tools_list = get_tools()
115
+
116
+ # Reset usage tracker before each query
117
+ reset_tracker()
118
+
119
+ # Use command line parameters if provided (not default values), otherwise use config
120
+ temp_to_use = temperature if temperature != 0.0 else get_config().temperature
121
+
122
+ # Get profile parameters if a profile is set
123
+ config = get_config()
124
+ profile_data = None
125
+ if config.profile:
126
+ profile_data = config.get_available_profiles()[config.profile]
127
+
128
+ # Display generation parameters if verbose mode is enabled
129
+ if verbose:
130
+ display_generation_params(temp_to_use, profile_data, temperature)
131
+
132
+ # Create config_params dictionary with generation parameters
133
+ config_params = {
134
+ "temperature": temp_to_use
135
+ }
136
+
137
+ # Add top_k and top_p from profile if available
138
+ if profile_data:
139
+ if "top_k" in profile_data and profile_data["top_k"] != 0:
140
+ config_params["top_k"] = profile_data["top_k"]
141
+ if "top_p" in profile_data and profile_data["top_p"] != 0.0:
142
+ config_params["top_p"] = profile_data["top_p"]
143
+
144
+ # Initialize the agent
145
+ if get_config().no_tools:
146
+ # If no_tools mode is enabled, don't pass any tools to the agent
147
+ agent = claudine.Agent(
148
+ api_key=api_key,
149
+ system_prompt=instructions,
150
+ callbacks={"text": text_callback},
151
+ verbose=verbose,
152
+ max_tokens=8126,
153
+ max_tool_rounds=100,
154
+ config_params=config_params,
155
+ # Don't pass any tools, including text_editor_tool and bash_tool
156
+ )
157
+ else:
158
+ # Normal mode with tools
159
+ agent = claudine.Agent(
160
+ api_key=api_key,
161
+ system_prompt=instructions,
162
+ callbacks={"text": text_callback},
163
+ text_editor_tool=str_replace_editor,
164
+ bash_tool=bash_tool,
165
+ tools=tools_list,
166
+ verbose=verbose,
167
+ max_tokens=8126,
168
+ max_tool_rounds=100,
169
+ config_params=config_params,
170
+ )
171
+
172
+ return agent
@@ -0,0 +1,108 @@
1
+ """
2
+ Query handling functionality for Janito CLI.
3
+ """
4
+ import sys
5
+ import traceback
6
+ from typing import Optional
7
+ from rich.console import Console
8
+ import anthropic
9
+
10
+ from janito.token_report import generate_token_report
11
+ from janito.tools import print_usage_stats
12
+ from janito.cli.agent.initialization import initialize_agent
13
+ from janito.cli.agent.conversation import load_messages, save_messages
14
+
15
+ console = Console()
16
+
17
+ def handle_query(query: str, temperature: float, verbose: bool, show_tokens: bool, continue_conversation: Optional[str] = None, system_instructions: Optional[str] = None) -> None:
18
+ """
19
+ Handle a query by initializing the agent and sending the query.
20
+
21
+ Args:
22
+ query: The query to send to the agent
23
+ temperature: Temperature value for model generation
24
+ verbose: Whether to enable verbose mode
25
+ show_tokens: Whether to show detailed token usage
26
+ continue_conversation: Optional message ID to continue a specific conversation
27
+ system_instructions: Optional custom system instructions to use instead of loading from file
28
+ """
29
+ # Initialize the agent
30
+ agent = initialize_agent(temperature, verbose, system_instructions)
31
+
32
+ # Load previous messages if continuing conversation
33
+ if continue_conversation is not None:
34
+ # If continue_conversation is an empty string (from flag with no value), use default behavior
35
+ message_id = None if continue_conversation == "" else continue_conversation
36
+ messages = load_messages(message_id)
37
+ if messages:
38
+ agent.set_messages(messages)
39
+ if message_id:
40
+ console.print(f"[bold blue]🔄 Continuing conversation with ID: {message_id}[/bold blue]")
41
+ else:
42
+ console.print("[bold blue]🔄 Continuing most recent conversation[/bold blue]")
43
+
44
+ # Provide information about the conversation being continued
45
+ if verbose and len(messages) > 0:
46
+ # Get the number of messages
47
+ num_messages = len(messages)
48
+ # Get the last user message if available
49
+ last_user_message = next((msg.get("content", "") for msg in reversed(messages)
50
+ if msg.get("role") == "user"), "")
51
+ if last_user_message:
52
+ console.print(f"[dim]📝 Last query: \"{last_user_message[:60]}{'...' if len(last_user_message) > 60 else ''}\"[/dim]")
53
+ else:
54
+ console.print("[bold yellow]⚠️ No previous conversation found to continue[/bold yellow]")
55
+
56
+ # Send the query to the agent
57
+ try:
58
+ agent.query(query)
59
+
60
+ # Save messages after successful query and get the message ID
61
+ message_id = save_messages(agent)
62
+
63
+ # Print token usage report
64
+ if show_tokens:
65
+ generate_token_report(agent, verbose=True, interrupted=False)
66
+ else:
67
+ # Show basic token usage
68
+ generate_token_report(agent, verbose=False, interrupted=False)
69
+
70
+ # Print tool usage statistics
71
+ print_usage_stats()
72
+
73
+
74
+
75
+ except KeyboardInterrupt:
76
+ # Handle Ctrl+C by printing token and tool usage information
77
+ console.print("\n[bold yellow]⚠️ Query interrupted by user (Ctrl+C)[/bold yellow]")
78
+
79
+ # Save messages even if interrupted
80
+ message_id = save_messages(agent)
81
+
82
+ # Print token usage report (even if interrupted)
83
+ try:
84
+ if show_tokens:
85
+ generate_token_report(agent, verbose=True, interrupted=True)
86
+ else:
87
+ # Show basic token usage
88
+ generate_token_report(agent, verbose=False, interrupted=True)
89
+
90
+ # Print tool usage statistics
91
+ print_usage_stats()
92
+
93
+
94
+ except Exception as e:
95
+ console.print(f"[bold red]❌ Error generating usage report:[/bold red] {str(e)}")
96
+ if verbose:
97
+ console.print(traceback.format_exc())
98
+
99
+ # Exit with non-zero status to indicate interruption
100
+ sys.exit(130) # 130 is the standard exit code for SIGINT
101
+
102
+ except anthropic.APIError as e:
103
+ console.print(f"[bold red]❌ Anthropic API Error:[/bold red] {str(e)}")
104
+
105
+ except Exception as e:
106
+ console.print(f"[bold red]❌ Error:[/bold red] {str(e)}")
107
+ if verbose:
108
+ console.print(traceback.format_exc())