janito 0.11.0__py3-none-any.whl → 0.13.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- janito/__init__.py +1 -1
- janito/__main__.py +6 -204
- janito/callbacks.py +34 -132
- janito/cli/__init__.py +6 -0
- janito/cli/agent.py +400 -0
- janito/cli/app.py +94 -0
- janito/cli/commands.py +329 -0
- janito/cli/output.py +29 -0
- janito/cli/utils.py +22 -0
- janito/config.py +358 -121
- janito/data/instructions_template.txt +28 -0
- janito/token_report.py +154 -145
- janito/tools/__init__.py +38 -21
- janito/tools/bash/bash.py +84 -0
- janito/tools/bash/unix_persistent_bash.py +184 -0
- janito/tools/bash/win_persistent_bash.py +308 -0
- janito/tools/decorators.py +2 -13
- janito/tools/delete_file.py +27 -9
- janito/tools/fetch_webpage/__init__.py +34 -0
- janito/tools/fetch_webpage/chunking.py +76 -0
- janito/tools/fetch_webpage/core.py +155 -0
- janito/tools/fetch_webpage/extractors.py +276 -0
- janito/tools/fetch_webpage/news.py +137 -0
- janito/tools/fetch_webpage/utils.py +108 -0
- janito/tools/find_files.py +106 -44
- janito/tools/move_file.py +72 -0
- janito/tools/prompt_user.py +37 -6
- janito/tools/replace_file.py +31 -4
- janito/tools/rich_console.py +176 -0
- janito/tools/search_text.py +35 -22
- janito/tools/str_replace_editor/editor.py +7 -4
- janito/tools/str_replace_editor/handlers/__init__.py +16 -0
- janito/tools/str_replace_editor/handlers/create.py +60 -0
- janito/tools/str_replace_editor/handlers/insert.py +100 -0
- janito/tools/str_replace_editor/handlers/str_replace.py +94 -0
- janito/tools/str_replace_editor/handlers/undo.py +64 -0
- janito/tools/str_replace_editor/handlers/view.py +159 -0
- janito/tools/str_replace_editor/utils.py +0 -1
- janito/tools/usage_tracker.py +136 -0
- janito-0.13.0.dist-info/METADATA +300 -0
- janito-0.13.0.dist-info/RECORD +47 -0
- janito/chat_history.py +0 -117
- janito/data/instructions.txt +0 -4
- janito/tools/bash.py +0 -22
- janito/tools/str_replace_editor/handlers.py +0 -335
- janito-0.11.0.dist-info/METADATA +0 -86
- janito-0.11.0.dist-info/RECORD +0 -26
- {janito-0.11.0.dist-info → janito-0.13.0.dist-info}/WHEEL +0 -0
- {janito-0.11.0.dist-info → janito-0.13.0.dist-info}/entry_points.txt +0 -0
- {janito-0.11.0.dist-info → janito-0.13.0.dist-info}/licenses/LICENSE +0 -0
janito/config.py
CHANGED
@@ -1,121 +1,358 @@
|
|
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
|
-
|
9
|
-
import
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
""
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
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
|
+
import typer
|
9
|
+
from typing import Dict, Any, Optional
|
10
|
+
|
11
|
+
# Predefined parameter profiles
|
12
|
+
PROFILES = {
|
13
|
+
"precise": {
|
14
|
+
"temperature": 0.2,
|
15
|
+
"top_p": 0.85,
|
16
|
+
"top_k": 20,
|
17
|
+
"description": "Factual answers, documentation, structured data, avoiding hallucinations"
|
18
|
+
},
|
19
|
+
"balanced": {
|
20
|
+
"temperature": 0.5,
|
21
|
+
"top_p": 0.9,
|
22
|
+
"top_k": 40,
|
23
|
+
"description": "Professional writing, summarization, everyday tasks with moderate creativity"
|
24
|
+
},
|
25
|
+
"conversational": {
|
26
|
+
"temperature": 0.7,
|
27
|
+
"top_p": 0.9,
|
28
|
+
"top_k": 45,
|
29
|
+
"description": "Natural dialogue, educational content, support conversations"
|
30
|
+
},
|
31
|
+
"creative": {
|
32
|
+
"temperature": 0.9,
|
33
|
+
"top_p": 0.95,
|
34
|
+
"top_k": 70,
|
35
|
+
"description": "Storytelling, brainstorming, marketing copy, poetry"
|
36
|
+
},
|
37
|
+
"technical": {
|
38
|
+
"temperature": 0.3,
|
39
|
+
"top_p": 0.95,
|
40
|
+
"top_k": 15,
|
41
|
+
"description": "Code generation, debugging, decision analysis, technical problem-solving"
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
class Config:
|
46
|
+
"""Singleton configuration class for Janito."""
|
47
|
+
_instance = None
|
48
|
+
|
49
|
+
def __new__(cls):
|
50
|
+
if cls._instance is None:
|
51
|
+
cls._instance = super(Config, cls).__new__(cls)
|
52
|
+
cls._instance._workspace_dir = os.getcwd()
|
53
|
+
cls._instance._verbose = False
|
54
|
+
# Chat history context feature has been removed
|
55
|
+
cls._instance._ask_mode = False
|
56
|
+
cls._instance._trust_mode = False # New trust mode setting
|
57
|
+
# Set technical profile as default
|
58
|
+
profile_data = PROFILES["technical"]
|
59
|
+
cls._instance._temperature = profile_data["temperature"]
|
60
|
+
cls._instance._profile = "technical"
|
61
|
+
cls._instance._role = "software engineer"
|
62
|
+
cls._instance._gitbash_path = None # Default to None for auto-detection
|
63
|
+
cls._instance._load_config()
|
64
|
+
return cls._instance
|
65
|
+
|
66
|
+
def _load_config(self) -> None:
|
67
|
+
"""Load configuration from file."""
|
68
|
+
config_path = Path(self._workspace_dir) / ".janito" / "config.json"
|
69
|
+
if config_path.exists():
|
70
|
+
try:
|
71
|
+
with open(config_path, "r", encoding="utf-8") as f:
|
72
|
+
config_data = json.load(f)
|
73
|
+
# Chat history context feature has been removed
|
74
|
+
if "debug_mode" in config_data:
|
75
|
+
self._verbose = config_data["debug_mode"]
|
76
|
+
if "ask_mode" in config_data:
|
77
|
+
self._ask_mode = config_data["ask_mode"]
|
78
|
+
if "trust_mode" in config_data:
|
79
|
+
self._trust_mode = config_data["trust_mode"]
|
80
|
+
if "temperature" in config_data:
|
81
|
+
self._temperature = config_data["temperature"]
|
82
|
+
if "profile" in config_data:
|
83
|
+
self._profile = config_data["profile"]
|
84
|
+
if "role" in config_data:
|
85
|
+
self._role = config_data["role"]
|
86
|
+
if "gitbash_path" in config_data:
|
87
|
+
self._gitbash_path = config_data["gitbash_path"]
|
88
|
+
except Exception as e:
|
89
|
+
print(f"Warning: Failed to load configuration: {str(e)}")
|
90
|
+
|
91
|
+
def _save_config(self) -> None:
|
92
|
+
"""Save configuration to file."""
|
93
|
+
config_dir = Path(self._workspace_dir) / ".janito"
|
94
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
95
|
+
config_path = config_dir / "config.json"
|
96
|
+
|
97
|
+
config_data = {
|
98
|
+
# Chat history context feature has been removed
|
99
|
+
"verbose": self._verbose,
|
100
|
+
"ask_mode": self._ask_mode,
|
101
|
+
# trust_mode is not saved as it's a per-session setting
|
102
|
+
"temperature": self._temperature,
|
103
|
+
"role": self._role
|
104
|
+
}
|
105
|
+
|
106
|
+
# Save profile name if one is set
|
107
|
+
if self._profile:
|
108
|
+
config_data["profile"] = self._profile
|
109
|
+
|
110
|
+
# Save GitBash path if one is set
|
111
|
+
if self._gitbash_path:
|
112
|
+
config_data["gitbash_path"] = self._gitbash_path
|
113
|
+
|
114
|
+
try:
|
115
|
+
with open(config_path, "w", encoding="utf-8") as f:
|
116
|
+
json.dump(config_data, f, indent=2)
|
117
|
+
except Exception as e:
|
118
|
+
print(f"Warning: Failed to save configuration: {str(e)}")
|
119
|
+
|
120
|
+
def set_profile(self, profile_name: str) -> None:
|
121
|
+
"""Set parameter values based on a predefined profile.
|
122
|
+
|
123
|
+
Args:
|
124
|
+
profile_name: Name of the profile to use (precise, balanced, conversational, creative, technical)
|
125
|
+
|
126
|
+
Raises:
|
127
|
+
ValueError: If the profile name is not recognized
|
128
|
+
"""
|
129
|
+
profile_name = profile_name.lower()
|
130
|
+
if profile_name not in PROFILES:
|
131
|
+
valid_profiles = ", ".join(PROFILES.keys())
|
132
|
+
raise ValueError(f"Unknown profile: {profile_name}. Valid profiles are: {valid_profiles}")
|
133
|
+
|
134
|
+
profile = PROFILES[profile_name]
|
135
|
+
self._temperature = profile["temperature"]
|
136
|
+
self._profile = profile_name
|
137
|
+
self._save_config()
|
138
|
+
|
139
|
+
@property
|
140
|
+
def profile(self) -> Optional[str]:
|
141
|
+
"""Get the current profile name."""
|
142
|
+
return self._profile
|
143
|
+
|
144
|
+
@staticmethod
|
145
|
+
def get_available_profiles() -> Dict[str, Dict[str, Any]]:
|
146
|
+
"""Get all available predefined profiles."""
|
147
|
+
return PROFILES
|
148
|
+
|
149
|
+
@staticmethod
|
150
|
+
def set_api_key(api_key: str) -> None:
|
151
|
+
"""Set the API key in the global configuration file.
|
152
|
+
|
153
|
+
Args:
|
154
|
+
api_key: The Anthropic API key to store
|
155
|
+
|
156
|
+
Returns:
|
157
|
+
None
|
158
|
+
"""
|
159
|
+
# Create .janito directory in user's home directory if it doesn't exist
|
160
|
+
home_dir = Path.home()
|
161
|
+
config_dir = home_dir / ".janito"
|
162
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
163
|
+
|
164
|
+
# Create or update the config.json file
|
165
|
+
config_path = config_dir / "config.json"
|
166
|
+
|
167
|
+
# Load existing config if it exists
|
168
|
+
config_data = {}
|
169
|
+
if config_path.exists():
|
170
|
+
try:
|
171
|
+
with open(config_path, "r", encoding="utf-8") as f:
|
172
|
+
config_data = json.load(f)
|
173
|
+
except Exception as e:
|
174
|
+
print(f"Warning: Failed to load global configuration: {str(e)}")
|
175
|
+
|
176
|
+
# Update the API key
|
177
|
+
config_data["api_key"] = api_key
|
178
|
+
|
179
|
+
# Save the updated config
|
180
|
+
try:
|
181
|
+
with open(config_path, "w", encoding="utf-8") as f:
|
182
|
+
json.dump(config_data, f, indent=2)
|
183
|
+
print(f"API key saved to {config_path}")
|
184
|
+
except Exception as e:
|
185
|
+
raise ValueError(f"Failed to save API key: {str(e)}")
|
186
|
+
|
187
|
+
@staticmethod
|
188
|
+
def get_api_key() -> Optional[str]:
|
189
|
+
"""Get the API key from the global configuration file.
|
190
|
+
|
191
|
+
Returns:
|
192
|
+
The API key if found, None otherwise
|
193
|
+
"""
|
194
|
+
# Look for config.json in user's home directory
|
195
|
+
home_dir = Path.home()
|
196
|
+
config_path = home_dir / ".janito" / "config.json"
|
197
|
+
|
198
|
+
if config_path.exists():
|
199
|
+
try:
|
200
|
+
with open(config_path, "r", encoding="utf-8") as f:
|
201
|
+
config_data = json.load(f)
|
202
|
+
return config_data.get("api_key")
|
203
|
+
except Exception:
|
204
|
+
# Silently fail and return None
|
205
|
+
pass
|
206
|
+
|
207
|
+
return None
|
208
|
+
|
209
|
+
@property
|
210
|
+
def workspace_dir(self) -> str:
|
211
|
+
"""Get the current workspace directory."""
|
212
|
+
return self._workspace_dir
|
213
|
+
|
214
|
+
@workspace_dir.setter
|
215
|
+
def workspace_dir(self, path: str) -> None:
|
216
|
+
"""Set the workspace directory."""
|
217
|
+
# Convert to absolute path if not already
|
218
|
+
if not os.path.isabs(path):
|
219
|
+
path = os.path.normpath(os.path.abspath(path))
|
220
|
+
else:
|
221
|
+
# Ensure Windows paths are properly formatted
|
222
|
+
path = os.path.normpath(path)
|
223
|
+
|
224
|
+
# Check if the directory exists
|
225
|
+
if not os.path.isdir(path):
|
226
|
+
create_dir = typer.confirm(f"Workspace directory does not exist: {path}\nDo you want to create it?")
|
227
|
+
if create_dir:
|
228
|
+
try:
|
229
|
+
os.makedirs(path, exist_ok=True)
|
230
|
+
print(f"Created workspace directory: {path}")
|
231
|
+
except Exception as e:
|
232
|
+
raise ValueError(f"Failed to create workspace directory: {str(e)}") from e
|
233
|
+
else:
|
234
|
+
raise ValueError(f"Workspace directory does not exist: {path}")
|
235
|
+
|
236
|
+
self._workspace_dir = path
|
237
|
+
|
238
|
+
@property
|
239
|
+
def verbose(self) -> bool:
|
240
|
+
"""Get the verbose mode status."""
|
241
|
+
return self._verbose
|
242
|
+
|
243
|
+
@verbose.setter
|
244
|
+
def verbose(self, value: bool) -> None:
|
245
|
+
"""Set the verbose mode status."""
|
246
|
+
self._verbose = value
|
247
|
+
|
248
|
+
# For backward compatibility
|
249
|
+
@property
|
250
|
+
def debug_mode(self) -> bool:
|
251
|
+
"""Get the debug mode status (alias for verbose)."""
|
252
|
+
return self._verbose
|
253
|
+
|
254
|
+
@debug_mode.setter
|
255
|
+
def debug_mode(self, value: bool) -> None:
|
256
|
+
"""Set the debug mode status (alias for verbose)."""
|
257
|
+
self._verbose = value
|
258
|
+
|
259
|
+
# Chat history context feature has been removed
|
260
|
+
|
261
|
+
@property
|
262
|
+
def ask_mode(self) -> bool:
|
263
|
+
"""Get the ask mode status."""
|
264
|
+
return self._ask_mode
|
265
|
+
|
266
|
+
@ask_mode.setter
|
267
|
+
def ask_mode(self, value: bool) -> None:
|
268
|
+
"""Set the ask mode status."""
|
269
|
+
self._ask_mode = value
|
270
|
+
self._save_config()
|
271
|
+
|
272
|
+
@property
|
273
|
+
def trust_mode(self) -> bool:
|
274
|
+
"""Get the trust mode status."""
|
275
|
+
return self._trust_mode
|
276
|
+
|
277
|
+
@trust_mode.setter
|
278
|
+
def trust_mode(self, value: bool) -> None:
|
279
|
+
"""Set the trust mode status.
|
280
|
+
|
281
|
+
Note: This setting is not persisted to config file
|
282
|
+
as it's meant to be a per-session setting.
|
283
|
+
"""
|
284
|
+
self._trust_mode = value
|
285
|
+
# Don't save to config file - this is a per-session setting
|
286
|
+
|
287
|
+
@property
|
288
|
+
def temperature(self) -> float:
|
289
|
+
"""Get the temperature value for model generation."""
|
290
|
+
return self._temperature
|
291
|
+
|
292
|
+
@temperature.setter
|
293
|
+
def temperature(self, value: float) -> None:
|
294
|
+
"""Set the temperature value for model generation."""
|
295
|
+
if value < 0.0 or value > 1.0:
|
296
|
+
raise ValueError("Temperature must be between 0.0 and 1.0")
|
297
|
+
self._temperature = value
|
298
|
+
self._save_config()
|
299
|
+
|
300
|
+
# top_k and top_p are now only accessible through profiles
|
301
|
+
|
302
|
+
@property
|
303
|
+
def role(self) -> str:
|
304
|
+
"""Get the role for the assistant."""
|
305
|
+
return self._role
|
306
|
+
|
307
|
+
@role.setter
|
308
|
+
def role(self, value: str) -> None:
|
309
|
+
"""Set the role for the assistant."""
|
310
|
+
self._role = value
|
311
|
+
self._save_config()
|
312
|
+
|
313
|
+
@property
|
314
|
+
def gitbash_path(self) -> Optional[str]:
|
315
|
+
"""Get the path to the GitBash executable."""
|
316
|
+
return self._gitbash_path
|
317
|
+
|
318
|
+
@gitbash_path.setter
|
319
|
+
def gitbash_path(self, value: Optional[str]) -> None:
|
320
|
+
"""Set the path to the GitBash executable.
|
321
|
+
|
322
|
+
Args:
|
323
|
+
value: Path to the GitBash executable, or None to use auto-detection
|
324
|
+
"""
|
325
|
+
# If a path is provided, verify it exists
|
326
|
+
if value is not None and not os.path.exists(value):
|
327
|
+
raise ValueError(f"GitBash executable not found at: {value}")
|
328
|
+
|
329
|
+
self._gitbash_path = value
|
330
|
+
self._save_config()
|
331
|
+
|
332
|
+
def reset_config(self) -> bool:
|
333
|
+
"""Reset configuration by removing the config file.
|
334
|
+
|
335
|
+
Returns:
|
336
|
+
bool: True if the config file was removed, False if it didn't exist
|
337
|
+
"""
|
338
|
+
config_path = Path(self._workspace_dir) / ".janito" / "config.json"
|
339
|
+
if config_path.exists():
|
340
|
+
config_path.unlink()
|
341
|
+
# Reset instance variables to defaults
|
342
|
+
self._verbose = False
|
343
|
+
# Chat history context feature has been removed
|
344
|
+
self._ask_mode = False
|
345
|
+
self._trust_mode = False
|
346
|
+
# Set technical profile as default
|
347
|
+
profile_data = PROFILES["technical"]
|
348
|
+
self._temperature = profile_data["temperature"]
|
349
|
+
self._profile = "technical"
|
350
|
+
self._role = "software engineer"
|
351
|
+
self._gitbash_path = None # Reset to auto-detection
|
352
|
+
return True
|
353
|
+
return False
|
354
|
+
|
355
|
+
# Convenience function to get the config instance
|
356
|
+
def get_config() -> Config:
|
357
|
+
"""Get the singleton Config instance."""
|
358
|
+
return Config()
|
@@ -0,0 +1,28 @@
|
|
1
|
+
You are a {{ role }}, using the name Janito .
|
2
|
+
You will be assisting an user using a computer system on a {{ platform }} platform.
|
3
|
+
You can find more about the current project using the tools in the workspace directory.
|
4
|
+
If the question is related to the project, use the tools using the relative path "." .
|
5
|
+
|
6
|
+
If creating or editing files with a large number of lines, organize them into smaller files.
|
7
|
+
If creating or editing files in an existing directory check surrounding files for the used patterns.
|
8
|
+
|
9
|
+
# Structure Discovery (./docs/STRUCTURE.md)
|
10
|
+
Always start exploring the project by viewing for the file ./docs/STRUCTURE.md.
|
11
|
+
Do not track files or directories wich are in .gitignore in the structure.
|
12
|
+
At the end of responding to the user, update the structure file based on the files and directories you have interacted with,
|
13
|
+
be precise focusing on the most important files and directories, avoid adding extra information like architecture or design patterns.
|
14
|
+
|
15
|
+
# Tools
|
16
|
+
The bash tool does not support commands which will require user input.
|
17
|
+
Prefer the str_replace_editor tool to view directories and file contents.
|
18
|
+
|
19
|
+
</IMPORTANT>
|
20
|
+
Call the user_prompt tool when:
|
21
|
+
- There are multiple options to apply a certain change
|
22
|
+
- The next operation risk is moderated or high
|
23
|
+
- The implementation plan is complex, requiring a review
|
24
|
+
Proceed according to the user answer.
|
25
|
+
<IMPORTANT/>
|
26
|
+
|
27
|
+
When changing code in Python files, be mindful about the need to review the imports specially when new type hints are used (eg. Optional, Tuple, List, Dict, etc).
|
28
|
+
After performing changes to a project in interfaces which are exposed to the user, provide a short summary on how to verify the changes. eg. "run cmd xpto"
|