janito 0.13.0__py3-none-any.whl → 0.15.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 (50) hide show
  1. janito/__init__.py +1 -1
  2. janito/cli/agent/__init__.py +7 -0
  3. janito/cli/agent/conversation.py +149 -0
  4. janito/cli/agent/initialization.py +168 -0
  5. janito/cli/agent/query.py +112 -0
  6. janito/cli/agent.py +7 -395
  7. janito/cli/app.py +103 -19
  8. janito/cli/commands/__init__.py +12 -0
  9. janito/cli/commands/config.py +30 -0
  10. janito/cli/commands/history.py +119 -0
  11. janito/cli/commands/profile.py +93 -0
  12. janito/cli/commands/validation.py +24 -0
  13. janito/cli/commands/workspace.py +31 -0
  14. janito/cli/commands.py +9 -326
  15. janito/config/README.md +104 -0
  16. janito/config/__init__.py +16 -0
  17. janito/config/cli/__init__.py +28 -0
  18. janito/config/cli/commands.py +397 -0
  19. janito/config/cli/validators.py +77 -0
  20. janito/config/core/__init__.py +23 -0
  21. janito/config/core/file_operations.py +90 -0
  22. janito/config/core/properties.py +316 -0
  23. janito/config/core/singleton.py +282 -0
  24. janito/config/profiles/__init__.py +8 -0
  25. janito/config/profiles/definitions.py +38 -0
  26. janito/config/profiles/manager.py +80 -0
  27. janito/data/instructions_template.txt +12 -6
  28. janito/tools/__init__.py +8 -2
  29. janito/tools/bash/bash.py +80 -7
  30. janito/tools/bash/unix_persistent_bash.py +32 -1
  31. janito/tools/bash/win_persistent_bash.py +34 -1
  32. janito/tools/fetch_webpage/__init__.py +22 -33
  33. janito/tools/fetch_webpage/core.py +182 -155
  34. janito/tools/move_file.py +1 -1
  35. janito/tools/search_text.py +225 -239
  36. janito/tools/str_replace_editor/handlers/view.py +14 -8
  37. janito/tools/think.py +37 -0
  38. janito/tools/usage_tracker.py +1 -0
  39. {janito-0.13.0.dist-info → janito-0.15.0.dist-info}/METADATA +204 -23
  40. janito-0.15.0.dist-info/RECORD +64 -0
  41. janito/config.py +0 -358
  42. janito/test_file.py +0 -4
  43. janito/tools/fetch_webpage/chunking.py +0 -76
  44. janito/tools/fetch_webpage/extractors.py +0 -276
  45. janito/tools/fetch_webpage/news.py +0 -137
  46. janito/tools/fetch_webpage/utils.py +0 -108
  47. janito-0.13.0.dist-info/RECORD +0 -47
  48. {janito-0.13.0.dist-info → janito-0.15.0.dist-info}/WHEEL +0 -0
  49. {janito-0.13.0.dist-info → janito-0.15.0.dist-info}/entry_points.txt +0 -0
  50. {janito-0.13.0.dist-info → janito-0.15.0.dist-info}/licenses/LICENSE +0 -0
janito/__init__.py CHANGED
@@ -2,4 +2,4 @@
2
2
  Janito package.
