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.
Files changed (49) hide show
  1. janito/__init__.py +1 -1
  2. janito/__main__.py +3 -147
  3. janito/callbacks.py +13 -109
  4. janito/cli/__init__.py +6 -0
  5. janito/cli/agent.py +287 -0
  6. janito/cli/app.py +86 -0
  7. janito/cli/commands.py +329 -0
  8. janito/cli/output.py +29 -0
  9. janito/cli/utils.py +22 -0
  10. janito/config.py +338 -63
  11. janito/data/instructions_template.txt +27 -0
  12. janito/token_report.py +124 -43
  13. janito/tools/__init__.py +29 -1
  14. janito/tools/bash/bash.py +82 -0
  15. janito/tools/bash/unix_persistent_bash.py +182 -0
  16. janito/tools/bash/win_persistent_bash.py +306 -0
  17. janito/tools/decorators.py +90 -84
  18. janito/tools/delete_file.py +65 -44
  19. janito/tools/fetch_webpage/__init__.py +34 -0
  20. janito/tools/fetch_webpage/chunking.py +76 -0
  21. janito/tools/fetch_webpage/core.py +155 -0
  22. janito/tools/fetch_webpage/extractors.py +276 -0
  23. janito/tools/fetch_webpage/news.py +137 -0
  24. janito/tools/fetch_webpage/utils.py +108 -0
  25. janito/tools/find_files.py +108 -42
  26. janito/tools/move_file.py +72 -0
  27. janito/tools/prompt_user.py +57 -0
  28. janito/tools/replace_file.py +63 -0
  29. janito/tools/rich_console.py +139 -0
  30. janito/tools/search_text.py +33 -21
  31. janito/tools/str_replace_editor/editor.py +55 -43
  32. janito/tools/str_replace_editor/handlers/__init__.py +16 -0
  33. janito/tools/str_replace_editor/handlers/create.py +60 -0
  34. janito/tools/str_replace_editor/handlers/insert.py +100 -0
  35. janito/tools/str_replace_editor/handlers/str_replace.py +92 -0
  36. janito/tools/str_replace_editor/handlers/undo.py +64 -0
  37. janito/tools/str_replace_editor/handlers/view.py +153 -0
  38. janito/tools/str_replace_editor/utils.py +7 -62
  39. janito/tools/usage_tracker.py +136 -0
  40. janito-0.12.0.dist-info/METADATA +203 -0
  41. janito-0.12.0.dist-info/RECORD +47 -0
  42. janito/cli.py +0 -202
  43. janito/data/instructions.txt +0 -4
  44. janito/tools/str_replace_editor/handlers.py +0 -338
  45. janito-0.10.1.dist-info/METADATA +0 -86
  46. janito-0.10.1.dist-info/RECORD +0 -23
  47. {janito-0.10.1.dist-info → janito-0.12.0.dist-info}/WHEEL +0 -0
  48. {janito-0.10.1.dist-info → janito-0.12.0.dist-info}/entry_points.txt +0 -0
  49. {janito-0.10.1.dist-info → janito-0.12.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.11.0"
5
+ __version__ = "0.12.0"
janito/__main__.py CHANGED
@@ -1,151 +1,7 @@
1
1
  """
2
- Main entry point for the janito CLI.
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
- def pre_tool_callback(tool_name: str, tool_input: Dict[str, Any], preamble_text: str = "") -> Tuple[Dict[str, Any], bool]:
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 post_tool_callback(tool_name: str, tool_input: Dict[str, Any], result: Any) -> Any:
13
+ def text_callback(text: str) -> None:
74
14
  """
75
- Callback function that runs after a tool is executed.
15
+ Callback function that handles text output from the agent.
76
16
 
77
17
  Args:
78
- tool_name: Name of the tool that was called
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
- Modified result
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(post_tool_callback, "counter"):
90
- post_tool_callback.counter = 1
91
- console.print(f"[bold green]DEBUG: Completed tool call #{post_tool_callback.counter}[/bold green]")
92
- post_tool_callback.counter += 1
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
- # Extract the last line of the result
95
- if isinstance(result, tuple) and len(result) >= 1:
96
- content, is_error = result
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
@@ -0,0 +1,6 @@
1
+ """
2
+ CLI module for Janito.
3
+ """
4
+ from janito.cli.app import app
5
+
6
+ __all__ = ["app"]
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)