janito 0.12.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.
Files changed (40) 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 +172 -0
  5. janito/cli/agent/query.py +108 -0
  6. janito/cli/agent.py +7 -282
  7. janito/cli/app.py +105 -9
  8. janito/cli/commands/__init__.py +12 -0
  9. janito/cli/commands/config.py +242 -0
  10. janito/cli/commands/history.py +119 -0
  11. janito/cli/commands/profile.py +72 -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.py +37 -0
  16. janito/data/instructions_template.txt +9 -5
  17. janito/tools/__init__.py +8 -2
  18. janito/tools/bash/bash.py +3 -1
  19. janito/tools/bash/unix_persistent_bash.py +183 -181
  20. janito/tools/bash/win_persistent_bash.py +4 -2
  21. janito/tools/fetch_webpage/__init__.py +22 -33
  22. janito/tools/fetch_webpage/core.py +182 -155
  23. janito/tools/rich_console.py +46 -9
  24. janito/tools/search_text.py +225 -238
  25. janito/tools/str_replace_editor/handlers/str_replace.py +3 -1
  26. janito/tools/str_replace_editor/handlers/view.py +14 -8
  27. janito/tools/think.py +37 -0
  28. janito/tools/usage_tracker.py +1 -0
  29. janito-0.14.0.dist-info/METADATA +396 -0
  30. janito-0.14.0.dist-info/RECORD +53 -0
  31. janito/test_file.py +0 -4
  32. janito/tools/fetch_webpage/chunking.py +0 -76
  33. janito/tools/fetch_webpage/extractors.py +0 -276
  34. janito/tools/fetch_webpage/news.py +0 -137
  35. janito/tools/fetch_webpage/utils.py +0 -108
  36. janito-0.12.0.dist-info/METADATA +0 -203
  37. janito-0.12.0.dist-info/RECORD +0 -47
  38. {janito-0.12.0.dist-info → janito-0.14.0.dist-info}/WHEEL +0 -0
  39. {janito-0.12.0.dist-info → janito-0.14.0.dist-info}/entry_points.txt +0 -0
  40. {janito-0.12.0.dist-info → janito-0.14.0.dist-info}/licenses/LICENSE +0 -0
janito/cli/agent.py CHANGED
@@ -1,287 +1,12 @@
1
1
  """
2
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
3
 
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)}")
4
+ This file is a compatibility layer that imports from the new module structure.
5
+ """
183
6
 
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
7
+ # Import the public API from the new module structure
8
+ from janito.cli.agent.query import handle_query
9
+ from janito.cli.agent.conversation import load_messages, save_messages
213
10
 
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())
11
+ # Export the public API
12
+ __all__ = ["handle_query", "load_messages", "save_messages"]
janito/cli/app.py CHANGED
@@ -7,6 +7,7 @@ import typer
7
7
  from rich.console import Console
8
8
  import importlib.metadata
9
9
 
10
+ from janito import __version__
10
11
  from janito.config import get_config
11
12
  from janito.cli.commands import handle_config_commands, validate_parameters
12
13
  from janito.cli.agent import handle_query