3
3
  """
4
4
 
5
- __version__ = "0.13.0"
5
+ __version__ = "0.15.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,168 @@
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 or user input.
24
+
25
+ Returns:
26
+ str: The API key
27
+ """
28
+ # Get API key from global config or ask the user
29
+ api_key = Config.get_api_key()
30
+
31
+ # If not found, prompt the user
32
+ if not api_key:
33
+ console.print("[bold yellow]⚠️ Warning:[/bold yellow] API key not found in global config.")
34
+ console.print("🔑 Please set it using --set-api-key or provide your API key now:")
35
+ api_key = typer.prompt("Anthropic API Key", hide_input=True)
36
+
37
+ return api_key
38
+
39
+ def load_instructions() -> str:
40
+ """
41
+ Load instructions template and render it with variables.
42
+
43
+ Returns:
44
+ str: The rendered instructions
45
+ """
46
+ try:
47
+ # For Python 3.9+
48
+ try:
49
+ from importlib.resources import files
50
+ template_content = files('janito.data').joinpath('instructions_template.txt').read_text(encoding='utf-8')
51
+ # Fallback for older Python versions
52
+ except (ImportError, AttributeError):
53
+ template_content = pkg_resources.read_text('janito.data', 'instructions_template.txt', encoding='utf-8')
54
+
55
+ # Create template variables
56
+ template_variables = {
57
+ 'platform': platform.system(),
58
+ 'role': get_config().role,
59
+ # Add any other variables you want to pass to the template here
60
+ }
61
+
62
+ # Create template and render
63
+ template = Template(template_content)
64
+ instructions = template.render(**template_variables)
65
+
66
+ except Exception as e:
67
+ console.print(f"[bold red]❌ Error loading instructions template:[/bold red] {str(e)}")
68
+ # Try to fall back to regular instructions.txt
69
+ try:
70
+ # For Python 3.9+
71
+ try:
72
+ from importlib.resources import files
73
+ instructions = files('janito.data').joinpath('instructions.txt').read_text(encoding='utf-8')
74
+ # Fallback for older Python versions
75
+ except (ImportError, AttributeError):
76
+ instructions = pkg_resources.read_text('janito.data', 'instructions.txt', encoding='utf-8')
77
+ except Exception as e2:
78
+ console.print(f"[bold red]❌ Error loading fallback instructions:[/bold red] {str(e2)}")
79
+ instructions = "You are Janito, an AI assistant."
80
+
81
+ return instructions
82
+
83
+ def initialize_agent(temperature: float, verbose: bool, system_instructions: Optional[str] = None) -> claudine.Agent:
84
+ """
85
+ Initialize the Claude agent with tools and configuration.
86
+
87
+ Args:
88
+ temperature: Temperature value for model generation
89
+ verbose: Whether to enable verbose mode
90
+ system_instructions: Optional custom system instructions to use instead of loading from file
91
+
92
+ Returns:
93
+ claudine.Agent: The initialized agent
94
+ """
95
+ # Get API key
96
+ api_key = get_api_key()
97
+
98
+ # Load instructions or use provided system instructions
99
+ if system_instructions:
100
+ instructions = system_instructions
101
+ if verbose:
102
+ console.print("[bold blue]🔄 Using custom system instructions provided via --system parameter[/bold blue]")
103
+ # Print the first 50 characters of the instructions for verification
104
+ preview = system_instructions[:50] + "..." if len(system_instructions) > 50 else system_instructions
105
+ console.print(f"[dim]System instructions preview: {preview}[/dim]")
106
+ else:
107
+ instructions = load_instructions()
108
+
109
+ # Get tools
110
+ tools_list = get_tools()
111
+
112
+ # Reset usage tracker before each query
113
+ reset_tracker()
114
+
115
+ # Use command line parameters if provided (not default values), otherwise use config
116
+ temp_to_use = temperature if temperature != 0.0 else get_config().temperature
117
+
118
+ # Get profile parameters if a profile is set
119
+ config = get_config()
120
+ profile_data = None
121
+ if config.profile:
122
+ profile_data = config.get_available_profiles()[config.profile]
123
+
124
+ # Display generation parameters if verbose mode is enabled
125
+ if verbose:
126
+ display_generation_params(temp_to_use, profile_data, temperature)
127
+
128
+ # Create config_params dictionary with generation parameters
129
+ config_params = {
130
+ "temperature": temp_to_use
131
+ }
132
+
133
+ # Add top_k and top_p from profile if available
134
+ if profile_data:
135
+ if "top_k" in profile_data and profile_data["top_k"] != 0:
136
+ config_params["top_k"] = profile_data["top_k"]
137
+ if "top_p" in profile_data and profile_data["top_p"] != 0.0:
138
+ config_params["top_p"] = profile_data["top_p"]
139
+
140
+ # Initialize the agent
141
+ if get_config().no_tools:
142
+ # If no_tools mode is enabled, don't pass any tools to the agent
143
+ agent = claudine.Agent(
144
+ api_key=api_key,
145
+ system_prompt=instructions,
146
+ callbacks={"text": text_callback},
147
+ verbose=verbose,
148
+ max_tokens=8126,
149
+ max_tool_rounds=100,
150
+ config_params=config_params,
151
+ # Don't pass any tools, including text_editor_tool and bash_tool
152
+ )
153
+ else:
154
+ # Normal mode with tools
155
+ agent = claudine.Agent(
156
+ api_key=api_key,
157
+ system_prompt=instructions,
158
+ callbacks={"text": text_callback},
159
+ text_editor_tool=str_replace_editor,
160
+ bash_tool=bash_tool,
161
+ tools=tools_list,
162
+ verbose=verbose,
163
+ max_tokens=8126,
164
+ max_tool_rounds=100,
165
+ config_params=config_params,
166
+ )
167
+
168
+ return agent
@@ -0,0 +1,112 @@
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
+ from janito.config import Config
15
+
16
+ console = Console()
17
+
18
+ def handle_query(query: str, temperature: float, verbose: bool, show_tokens: bool, continue_conversation: Optional[str] = None, system_instructions: Optional[str] = None) -> None:
19
+ """
20
+ Handle a query by initializing the agent and sending the query.
21
+
22
+ Args:
23
+ query: The query to send to the agent
24
+ temperature: Temperature value for model generation
25
+ verbose: Whether to enable verbose mode
26
+ show_tokens: Whether to show detailed token usage
27
+ continue_conversation: Optional message ID to continue a specific conversation
28
+ system_instructions: Optional custom system instructions to use instead of loading from file
29
+ """
30
+ # Initialize the agent
31
+ agent = initialize_agent(temperature, verbose, system_instructions)
32
+
33
+ # Load previous messages if continuing conversation
34
+ if continue_conversation is not None:
35
+ # If continue_conversation is an empty string (from flag with no value), use default behavior
36
+ message_id = None if continue_conversation == "" else continue_conversation
37
+ messages = load_messages(message_id)
38
+ if messages:
39
+ agent.set_messages(messages)
40
+ if message_id:
41
+ console.print(f"[bold blue]🔄 Continuing conversation with ID: {message_id}[/bold blue]")
42
+ else:
43
+ console.print("[bold blue]🔄 Continuing most recent conversation[/bold blue]")
44
+
45
+ # Provide information about the conversation being continued
46
+ if verbose and len(messages) > 0:
47
+ # Get the number of messages
48
+ num_messages = len(messages)
49
+ # Get the last user message if available
50
+ last_user_message = next((msg.get("content", "") for msg in reversed(messages)
51
+ if msg.get("role") == "user"), "")
52
+ if last_user_message:
53
+ console.print(f"[dim]📝 Last query: \"{last_user_message[:60]}{'...' if len(last_user_message) > 60 else ''}\"[/dim]")
54
+ else:
55
+ console.print("[bold yellow]⚠️ No previous conversation found to continue[/bold yellow]")
56
+
57
+ # Send the query to the agent
58
+ try:
59
+ agent.query(query)
60
+
61
+ # Save messages after successful query and get the message ID
62
+ message_id = save_messages(agent)
63
+
64
+ # Check if usage reports should be shown
65
+ if Config().show_usage_report:
66
+ # Print token usage report
67
+ if show_tokens:
68
+ generate_token_report(agent, verbose=True, interrupted=False)
69
+ else:
70
+ # Show basic token usage
71
+ generate_token_report(agent, verbose=False, interrupted=False)
72
+
73
+ # Print tool usage statistics
74
+ print_usage_stats()
75
+
76
+
77
+
78
+ except KeyboardInterrupt:
79
+ # Handle Ctrl+C by printing token and tool usage information
80
+ console.print("\n[bold yellow]⚠️ Query interrupted by user (Ctrl+C)[/bold yellow]")
81
+
82
+ # Save messages even if interrupted
83
+ message_id = save_messages(agent)
84
+
85
+ # Check if usage reports should be shown
86
+ if Config().show_usage_report:
87
+ # Print token usage report (even if interrupted)
88
+ try:
89
+ if show_tokens:
90
+ generate_token_report(agent, verbose=True, interrupted=True)
91
+ else:
92
+ # Show basic token usage
93
+ generate_token_report(agent, verbose=False, interrupted=True)
94
+
95
+ # Print tool usage statistics
96
+ print_usage_stats()
97
+
98
+ except Exception as e:
99
+ console.print(f"[bold red]❌ Error generating usage report:[/bold red] {str(e)}")
100
+ if verbose:
101
+ console.print(traceback.format_exc())
102
+
103
+ # Exit with non-zero status to indicate interruption
104
+ sys.exit(130) # 130 is the standard exit code for SIGINT
105
+
106
+ except anthropic.APIError as e:
107
+ console.print(f"[bold red]❌ Anthropic API Error:[/bold red] {str(e)}")
108
+
109
+ except Exception as e:
110
+ console.print(f"[bold red]❌ Error:[/bold red] {str(e)}")
111
+ if verbose:
112
+ console.print(traceback.format_exc())