janito 0.10.1__tar.gz → 0.11.0__tar.gz
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-0.10.1 → janito-0.11.0}/.gitignore +2 -1
- {janito-0.10.1 → janito-0.11.0}/PKG-INFO +1 -1
- janito-0.11.0/janito/__main__.py +205 -0
- janito-0.11.0/janito/callbacks.py +132 -0
- janito-0.11.0/janito/chat_history.py +117 -0
- janito-0.11.0/janito/config.py +121 -0
- {janito-0.10.1 → janito-0.11.0}/janito/data/instructions.txt +4 -4
- janito-0.11.0/janito/token_report.py +145 -0
- janito-0.11.0/janito/tools/__init__.py +21 -0
- janito-0.11.0/janito/tools/bash.py +22 -0
- {janito-0.10.1 → janito-0.11.0}/janito/tools/decorators.py +101 -84
- {janito-0.10.1 → janito-0.11.0}/janito/tools/delete_file.py +47 -44
- {janito-0.10.1 → janito-0.11.0}/janito/tools/find_files.py +11 -7
- janito-0.11.0/janito/tools/prompt_user.py +26 -0
- janito-0.11.0/janito/tools/replace_file.py +36 -0
- {janito-0.10.1 → janito-0.11.0}/janito/tools/str_replace_editor/editor.py +52 -43
- {janito-0.10.1 → janito-0.11.0}/janito/tools/str_replace_editor/handlers.py +102 -105
- janito-0.11.0/janito/tools/str_replace_editor/utils.py +34 -0
- {janito-0.10.1 → janito-0.11.0}/pyproject.toml +1 -1
- janito-0.10.1/janito/__main__.py +0 -151
- janito-0.10.1/janito/callbacks.py +0 -130
- janito-0.10.1/janito/cli.py +0 -202
- janito-0.10.1/janito/config.py +0 -63
- janito-0.10.1/janito/token_report.py +0 -73
- janito-0.10.1/janito/tools/__init__.py +0 -10
- janito-0.10.1/janito/tools/str_replace_editor/utils.py +0 -88
- {janito-0.10.1 → janito-0.11.0}/LICENSE +0 -0
- {janito-0.10.1 → janito-0.11.0}/README.md +0 -0
- {janito-0.10.1 → janito-0.11.0}/janito/__init__.py +0 -0
- {janito-0.10.1 → janito-0.11.0}/janito/test_file.py +0 -0
- {janito-0.10.1 → janito-0.11.0}/janito/tools/search_text.py +0 -0
- {janito-0.10.1 → janito-0.11.0}/janito/tools/str_replace_editor/__init__.py +0 -0
@@ -0,0 +1,205 @@
|
|
1
|
+
"""
|
2
|
+
Main entry point for Janito.
|
3
|
+
"""
|
4
|
+
import os
|
5
|
+
import sys
|
6
|
+
from typing import Optional
|
7
|
+
import importlib.resources
|
8
|
+
import typer
|
9
|
+
from rich.console import Console
|
10
|
+
import anthropic
|
11
|
+
from janito.config import get_config
|
12
|
+
from janito.chat_history import store_conversation, get_chat_history_context
|
13
|
+
from janito.callbacks import pre_tool_callback, post_tool_callback, text_callback
|
14
|
+
from janito.token_report import generate_token_report
|
15
|
+
from janito.tools import str_replace_editor
|
16
|
+
from janito.tools.bash import bash_tool
|
17
|
+
import claudine
|
18
|
+
|
19
|
+
app = typer.Typer()
|
20
|
+
|
21
|
+
@app.command()
|
22
|
+
def create_tool(name: str = typer.Argument(..., help="Name of the tool to create")):
|
23
|
+
"""
|
24
|
+
Create a new tool with the given name.
|
25
|
+
"""
|
26
|
+
console = Console()
|
27
|
+
|
28
|
+
# Ensure workspace is set
|
29
|
+
workspace_dir = get_config().workspace_dir
|
30
|
+
|
31
|
+
# Create the tools directory if it doesn't exist
|
32
|
+
tools_dir = os.path.join(workspace_dir, "tools")
|
33
|
+
os.makedirs(tools_dir, exist_ok=True)
|
34
|
+
|
35
|
+
# Create the tool file
|
36
|
+
tool_file = os.path.join(tools_dir, f"{name}.py")
|
37
|
+
|
38
|
+
# Check if the file already exists
|
39
|
+
if os.path.exists(tool_file):
|
40
|
+
console.print(f"[bold red]Error:[/bold red] Tool file already exists: {tool_file}")
|
41
|
+
return
|
42
|
+
|
43
|
+
# Create the tool file with a template
|
44
|
+
template = f'''"""
|
45
|
+
{name} tool for Janito.
|
46
|
+
"""
|
47
|
+
|
48
|
+
def {name}(param1: str) -> str:
|
49
|
+
"""
|
50
|
+
Description of the {name} tool.
|
51
|
+
|
52
|
+
Args:
|
53
|
+
param1: Description of param1
|
54
|
+
|
55
|
+
Returns:
|
56
|
+
str: Description of return value
|
57
|
+
"""
|
58
|
+
# TODO: Implement the tool
|
59
|
+
return f"Executed {name} with param1={{param1}}"
|
60
|
+
'''
|
61
|
+
|
62
|
+
with open(tool_file, "w") as f:
|
63
|
+
f.write(template)
|
64
|
+
|
65
|
+
console.print(f"[bold green]Created tool:[/bold green] {tool_file}")
|
66
|
+
|
67
|
+
@app.callback(invoke_without_command=True)
|
68
|
+
def main(ctx: typer.Context,
|
69
|
+
query: Optional[str] = typer.Argument(None, help="Query to send to the claudine agent"),
|
70
|
+
verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose mode with detailed output"),
|
71
|
+
show_tokens: bool = typer.Option(False, "--show-tokens", "-t", help="Show detailed token usage and pricing information"),
|
72
|
+
workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Set the workspace directory"),
|
73
|
+
config_str: Optional[str] = typer.Option(None, "--set-config", help="Configuration string in format 'key=value', e.g., 'context=5' for number of history messages to include"),
|
74
|
+
show_config: bool = typer.Option(False, "--show-config", help="Show current configuration")):
|
75
|
+
"""
|
76
|
+
Janito CLI tool. If a query is provided without a command, it will be sent to the claudine agent.
|
77
|
+
"""
|
78
|
+
console = Console()
|
79
|
+
|
80
|
+
# Set verbose mode in config
|
81
|
+
get_config().verbose = verbose
|
82
|
+
|
83
|
+
if workspace:
|
84
|
+
try:
|
85
|
+
print(f"Setting workspace directory to: {workspace}")
|
86
|
+
get_config().workspace_dir = workspace
|
87
|
+
print(f"Workspace directory set to: {get_config().workspace_dir}")
|
88
|
+
except ValueError as e:
|
89
|
+
console.print(f"[bold red]Error:[/bold red] {str(e)}")
|
90
|
+
sys.exit(1)
|
91
|
+
|
92
|
+
# Show current configuration if requested
|
93
|
+
if show_config:
|
94
|
+
config = get_config()
|
95
|
+
console.print("[bold blue]Current Configuration:[/bold blue]")
|
96
|
+
console.print(f"[bold]Workspace Directory:[/bold] {config.workspace_dir}")
|
97
|
+
console.print(f"[bold]Verbose Mode:[/bold] {'Enabled' if config.verbose else 'Disabled'}")
|
98
|
+
console.print(f"[bold]Chat History Context Count:[/bold] {config.history_context_count} messages")
|
99
|
+
# Exit if this was the only operation requested
|
100
|
+
if ctx.invoked_subcommand is None and not query:
|
101
|
+
sys.exit(0)
|
102
|
+
|
103
|
+
# Handle the --set-config parameter
|
104
|
+
if config_str is not None:
|
105
|
+
try:
|
106
|
+
# Parse the config string
|
107
|
+
if "context=" in config_str:
|
108
|
+
context_value = config_str.split("context=")[1].strip()
|
109
|
+
# If there are other configs after context, extract just the number
|
110
|
+
if " " in context_value:
|
111
|
+
context_value = context_value.split(" ")[0]
|
112
|
+
|
113
|
+
try:
|
114
|
+
context_value = int(context_value)
|
115
|
+
if context_value < 0:
|
116
|
+
console.print("[bold red]Error:[/bold red] History context count must be a non-negative integer")
|
117
|
+
return
|
118
|
+
|
119
|
+
get_config().history_context_count = context_value
|
120
|
+
console.print(f"[bold green]Chat history context count set to {context_value} messages[/bold green]")
|
121
|
+
except ValueError:
|
122
|
+
console.print(f"[bold red]Error:[/bold red] Invalid context value: {context_value}. Must be an integer.")
|
123
|
+
else:
|
124
|
+
console.print(f"[bold yellow]Warning:[/bold yellow] Unsupported configuration in: {config_str}")
|
125
|
+
except Exception as e:
|
126
|
+
console.print(f"[bold red]Error:[/bold red] {str(e)}")
|
127
|
+
|
128
|
+
if ctx.invoked_subcommand is None:
|
129
|
+
# If no query provided in command line, read from stdin
|
130
|
+
if not query:
|
131
|
+
console.print("[bold blue]No query provided in command line. Reading from stdin...[/bold blue]")
|
132
|
+
query = sys.stdin.read().strip()
|
133
|
+
|
134
|
+
# Only proceed if we have a query (either from command line or stdin)
|
135
|
+
if query:
|
136
|
+
# Get API key from environment variable or ask the user
|
137
|
+
api_key = os.environ.get("ANTHROPIC_API_KEY")
|
138
|
+
if not api_key:
|
139
|
+
console.print("[bold yellow]Warning:[/bold yellow] ANTHROPIC_API_KEY environment variable not set.")
|
140
|
+
console.print("Please set it or provide your API key now:")
|
141
|
+
api_key = typer.prompt("Anthropic API Key", hide_input=True)
|
142
|
+
|
143
|
+
# Load instructions from file
|
144
|
+
import importlib.resources as pkg_resources
|
145
|
+
try:
|
146
|
+
# For Python 3.9+
|
147
|
+
try:
|
148
|
+
from importlib.resources import files
|
149
|
+
instructions = files('janito.data').joinpath('instructions.txt').read_text(encoding='utf-8')
|
150
|
+
# Fallback for older Python versions
|
151
|
+
except (ImportError, AttributeError):
|
152
|
+
instructions = pkg_resources.read_text('janito.data', 'instructions.txt', encoding='utf-8')
|
153
|
+
except Exception as e:
|
154
|
+
console.print(f"[bold red]Error loading instructions:[/bold red] {str(e)}")
|
155
|
+
instructions = "You are Janito, an AI assistant."
|
156
|
+
|
157
|
+
# Temporarily disable chat history
|
158
|
+
# Get chat history context
|
159
|
+
# chat_history = get_chat_history_context(get_config().history_context_count)
|
160
|
+
# if chat_history:
|
161
|
+
# console.print("[dim]Loaded chat history from previous sessions.[/dim]")
|
162
|
+
# # Append chat history to instructions
|
163
|
+
# instructions = f"{instructions}\n\n{chat_history}"
|
164
|
+
|
165
|
+
# Get tools
|
166
|
+
from janito.tools import get_tools
|
167
|
+
tools_list = get_tools()
|
168
|
+
|
169
|
+
# Initialize the agent with the tools
|
170
|
+
agent = claudine.Agent(
|
171
|
+
api_key=api_key,
|
172
|
+
system_prompt=instructions,
|
173
|
+
callbacks={"pre_tool": pre_tool_callback, "post_tool": post_tool_callback, "text": text_callback},
|
174
|
+
text_editor_tool=str_replace_editor,
|
175
|
+
#bash_tool=bash_tool,
|
176
|
+
tools=tools_list,
|
177
|
+
verbose=verbose
|
178
|
+
)
|
179
|
+
|
180
|
+
# Send the query to the agent
|
181
|
+
try:
|
182
|
+
agent.query(query)
|
183
|
+
|
184
|
+
# Temporarily disable storing conversation in chat history
|
185
|
+
# Store the conversation in chat history
|
186
|
+
# store_conversation(query, response, agent)
|
187
|
+
|
188
|
+
# Print token usage report if show_tokens mode is enabled
|
189
|
+
if show_tokens:
|
190
|
+
generate_token_report(agent, verbose=True)
|
191
|
+
else:
|
192
|
+
# Show basic token usage
|
193
|
+
generate_token_report(agent, verbose=False)
|
194
|
+
|
195
|
+
except anthropic.APIError as e:
|
196
|
+
console.print(f"[bold red]Anthropic API Error:[/bold red] {str(e)}")
|
197
|
+
|
198
|
+
except Exception as e:
|
199
|
+
console.print(f"[bold red]Error:[/bold red] {str(e)}")
|
200
|
+
if verbose:
|
201
|
+
import traceback
|
202
|
+
console.print(traceback.format_exc())
|
203
|
+
|
204
|
+
if __name__ == "__main__":
|
205
|
+
app()
|
@@ -0,0 +1,132 @@
|
|
1
|
+
"""
|
2
|
+
Callback functions for tool execution in janito.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from typing import Dict, Any, Tuple, Optional, List
|
6
|
+
from rich.console import Console
|
7
|
+
from rich.markdown import Markdown
|
8
|
+
|
9
|
+
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
|
+
|
16
|
+
# Note: ConsoleCallback has been removed as we're using pre_tool and post_tool callbacks directly
|
17
|
+
|
18
|
+
def pre_tool_callback(tool_name: str, tool_input: Dict[str, Any]) -> Tuple[Dict[str, Any], bool]:
|
19
|
+
"""
|
20
|
+
Callback function that runs before a tool is executed.
|
21
|
+
|
22
|
+
Args:
|
23
|
+
tool_name: Name of the tool being called
|
24
|
+
tool_input: Input parameters for the tool
|
25
|
+
|
26
|
+
Returns:
|
27
|
+
Tuple of (modified tool input, whether to cancel the tool call)
|
28
|
+
"""
|
29
|
+
console = Console()
|
30
|
+
|
31
|
+
# Add debug counter only when verbose mode is enabled
|
32
|
+
if get_config().verbose:
|
33
|
+
if not hasattr(pre_tool_callback, "counter"):
|
34
|
+
pre_tool_callback.counter = 1
|
35
|
+
console.print(f"[bold green]DEBUG: Starting tool call #{pre_tool_callback.counter}[/bold green]")
|
36
|
+
|
37
|
+
# Print the tool name and input
|
38
|
+
console.print(f"[bold green]Tool:[/bold green] {tool_name}")
|
39
|
+
console.print(f"[bold green]Input:[/bold green] {tool_input}")
|
40
|
+
else:
|
41
|
+
# For non-debug mode, just print a simple message
|
42
|
+
# Find the tool function
|
43
|
+
tool_func = None
|
44
|
+
if tool_name == "find_files":
|
45
|
+
tool_func = find_files
|
46
|
+
elif tool_name == "str_replace_editor":
|
47
|
+
tool_func = str_replace_editor
|
48
|
+
elif tool_name == "delete_file":
|
49
|
+
tool_func = delete_file
|
50
|
+
elif tool_name == "search_text":
|
51
|
+
tool_func = search_text
|
52
|
+
|
53
|
+
# Format the input for display
|
54
|
+
display_input = ""
|
55
|
+
if "path" in tool_input:
|
56
|
+
display_input = tool_input["path"]
|
57
|
+
elif "file_path" in tool_input:
|
58
|
+
display_input = tool_input["file_path"]
|
59
|
+
|
60
|
+
# Print formatted tool label if available
|
61
|
+
formatted_label = format_tool_label(tool_func, tool_input)
|
62
|
+
if formatted_label:
|
63
|
+
console.print("[bold cyan] Tool:[/bold cyan]", formatted_label, end=" → ")
|
64
|
+
else:
|
65
|
+
console.print("[bold cyan] Tool:[/bold cyan]", f"{tool_name} {display_input}", end=" → ")
|
66
|
+
|
67
|
+
return tool_input, True # Continue with the tool call
|
68
|
+
|
69
|
+
def post_tool_callback(tool_name: str, tool_input: Dict[str, Any], result: Any) -> Any:
|
70
|
+
"""
|
71
|
+
Callback function that runs after a tool is executed.
|
72
|
+
|
73
|
+
Args:
|
74
|
+
tool_name: Name of the tool that was called
|
75
|
+
tool_input: Input parameters for the tool
|
76
|
+
result: Result of the tool call
|
77
|
+
|
78
|
+
Returns:
|
79
|
+
Modified result
|
80
|
+
"""
|
81
|
+
console = Console()
|
82
|
+
|
83
|
+
# Add debug counter only when verbose mode is enabled
|
84
|
+
if get_config().verbose:
|
85
|
+
if not hasattr(post_tool_callback, "counter"):
|
86
|
+
post_tool_callback.counter = 1
|
87
|
+
console.print(f"[bold green]DEBUG: Completed tool call #{post_tool_callback.counter}[/bold green]")
|
88
|
+
post_tool_callback.counter += 1
|
89
|
+
|
90
|
+
# Show the number of lines in the result content
|
91
|
+
if isinstance(result, tuple) and len(result) >= 1:
|
92
|
+
content, is_error = result
|
93
|
+
# Define prefix icon based on is_error
|
94
|
+
icon_prefix = "❌ " if is_error else "✅ "
|
95
|
+
|
96
|
+
if isinstance(content, str):
|
97
|
+
# Count the number of lines in the content
|
98
|
+
line_count = content.count('\n') + 1 if content else 0
|
99
|
+
console.print(f"{icon_prefix}{line_count} items")
|
100
|
+
else:
|
101
|
+
console.print(f"{icon_prefix}{content}")
|
102
|
+
else:
|
103
|
+
# If result is not a tuple, convert to string and count lines
|
104
|
+
result_str = str(result)
|
105
|
+
# Default to success icon when no error status is available
|
106
|
+
icon_prefix = "✅ "
|
107
|
+
line_count = result_str.count('\n') + 1 if result_str else 0
|
108
|
+
console.print(f"{icon_prefix}{line_count} lines")
|
109
|
+
|
110
|
+
return result
|
111
|
+
|
112
|
+
def text_callback(text: str) -> None:
|
113
|
+
"""
|
114
|
+
Callback function that handles text output from the agent.
|
115
|
+
|
116
|
+
Args:
|
117
|
+
text: Text output from the agent
|
118
|
+
|
119
|
+
Returns:
|
120
|
+
None
|
121
|
+
"""
|
122
|
+
console = Console()
|
123
|
+
|
124
|
+
# Add debug counter only when debug mode is enabled
|
125
|
+
if get_config().debug_mode:
|
126
|
+
if not hasattr(text_callback, "counter"):
|
127
|
+
text_callback.counter = 1
|
128
|
+
console.print(f"[bold blue]DEBUG: Text callback #{text_callback.counter}[/bold blue]")
|
129
|
+
text_callback.counter += 1
|
130
|
+
|
131
|
+
# Print the text with markdown formatting
|
132
|
+
console.print("[bold magenta]Janito:[/bold magenta] ", Markdown(text, code_theme="monokai"), end="")
|
@@ -0,0 +1,117 @@
|
|
1
|
+
"""
|
2
|
+
Chat history module for Janito.
|
3
|
+
Handles storing and loading chat history.
|
4
|
+
"""
|
5
|
+
import os
|
6
|
+
import json
|
7
|
+
import datetime
|
8
|
+
from pathlib import Path
|
9
|
+
from typing import List, Dict, Any, Optional
|
10
|
+
from janito.config import get_config
|
11
|
+
|
12
|
+
def ensure_chat_history_dir() -> Path:
|
13
|
+
"""
|
14
|
+
Ensure the chat history directory exists.
|
15
|
+
|
16
|
+
Returns:
|
17
|
+
Path: Path to the chat history directory
|
18
|
+
"""
|
19
|
+
workspace_dir = get_config().workspace_dir
|
20
|
+
chat_history_dir = Path(workspace_dir) / ".janito" / "chat_history"
|
21
|
+
chat_history_dir.mkdir(parents=True, exist_ok=True)
|
22
|
+
return chat_history_dir
|
23
|
+
|
24
|
+
|
25
|
+
|
26
|
+
def store_conversation(query: str, response: str, agent=None) -> None:
|
27
|
+
"""
|
28
|
+
Store a conversation in the chat history.
|
29
|
+
|
30
|
+
Args:
|
31
|
+
query: The user's query
|
32
|
+
response: The agent's response
|
33
|
+
agent: Optional agent instance for using get_messages method
|
34
|
+
"""
|
35
|
+
chat_history_dir = ensure_chat_history_dir()
|
36
|
+
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
37
|
+
filename = f"{timestamp}.json"
|
38
|
+
|
39
|
+
# Create the conversation data
|
40
|
+
conversation = {
|
41
|
+
"timestamp": timestamp,
|
42
|
+
"query": query,
|
43
|
+
"response": response
|
44
|
+
}
|
45
|
+
|
46
|
+
# Write to file
|
47
|
+
with open(chat_history_dir / filename, "w", encoding="utf-8") as f:
|
48
|
+
json.dump(conversation, f, ensure_ascii=False, indent=2)
|
49
|
+
|
50
|
+
def load_recent_conversations(count: int = 5) -> List[Dict[str, str]]:
|
51
|
+
"""
|
52
|
+
Load the most recent conversations from the chat history.
|
53
|
+
|
54
|
+
Args:
|
55
|
+
count: Number of conversations to load
|
56
|
+
|
57
|
+
Returns:
|
58
|
+
List[Dict[str, str]]: List of conversations
|
59
|
+
"""
|
60
|
+
chat_history_dir = ensure_chat_history_dir()
|
61
|
+
|
62
|
+
# Get all JSON files in the chat history directory
|
63
|
+
history_files = list(chat_history_dir.glob("*.json"))
|
64
|
+
|
65
|
+
# Sort by filename (which includes timestamp)
|
66
|
+
history_files.sort(reverse=True)
|
67
|
+
|
68
|
+
# Load the most recent conversations
|
69
|
+
conversations = []
|
70
|
+
for file_path in history_files[:count]:
|
71
|
+
try:
|
72
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
73
|
+
conversation = json.load(f)
|
74
|
+
conversations.append(conversation)
|
75
|
+
except Exception as e:
|
76
|
+
print(f"Error loading chat history file {file_path}: {e}")
|
77
|
+
|
78
|
+
return conversations
|
79
|
+
|
80
|
+
def format_conversation_for_context(conversation: Dict[str, str]) -> str:
|
81
|
+
"""
|
82
|
+
Format a conversation for inclusion in the context.
|
83
|
+
|
84
|
+
Args:
|
85
|
+
conversation: The conversation to format
|
86
|
+
|
87
|
+
Returns:
|
88
|
+
str: The formatted conversation
|
89
|
+
"""
|
90
|
+
timestamp = conversation.get("timestamp", "Unknown time")
|
91
|
+
query = conversation.get("query", "")
|
92
|
+
response = conversation.get("response", "")
|
93
|
+
|
94
|
+
formatted_time = datetime.datetime.strptime(timestamp, "%Y%m%d_%H%M%S").strftime("%Y-%m-%d %H:%M:%S")
|
95
|
+
|
96
|
+
return f"--- Conversation from {formatted_time} ---\nUser: {query}\n\nAssistant: {response}\n\n"
|
97
|
+
|
98
|
+
def get_chat_history_context(count: int = 5) -> str:
|
99
|
+
"""
|
100
|
+
Get the chat history formatted for inclusion in the agent's context.
|
101
|
+
|
102
|
+
Args:
|
103
|
+
count: Number of conversations to include
|
104
|
+
|
105
|
+
Returns:
|
106
|
+
str: The formatted chat history
|
107
|
+
"""
|
108
|
+
conversations = load_recent_conversations(count)
|
109
|
+
|
110
|
+
if not conversations:
|
111
|
+
return ""
|
112
|
+
|
113
|
+
context = "# Previous conversations:\n\n"
|
114
|
+
for conversation in conversations:
|
115
|
+
context += format_conversation_for_context(conversation)
|
116
|
+
|
117
|
+
return context
|
@@ -0,0 +1,121 @@
|
|
1
|
+
"""
|
2
|
+
Configuration module for Janito.
|
3
|
+
Provides a singleton Config class to access configuration values.
|
4
|
+
"""
|
5
|
+
import os
|
6
|
+
import json
|
7
|
+
from pathlib import Path
|
8
|
+
from typing import Optional, Any, Dict
|
9
|
+
import typer
|
10
|
+
|
11
|
+
class Config:
|
12
|
+
"""Singleton configuration class for Janito."""
|
13
|
+
_instance = None
|
14
|
+
|
15
|
+
def __new__(cls):
|
16
|
+
if cls._instance is None:
|
17
|
+
cls._instance = super(Config, cls).__new__(cls)
|
18
|
+
cls._instance._workspace_dir = os.getcwd()
|
19
|
+
cls._instance._verbose = False
|
20
|
+
cls._instance._history_context_count = 5
|
21
|
+
cls._instance._load_config()
|
22
|
+
return cls._instance
|
23
|
+
|
24
|
+
def _load_config(self) -> None:
|
25
|
+
"""Load configuration from file."""
|
26
|
+
config_path = Path(self._workspace_dir) / ".janito" / "config.json"
|
27
|
+
if config_path.exists():
|
28
|
+
try:
|
29
|
+
with open(config_path, "r", encoding="utf-8") as f:
|
30
|
+
config_data = json.load(f)
|
31
|
+
if "history_context_count" in config_data:
|
32
|
+
self._history_context_count = config_data["history_context_count"]
|
33
|
+
if "debug_mode" in config_data:
|
34
|
+
self._verbose = config_data["debug_mode"]
|
35
|
+
except Exception as e:
|
36
|
+
print(f"Warning: Failed to load configuration: {str(e)}")
|
37
|
+
|
38
|
+
def _save_config(self) -> None:
|
39
|
+
"""Save configuration to file."""
|
40
|
+
config_dir = Path(self._workspace_dir) / ".janito"
|
41
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
42
|
+
config_path = config_dir / "config.json"
|
43
|
+
|
44
|
+
config_data = {
|
45
|
+
"history_context_count": self._history_context_count,
|
46
|
+
"verbose": self._verbose
|
47
|
+
}
|
48
|
+
|
49
|
+
try:
|
50
|
+
with open(config_path, "w", encoding="utf-8") as f:
|
51
|
+
json.dump(config_data, f, indent=2)
|
52
|
+
except Exception as e:
|
53
|
+
print(f"Warning: Failed to save configuration: {str(e)}")
|
54
|
+
|
55
|
+
@property
|
56
|
+
def workspace_dir(self) -> str:
|
57
|
+
"""Get the current workspace directory."""
|
58
|
+
return self._workspace_dir
|
59
|
+
|
60
|
+
@workspace_dir.setter
|
61
|
+
def workspace_dir(self, path: str) -> None:
|
62
|
+
"""Set the workspace directory."""
|
63
|
+
# Convert to absolute path if not already
|
64
|
+
if not os.path.isabs(path):
|
65
|
+
path = os.path.normpath(os.path.abspath(path))
|
66
|
+
else:
|
67
|
+
# Ensure Windows paths are properly formatted
|
68
|
+
path = os.path.normpath(path)
|
69
|
+
|
70
|
+
# Check if the directory exists
|
71
|
+
if not os.path.isdir(path):
|
72
|
+
create_dir = typer.confirm(f"Workspace directory does not exist: {path}\nDo you want to create it?")
|
73
|
+
if create_dir:
|
74
|
+
try:
|
75
|
+
os.makedirs(path, exist_ok=True)
|
76
|
+
print(f"Created workspace directory: {path}")
|
77
|
+
except Exception as e:
|
78
|
+
raise ValueError(f"Failed to create workspace directory: {str(e)}")
|
79
|
+
else:
|
80
|
+
raise ValueError(f"Workspace directory does not exist: {path}")
|
81
|
+
|
82
|
+
self._workspace_dir = path
|
83
|
+
|
84
|
+
@property
|
85
|
+
def verbose(self) -> bool:
|
86
|
+
"""Get the verbose mode status."""
|
87
|
+
return self._verbose
|
88
|
+
|
89
|
+
@verbose.setter
|
90
|
+
def verbose(self, value: bool) -> None:
|
91
|
+
"""Set the verbose mode status."""
|
92
|
+
self._verbose = value
|
93
|
+
|
94
|
+
# For backward compatibility
|
95
|
+
@property
|
96
|
+
def debug_mode(self) -> bool:
|
97
|
+
"""Get the debug mode status (alias for verbose)."""
|
98
|
+
return self._verbose
|
99
|
+
|
100
|
+
@debug_mode.setter
|
101
|
+
def debug_mode(self, value: bool) -> None:
|
102
|
+
"""Set the debug mode status (alias for verbose)."""
|
103
|
+
self._verbose = value
|
104
|
+
|
105
|
+
@property
|
106
|
+
def history_context_count(self) -> int:
|
107
|
+
"""Get the number of previous conversations to include in context."""
|
108
|
+
return self._history_context_count
|
109
|
+
|
110
|
+
@history_context_count.setter
|
111
|
+
def history_context_count(self, count: int) -> None:
|
112
|
+
"""Set the number of previous conversations to include in context."""
|
113
|
+
if count < 0:
|
114
|
+
raise ValueError("History context count must be a non-negative integer")
|
115
|
+
self._history_context_count = count
|
116
|
+
self._save_config()
|
117
|
+
|
118
|
+
# Convenience function to get the config instance
|
119
|
+
def get_config() -> Config:
|
120
|
+
"""Get the singleton Config instance."""
|
121
|
+
return Config()
|
@@ -1,4 +1,4 @@
|
|
1
|
-
You are an expert software engineer, working in a project.
|
2
|
-
When using str_replace_editor be aware that our files starting path is "." .
|
3
|
-
|
4
|
-
|
1
|
+
You are an expert software engineer, working in a project.
|
2
|
+
When using str_replace_editor be aware that our files starting path is "." .
|
3
|
+
|
4
|
+
If creating or editing files with a large number of lines, organize them into smaller files.
|