@@ -19,18 +20,23 @@ console = Console()
19
20
  def main(ctx: typer.Context,
20
21
  query: Optional[str] = typer.Argument(None, help="Query to send to the claudine agent"),
21
22
  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
+ show_tokens: bool = typer.Option(False, "--show-tokens", "--tokens", help="Show detailed token usage and pricing information"),
23
24
  workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Set the workspace directory"),
24
25
  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
26
  show_config: bool = typer.Option(False, "--show-config", help="Show current configuration"),
26
27
  reset_config: bool = typer.Option(False, "--reset-config", help="Reset configuration by removing the config file"),
27
28
  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
29
  ask: bool = typer.Option(False, "--ask", help="Enable ask mode which disables tools that perform changes"),
30
+ trust: bool = typer.Option(False, "--trust", "-t", help="Enable trust mode which suppresses tool outputs for a more concise execution"),
31
+ no_tools: bool = typer.Option(False, "--no-tools", help="Disable all tools for this session (per-session setting, not saved to config)"),
29
32
  temperature: float = typer.Option(0.0, "--temperature", help="Set the temperature for model generation (0.0 to 1.0)"),
30
33
  profile: Optional[str] = typer.Option(None, "--profile", help="Use a predefined parameter profile (precise, balanced, conversational, creative, technical)"),
31
34
  role: Optional[str] = typer.Option(None, "--role", help="Set the assistant's role (default: 'software engineer')"),
35
+ system: Optional[str] = typer.Option(None, "--system", "-s", help="Provide custom system instructions, bypassing the default file load method"),
32
36
  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")):
37
+ continue_id: Optional[str] = typer.Option(None, "--continue-id", help="Continue a specific conversation with the given ID"),
38
+ continue_flag: Optional[str] = typer.Option(None, "--continue", "-c", help="Continue a conversation. Can be used as: 1) --continue (to continue most recent), 2) --continue 123 (to continue conversation with ID 123), or 3) --continue \"query\" (to continue most recent with new query)"),
39
+ history_flag: bool = typer.Option(False, "--history", help="Show a summary of conversations. Use --history for default (20) or --history n to specify count")):
34
40
  """
35
41
  Janito CLI tool. If a query is provided without a command, it will be sent to the claudine agent.
36
42
  """
@@ -40,22 +46,108 @@ def main(ctx: typer.Context,
40
46
  # Set ask mode in config
41
47
  get_config().ask_mode = ask
42
48
 
49
+ # Set trust mode in config
50
+ get_config().trust_mode = trust
51
+
52
+ # Set no-tools mode in config
53
+ get_config().no_tools = no_tools
54
+
43
55
  # Show a message if ask mode is enabled
44
56
  if ask:
45
57
  console.print("[bold yellow]⚠️ Ask Mode enabled:[/bold yellow] 🔒 Tools that perform changes are disabled")
58
+
59
+ # Show a message if trust mode is enabled
60
+ if trust:
61
+ console.print("[bold blue]⚡ Trust Mode enabled:[/bold blue] Tool outputs are suppressed for concise execution")
62
+
63
+ # Show a message if no-tools mode is enabled
64
+ if no_tools:
65
+ console.print("[bold magenta]🚫 No-Tools Mode enabled:[/bold magenta] All tools are disabled for this session")
46
66
 
47
67
  # Show version and exit if requested
48
68
  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]")
69
+ console.print(f"🚀 Janito version: {__version__}")
54
70
  sys.exit(0)
55
71
 
56
72
  # Validate temperature
57
73
  validate_parameters(temperature)
58
74
 
75
+ # Process continue flags before handling other options
76
+ continue_conversation = None
77
+
78
+ # First, parse continue_flag and continue_id from original sys.argv to avoid typer issues
79
+ # This is necessary because typer has trouble with quotes in some edge cases
80
+ try:
81
+ # Check if --continue or -c is in sys.argv
82
+ args = sys.argv
83
+
84
+ # Handle the --history flag with optional count parameter
85
+ history_count_override = None
86
+ if "--history" in args:
87
+ history_idx = args.index("--history")
88
+ history_flag = True
89
+
90
+ # Check if there's a number after --history and it's not another flag
91
+ if history_idx + 1 < len(args) and not args[history_idx + 1].startswith("-"):
92
+ try:
93
+ # Try to convert to int - if successful, it's a count
94
+ history_count_override = int(args[history_idx + 1])
95
+ except ValueError:
96
+ # Not a number, ignore it
97
+ pass
98
+
99
+ if "--continue" in args or "-c" in args:
100
+ continue_idx = args.index("--continue") if "--continue" in args else args.index("-c")
101
+
102
+ # Check if there's at least one argument after --continue
103
+ if continue_idx + 1 < len(args) and not args[continue_idx + 1].startswith("-"):
104
+ # If next arg doesn't start with "-", it's our continue value
105
+ continue_value = args[continue_idx + 1]
106
+
107
+ # Check if continue_value is a numeric ID or a query
108
+ if continue_value.isdigit():
109
+ # It's an ID
110
+ continue_conversation = continue_value
111
+
112
+ # Check if there's a query after the ID
113
+ if continue_idx + 2 < len(args) and not args[continue_idx + 2].startswith("-"):
114
+ query = args[continue_idx + 2]
115
+ else:
116
+ # It's a query string for the most recent conversation
117
+ continue_conversation = "" # Empty string means continue most recent
118
+ query = continue_value
119
+
120
+ if verbose:
121
+ console.print(f"[bold blue]🔄 Continuing most recent conversation[/bold blue]")
122
+ console.print(f"[dim]📝 Query: {query}[/dim]")
123
+ else:
124
+ # --continue with no args means continue most recent conversation
125
+ continue_conversation = ""
126
+
127
+ # Handle explicit --continue-id if specified (this takes precedence)
128
+ if "--continue-id" in args:
129
+ continue_id_idx = args.index("--continue-id")
130
+ if continue_id_idx + 1 < len(args) and not args[continue_id_idx + 1].startswith("-"):
131
+ continue_conversation = args[continue_id_idx + 1]
132
+ except Exception as e:
133
+ if verbose:
134
+ console.print(f"[bold yellow]⚠️ Error parsing continue arguments: {str(e)}[/bold yellow]")
135
+
136
+ # Fall back to typer-processed args if our parsing failed
137
+ if continue_conversation is None:
138
+ # Handle the --continue-id option
139
+ if continue_id is not None:
140
+ continue_conversation = continue_id
141
+ # Handle the --continue flag option (processed by typer)
142
+ elif continue_flag is not None:
143
+ if continue_flag == "":
144
+ continue_conversation = "" # Empty string means continue most recent
145
+ elif continue_flag.isdigit():
146
+ continue_conversation = continue_flag
147
+ else:
148
+ continue_conversation = "" # Empty string means continue most recent
149
+ query = continue_flag # Use the continue_flag as the query
150
+
59
151
  # Handle configuration-related commands
60
152
  exit_after_config = handle_config_commands(
61
153
  ctx,
@@ -67,7 +159,10 @@ def main(ctx: typer.Context,
67
159
  set_api_key,
68
160
  config_str,
69
161
  query,
70
- continue_conversation
162
+ continue_id,
163
+ continue_flag,
164
+ history_flag,
165
+ history_count_override
71
166
  )
72
167
 
73
168
  if exit_after_config:
@@ -76,6 +171,7 @@ def main(ctx: typer.Context,
76
171
  # Handle query if no subcommand was invoked
77
172
  if ctx.invoked_subcommand is None:
78
173
  # If no query provided in command line, read from stdin
174
+ # Only prompt for stdin if query is still None after processing --continue flag
79
175
  if not query:
80
176
  console.print("[bold blue]📝 No query provided in command line. Reading from stdin...[/bold blue]")
81
177
  console.print(get_stdin_termination_hint())
@@ -83,4 +179,4 @@ def main(ctx: typer.Context,
83
179
 
84
180
  # Only proceed if we have a query (either from command line or stdin)
85
181
  if query:
86
- handle_query(query, temperature, verbose, show_tokens, continue_conversation)
182
+ handle_query(query, temperature, verbose, show_tokens, continue_conversation, system)
@@ -0,0 +1,12 @@
1
+ """
2
+ Command handling logic for Janito CLI.
3
+ """
4
+ from janito.cli.commands.config import handle_config_commands
5
+ from janito.cli.commands.validation import validate_parameters
6
+ from janito.cli.commands.history import handle_history
7
+
8
+ __all__ = [
9
+ "handle_config_commands",
10
+ "validate_parameters",
11
+ "handle_history",
12
+ ]