janito 0.12.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/cli/agent.py +135 -22
- janito/cli/app.py +10 -2
- janito/cli/commands.py +2 -2
- janito/config.py +20 -0
- janito/data/instructions_template.txt +5 -4
- janito/tools/bash/bash.py +3 -1
- janito/tools/bash/unix_persistent_bash.py +183 -181
- janito/tools/bash/win_persistent_bash.py +4 -2
- janito/tools/rich_console.py +46 -9
- janito/tools/search_text.py +4 -3
- janito/tools/str_replace_editor/handlers/str_replace.py +3 -1
- janito/tools/str_replace_editor/handlers/view.py +14 -8
- {janito-0.12.0.dist-info → janito-0.13.0.dist-info}/METADATA +101 -4
- {janito-0.12.0.dist-info → janito-0.13.0.dist-info}/RECORD +19 -19
- {janito-0.12.0.dist-info → janito-0.13.0.dist-info}/WHEEL +0 -0
- {janito-0.12.0.dist-info → janito-0.13.0.dist-info}/entry_points.txt +0 -0
- {janito-0.12.0.dist-info → janito-0.13.0.dist-info}/licenses/LICENSE +0 -0
janito/__init__.py
CHANGED
janito/cli/agent.py
CHANGED
@@ -7,6 +7,8 @@ import json
|
|
7
7
|
import anthropic
|
8
8
|
import claudine
|
9
9
|
import typer
|
10
|
+
import datetime
|
11
|
+
from typing import Optional
|
10
12
|
from rich.console import Console
|
11
13
|
from pathlib import Path
|
12
14
|
from jinja2 import Template
|
@@ -19,6 +21,7 @@ from janito.tools import str_replace_editor
|
|
19
21
|
from janito.tools.bash.bash import bash_tool
|
20
22
|
from janito.cli.output import display_generation_params
|
21
23
|
|
24
|
+
|
22
25
|
console = Console()
|
23
26
|
|
24
27
|
def get_api_key() -> str:
|
@@ -154,12 +157,25 @@ def initialize_agent(temperature: float, verbose: bool) -> claudine.Agent:
|
|
154
157
|
|
155
158
|
return agent
|
156
159
|
|
160
|
+
def generate_message_id():
|
161
|
+
"""
|
162
|
+
Generate a message ID based on timestamp with seconds granularity
|
163
|
+
|
164
|
+
Returns:
|
165
|
+
str: A timestamp-based message ID
|
166
|
+
"""
|
167
|
+
timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
|
168
|
+
return timestamp
|
169
|
+
|
157
170
|
def save_messages(agent):
|
158
171
|
"""
|
159
|
-
Save agent messages to .janito/
|
172
|
+
Save agent messages to .janito/last_messages/{message_id}.json
|
160
173
|
|
161
174
|
Args:
|
162
175
|
agent: The claudine agent instance
|
176
|
+
|
177
|
+
Returns:
|
178
|
+
str: The message ID used for saving
|
163
179
|
"""
|
164
180
|
try:
|
165
181
|
# Get the workspace directory
|
@@ -169,49 +185,115 @@ def save_messages(agent):
|
|
169
185
|
janito_dir = workspace_dir / ".janito"
|
170
186
|
janito_dir.mkdir(exist_ok=True)
|
171
187
|
|
188
|
+
# Create last_messages directory if it doesn't exist
|
189
|
+
messages_dir = janito_dir / "last_messages"
|
190
|
+
messages_dir.mkdir(exist_ok=True)
|
191
|
+
|
192
|
+
# Generate a unique message ID
|
193
|
+
message_id = generate_message_id()
|
194
|
+
|
172
195
|
# Get messages from the agent
|
173
196
|
messages = agent.get_messages()
|
174
197
|
|
198
|
+
# Create a message object with metadata
|
199
|
+
message_object = {
|
200
|
+
"id": message_id,
|
201
|
+
"timestamp": datetime.datetime.now().isoformat(),
|
202
|
+
"messages": messages
|
203
|
+
}
|
204
|
+
|
175
205
|
# Save messages to file
|
176
|
-
|
177
|
-
|
206
|
+
message_file = messages_dir / f"{message_id}.json"
|
207
|
+
with open(message_file, "w", encoding="utf-8") as f:
|
208
|
+
json.dump(message_object, f, ensure_ascii=False, indent=2)
|
209
|
+
|
210
|
+
# No longer saving to last_message.json for backward compatibility
|
178
211
|
|
179
212
|
if get_config().verbose:
|
180
|
-
console.print(f"[bold green]✅ Conversation saved to {
|
213
|
+
console.print(f"[bold green]✅ Conversation saved to {message_file}[/bold green]")
|
214
|
+
|
215
|
+
return message_id
|
181
216
|
except Exception as e:
|
182
217
|
console.print(f"[bold red]❌ Error saving conversation:[/bold red] {str(e)}")
|
218
|
+
return None
|
183
219
|
|
184
|
-
def load_messages():
|
220
|
+
def load_messages(message_id=None):
|
185
221
|
"""
|
186
|
-
Load messages from .janito/
|
222
|
+
Load messages from .janito/last_messages/{message_id}.json or the latest message file
|
187
223
|
|
224
|
+
Args:
|
225
|
+
message_id: Optional message ID to load specific conversation
|
226
|
+
|
188
227
|
Returns:
|
189
228
|
List of message dictionaries or None if file doesn't exist
|
190
229
|
"""
|
191
230
|
try:
|
192
231
|
# Get the workspace directory
|
193
232
|
workspace_dir = Path(get_config().workspace_dir)
|
233
|
+
janito_dir = workspace_dir / ".janito"
|
234
|
+
messages_dir = janito_dir / "last_messages"
|
235
|
+
|
236
|
+
# If message_id is provided, try to load that specific conversation
|
237
|
+
if message_id:
|
238
|
+
# Check if the message ID is a file name or just the ID
|
239
|
+
if message_id.endswith('.json'):
|
240
|
+
message_file = messages_dir / message_id
|
241
|
+
else:
|
242
|
+
message_file = messages_dir / f"{message_id}.json"
|
243
|
+
|
244
|
+
if not message_file.exists():
|
245
|
+
console.print(f"[bold yellow]⚠️ No conversation found with ID {message_id}[/bold yellow]")
|
246
|
+
return None
|
247
|
+
|
248
|
+
# Load messages from file
|
249
|
+
with open(message_file, "r", encoding="utf-8") as f:
|
250
|
+
message_object = json.load(f)
|
251
|
+
|
252
|
+
# Extract messages from the message object
|
253
|
+
if isinstance(message_object, dict) and "messages" in message_object:
|
254
|
+
messages = message_object["messages"]
|
255
|
+
else:
|
256
|
+
# Handle legacy format
|
257
|
+
messages = message_object
|
258
|
+
|
259
|
+
if get_config().verbose:
|
260
|
+
console.print(f"[bold green]✅ Loaded conversation from {message_file}[/bold green]")
|
261
|
+
console.print(f"[dim]📝 Conversation has {len(messages)} messages[/dim]")
|
262
|
+
|
263
|
+
return messages
|
194
264
|
|
195
|
-
#
|
196
|
-
|
197
|
-
if not messages_file.exists():
|
265
|
+
# If no message_id is provided, try to load the latest message from last_messages directory
|
266
|
+
if not messages_dir.exists() or not any(messages_dir.iterdir()):
|
198
267
|
console.print("[bold yellow]⚠️ No previous conversation found[/bold yellow]")
|
199
268
|
return None
|
200
269
|
|
201
|
-
#
|
202
|
-
|
203
|
-
|
270
|
+
# Find the latest message file (based on filename which is a timestamp)
|
271
|
+
latest_file = max(
|
272
|
+
[f for f in messages_dir.iterdir() if f.is_file() and f.suffix == '.json'],
|
273
|
+
key=lambda x: x.stem
|
274
|
+
)
|
275
|
+
|
276
|
+
# Load messages from the latest file
|
277
|
+
with open(latest_file, "r", encoding="utf-8") as f:
|
278
|
+
message_object = json.load(f)
|
279
|
+
|
280
|
+
# Extract messages from the message object
|
281
|
+
if isinstance(message_object, dict) and "messages" in message_object:
|
282
|
+
messages = message_object["messages"]
|
283
|
+
else:
|
284
|
+
# Handle legacy format
|
285
|
+
messages = message_object
|
204
286
|
|
205
287
|
if get_config().verbose:
|
206
|
-
console.print(f"[bold green]✅ Loaded
|
288
|
+
console.print(f"[bold green]✅ Loaded latest conversation from {latest_file}[/bold green]")
|
207
289
|
console.print(f"[dim]📝 Conversation has {len(messages)} messages[/dim]")
|
208
290
|
|
209
291
|
return messages
|
210
292
|
except Exception as e:
|
211
|
-
console.print(f"[bold red]❌ Error loading
|
293
|
+
console.print(f"[bold red]❌ Error loading conversation:[/bold red] {str(e)}")
|
212
294
|
return None
|
213
295
|
|
214
|
-
def handle_query(query: str, temperature: float, verbose: bool, show_tokens: bool, continue_conversation:
|
296
|
+
def handle_query(query: str, temperature: float, verbose: bool, show_tokens: bool, continue_conversation: Optional[str] = None) -> None:
|
215
297
|
"""
|
216
298
|
Handle a query by initializing the agent and sending the query.
|
217
299
|
|
@@ -220,24 +302,29 @@ def handle_query(query: str, temperature: float, verbose: bool, show_tokens: boo
|
|
220
302
|
temperature: Temperature value for model generation
|
221
303
|
verbose: Whether to enable verbose mode
|
222
304
|
show_tokens: Whether to show detailed token usage
|
223
|
-
continue_conversation:
|
305
|
+
continue_conversation: Optional message ID to continue a specific conversation
|
224
306
|
"""
|
225
307
|
# Initialize the agent
|
226
308
|
agent = initialize_agent(temperature, verbose)
|
227
309
|
|
228
310
|
# Load previous messages if continuing conversation
|
229
|
-
if continue_conversation:
|
230
|
-
|
311
|
+
if continue_conversation is not None:
|
312
|
+
# If continue_conversation is an empty string (from flag with no value), use default behavior
|
313
|
+
message_id = None if continue_conversation == "" else continue_conversation
|
314
|
+
messages = load_messages(message_id)
|
231
315
|
if messages:
|
232
316
|
agent.set_messages(messages)
|
233
|
-
|
317
|
+
if message_id:
|
318
|
+
console.print(f"[bold blue]🔄 Continuing conversation with ID: {message_id}[/bold blue]")
|
319
|
+
else:
|
320
|
+
console.print("[bold blue]🔄 Continuing previous conversation[/bold blue]")
|
234
321
|
|
235
322
|
# Send the query to the agent
|
236
323
|
try:
|
237
324
|
agent.query(query)
|
238
325
|
|
239
|
-
# Save messages after successful query
|
240
|
-
save_messages(agent)
|
326
|
+
# Save messages after successful query and get the message ID
|
327
|
+
message_id = save_messages(agent)
|
241
328
|
|
242
329
|
# Print token usage report
|
243
330
|
if show_tokens:
|
@@ -249,13 +336,26 @@ def handle_query(query: str, temperature: float, verbose: bool, show_tokens: boo
|
|
249
336
|
# Print tool usage statistics
|
250
337
|
from janito.tools import print_usage_stats
|
251
338
|
print_usage_stats()
|
339
|
+
|
340
|
+
# Show message about continuing this conversation
|
341
|
+
if message_id:
|
342
|
+
script_name = "janito"
|
343
|
+
try:
|
344
|
+
# Check if we're running from the entry point script or as a module
|
345
|
+
if sys.argv[0].endswith(('janito', 'janito.exe')):
|
346
|
+
console.print(f"[bold green]💬 This conversation can be continued with:[/bold green] {script_name} --continue {message_id} <request>")
|
347
|
+
else:
|
348
|
+
console.print(f"[bold green]💬 This conversation can be continued with:[/bold green] python -m janito --continue {message_id} <request>")
|
349
|
+
except:
|
350
|
+
# Fallback message
|
351
|
+
console.print(f"[bold green]💬 This conversation can be continued with:[/bold green] --continue {message_id} <request>")
|
252
352
|
|
253
353
|
except KeyboardInterrupt:
|
254
354
|
# Handle Ctrl+C by printing token and tool usage information
|
255
355
|
console.print("\n[bold yellow]⚠️ Query interrupted by user (Ctrl+C)[/bold yellow]")
|
256
356
|
|
257
357
|
# Save messages even if interrupted
|
258
|
-
save_messages(agent)
|
358
|
+
message_id = save_messages(agent)
|
259
359
|
|
260
360
|
# Print token usage report (even if interrupted)
|
261
361
|
try:
|
@@ -268,6 +368,19 @@ def handle_query(query: str, temperature: float, verbose: bool, show_tokens: boo
|
|
268
368
|
# Print tool usage statistics
|
269
369
|
from janito.tools import print_usage_stats
|
270
370
|
print_usage_stats()
|
371
|
+
|
372
|
+
# Show message about continuing this conversation
|
373
|
+
if message_id:
|
374
|
+
script_name = "janito"
|
375
|
+
try:
|
376
|
+
# Check if we're running from the entry point script or as a module
|
377
|
+
if sys.argv[0].endswith(('janito', 'janito.exe')):
|
378
|
+
console.print(f"[bold green]💬 This conversation can be continued with:[/bold green] {script_name} --continue {message_id} <request>")
|
379
|
+
else:
|
380
|
+
console.print(f"[bold green]💬 This conversation can be continued with:[/bold green] python -m janito --continue {message_id} <request>")
|
381
|
+
except:
|
382
|
+
# Fallback message
|
383
|
+
console.print(f"[bold green]💬 This conversation can be continued with:[/bold green] --continue {message_id} <request>")
|
271
384
|
except Exception as e:
|
272
385
|
console.print(f"[bold red]❌ Error generating usage report:[/bold red] {str(e)}")
|
273
386
|
if verbose:
|
janito/cli/app.py
CHANGED
@@ -19,18 +19,19 @@ console = Console()
|
|
19
19
|
def main(ctx: typer.Context,
|
20
20
|
query: Optional[str] = typer.Argument(None, help="Query to send to the claudine agent"),
|
21
21
|
verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose mode with detailed output"),
|
22
|
-
show_tokens: bool = typer.Option(False, "--show-tokens", "
|
22
|
+
show_tokens: bool = typer.Option(False, "--show-tokens", "--tokens", help="Show detailed token usage and pricing information"),
|
23
23
|
workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Set the workspace directory"),
|
24
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
25
|
show_config: bool = typer.Option(False, "--show-config", help="Show current configuration"),
|
26
26
|
reset_config: bool = typer.Option(False, "--reset-config", help="Reset configuration by removing the config file"),
|
27
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
28
|
ask: bool = typer.Option(False, "--ask", help="Enable ask mode which disables tools that perform changes"),
|
29
|
+
trust: bool = typer.Option(False, "--trust", "-t", help="Enable trust mode which suppresses tool outputs for a more concise execution (per-session setting)"),
|
29
30
|
temperature: float = typer.Option(0.0, "--temperature", help="Set the temperature for model generation (0.0 to 1.0)"),
|
30
31
|
profile: Optional[str] = typer.Option(None, "--profile", help="Use a predefined parameter profile (precise, balanced, conversational, creative, technical)"),
|
31
32
|
role: Optional[str] = typer.Option(None, "--role", help="Set the assistant's role (default: 'software engineer')"),
|
32
33
|
version: bool = typer.Option(False, "--version", help="Show the version and exit"),
|
33
|
-
continue_conversation:
|
34
|
+
continue_conversation: Optional[str] = typer.Option(None, "--continue", "-c", help="Continue a previous conversation, optionally with a specific message ID")):
|
34
35
|
"""
|
35
36
|
Janito CLI tool. If a query is provided without a command, it will be sent to the claudine agent.
|
36
37
|
"""
|
@@ -40,9 +41,16 @@ def main(ctx: typer.Context,
|
|
40
41
|
# Set ask mode in config
|
41
42
|
get_config().ask_mode = ask
|
42
43
|
|
44
|
+
# Set trust mode in config
|
45
|
+
get_config().trust_mode = trust
|
46
|
+
|
43
47
|
# Show a message if ask mode is enabled
|
44
48
|
if ask:
|
45
49
|
console.print("[bold yellow]⚠️ Ask Mode enabled:[/bold yellow] 🔒 Tools that perform changes are disabled")
|
50
|
+
|
51
|
+
# Show a message if trust mode is enabled
|
52
|
+
if trust:
|
53
|
+
console.print("[bold blue]⚡ Trust Mode enabled:[/bold blue] Tool outputs are suppressed for concise execution (per-session setting)")
|
46
54
|
|
47
55
|
# Show version and exit if requested
|
48
56
|
if version:
|
janito/cli/commands.py
CHANGED
@@ -285,7 +285,7 @@ def handle_config_commands(
|
|
285
285
|
set_api_key: Optional[str],
|
286
286
|
config_str: Optional[str],
|
287
287
|
query: Optional[str],
|
288
|
-
continue_conversation:
|
288
|
+
continue_conversation: Optional[str] = None
|
289
289
|
) -> bool:
|
290
290
|
"""
|
291
291
|
Handle all configuration-related commands.
|
@@ -300,7 +300,7 @@ def handle_config_commands(
|
|
300
300
|
set_api_key: API key
|
301
301
|
config_str: Configuration string in format 'key=value'
|
302
302
|
query: Query string
|
303
|
-
continue_conversation:
|
303
|
+
continue_conversation: Optional message ID to continue a specific conversation
|
304
304
|
|
305
305
|
Returns:
|
306
306
|
bool: True if the program should exit after these operations
|
janito/config.py
CHANGED
@@ -53,6 +53,7 @@ class Config:
|
|
53
53
|
cls._instance._verbose = False
|
54
54
|
# Chat history context feature has been removed
|
55
55
|
cls._instance._ask_mode = False
|
56
|
+
cls._instance._trust_mode = False # New trust mode setting
|
56
57
|
# Set technical profile as default
|
57
58
|
profile_data = PROFILES["technical"]
|
58
59
|
cls._instance._temperature = profile_data["temperature"]
|
@@ -74,6 +75,8 @@ class Config:
|
|
74
75
|
self._verbose = config_data["debug_mode"]
|
75
76
|
if "ask_mode" in config_data:
|
76
77
|
self._ask_mode = config_data["ask_mode"]
|
78
|
+
if "trust_mode" in config_data:
|
79
|
+
self._trust_mode = config_data["trust_mode"]
|
77
80
|
if "temperature" in config_data:
|
78
81
|
self._temperature = config_data["temperature"]
|
79
82
|
if "profile" in config_data:
|
@@ -95,6 +98,7 @@ class Config:
|
|
95
98
|
# Chat history context feature has been removed
|
96
99
|
"verbose": self._verbose,
|
97
100
|
"ask_mode": self._ask_mode,
|
101
|
+
# trust_mode is not saved as it's a per-session setting
|
98
102
|
"temperature": self._temperature,
|
99
103
|
"role": self._role
|
100
104
|
}
|
@@ -265,6 +269,21 @@ class Config:
|
|
265
269
|
self._ask_mode = value
|
266
270
|
self._save_config()
|
267
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
|
+
|
268
287
|
@property
|
269
288
|
def temperature(self) -> float:
|
270
289
|
"""Get the temperature value for model generation."""
|
@@ -323,6 +342,7 @@ class Config:
|
|
323
342
|
self._verbose = False
|
324
343
|
# Chat history context feature has been removed
|
325
344
|
self._ask_mode = False
|
345
|
+
self._trust_mode = False
|
326
346
|
# Set technical profile as default
|
327
347
|
profile_data = PROFILES["technical"]
|
328
348
|
self._temperature = profile_data["temperature"]
|
@@ -6,22 +6,23 @@ If the question is related to the project, use the tools using the relative path
|
|
6
6
|
If creating or editing files with a large number of lines, organize them into smaller files.
|
7
7
|
If creating or editing files in an existing directory check surrounding files for the used patterns.
|
8
8
|
|
9
|
-
# Structure Discovery (
|
10
|
-
Always start exploring the project by viewing for the file
|
9
|
+
# Structure Discovery (./docs/STRUCTURE.md)
|
10
|
+
Always start exploring the project by viewing for the file ./docs/STRUCTURE.md.
|
11
11
|
Do not track files or directories wich are in .gitignore in the structure.
|
12
12
|
At the end of responding to the user, update the structure file based on the files and directories you have interacted with,
|
13
13
|
be precise focusing on the most important files and directories, avoid adding extra information like architecture or design patterns.
|
14
14
|
|
15
|
-
|
16
15
|
# Tools
|
17
16
|
The bash tool does not support commands which will require user input.
|
18
17
|
Prefer the str_replace_editor tool to view directories and file contents.
|
19
18
|
|
20
19
|
</IMPORTANT>
|
21
|
-
Call the tool
|
20
|
+
Call the user_prompt tool when:
|
22
21
|
- There are multiple options to apply a certain change
|
23
22
|
- The next operation risk is moderated or high
|
24
23
|
- The implementation plan is complex, requiring a review
|
25
24
|
Proceed according to the user answer.
|
26
25
|
<IMPORTANT/>
|
27
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"
|
janito/tools/bash/bash.py
CHANGED
@@ -34,7 +34,9 @@ def bash_tool(command: str, restart: Optional[bool] = False) -> Tuple[str, bool]
|
|
34
34
|
# Import console for printing output in real-time
|
35
35
|
from janito.tools.rich_console import console, print_info
|
36
36
|
|
37
|
-
|
37
|
+
# Only print command if not in trust mode
|
38
|
+
if not get_config().trust_mode:
|
39
|
+
print_info(f"{command}", "Bash Run")
|
38
40
|
global _bash_session
|
39
41
|
|
40
42
|
# Check if in ask mode and if the command might modify files
|
@@ -1,182 +1,184 @@
|
|
1
|
-
import subprocess
|
2
|
-
import time
|
3
|
-
import uuid
|
4
|
-
|
5
|
-
class PersistentBash:
|
6
|
-
"""
|
7
|
-
A wrapper class that maintains a persistent Bash session.
|
8
|
-
Allows sending commands and collecting output without restarting Bash.
|
9
|
-
"""
|
10
|
-
|
11
|
-
def __init__(self, bash_path=None):
|
12
|
-
"""
|
13
|
-
Initialize a persistent Bash session.
|
14
|
-
|
15
|
-
Args:
|
16
|
-
bash_path (str, optional): Path to the Bash executable. If None, tries to detect automatically.
|
17
|
-
"""
|
18
|
-
self.process = None
|
19
|
-
self.bash_path = bash_path
|
20
|
-
|
21
|
-
# If bash_path is not provided, try to detect it
|
22
|
-
if self.bash_path is None:
|
23
|
-
# On Unix-like systems, bash is usually in the PATH
|
24
|
-
self.bash_path = "bash"
|
25
|
-
|
26
|
-
# Check if bash exists
|
27
|
-
try:
|
28
|
-
subprocess.run(["which", "bash"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
29
|
-
except subprocess.CalledProcessError as err:
|
30
|
-
raise FileNotFoundError("Could not find bash executable. Please specify the path manually.") from err
|
31
|
-
|
32
|
-
# Start the bash process
|
33
|
-
self.start_process()
|
34
|
-
|
35
|
-
def start_process(self):
|
36
|
-
"""Start the Bash process."""
|
37
|
-
# Create a subprocess with pipe for stdin, stdout, and stderr
|
38
|
-
bash_args = [self.bash_path]
|
39
|
-
|
40
|
-
self.process = subprocess.Popen(
|
41
|
-
bash_args,
|
42
|
-
stdin=subprocess.PIPE,
|
43
|
-
stdout=subprocess.PIPE,
|
44
|
-
stderr=subprocess.STDOUT, # Redirect stderr to stdout
|
45
|
-
text=True, # Use text mode for input/output
|
46
|
-
bufsize=0, # Unbuffered
|
47
|
-
universal_newlines=True, # Universal newlines mode
|
48
|
-
)
|
49
|
-
|
50
|
-
# Set up a more reliable environment
|
51
|
-
setup_commands = [
|
52
|
-
"export PS1='$ '", # Simple prompt to avoid parsing issues
|
53
|
-
"export TERM=dumb", # Disable color codes and other terminal features
|
54
|
-
"set +o history", # Disable history
|
55
|
-
"shopt -s expand_aliases", # Enable alias expansion
|
56
|
-
]
|
57
|
-
|
58
|
-
# Send setup commands
|
59
|
-
for cmd in setup_commands:
|
60
|
-
self._send_command(cmd)
|
61
|
-
|
62
|
-
# Clear initial output with a marker
|
63
|
-
marker = f"INIT_COMPLETE_{uuid.uuid4().hex}"
|
64
|
-
self._send_command(f"echo {marker}")
|
65
|
-
|
66
|
-
while True:
|
67
|
-
line = self.process.stdout.readline().strip()
|
68
|
-
if marker in line:
|
69
|
-
break
|
70
|
-
|
71
|
-
def _send_command(self, command):
|
72
|
-
"""Send a command to the Bash process without reading the output."""
|
73
|
-
if self.process is None or self.process.poll() is not None:
|
74
|
-
self.start_process()
|
75
|
-
|
76
|
-
self.process.stdin.write(command + "\n")
|
77
|
-
self.process.stdin.flush()
|
78
|
-
|
79
|
-
def execute(self, command, timeout=None):
|
80
|
-
"""
|
81
|
-
Execute a command in the Bash session and return the output.
|
82
|
-
|
83
|
-
Args:
|
84
|
-
command (str): The command to execute.
|
85
|
-
timeout (int, optional): Timeout in seconds. If None, no timeout is applied.
|
86
|
-
|
87
|
-
Returns:
|
88
|
-
str: The command output.
|
89
|
-
"""
|
90
|
-
from janito.tools.rich_console import console
|
91
|
-
|
92
|
-
if self.process is None or self.process.poll() is not None:
|
93
|
-
# Process has terminated, restart it
|
94
|
-
self.start_process()
|
95
|
-
|
96
|
-
# Create a unique marker to identify the end of output
|
97
|
-
end_marker = f"END_OF_COMMAND_{uuid.uuid4().hex}"
|
98
|
-
|
99
|
-
# Construct the wrapped command with echo markers
|
100
|
-
# Only use timeout when explicitly requested
|
101
|
-
if timeout is not None and timeout > 0:
|
102
|
-
# Check if timeout command is available
|
103
|
-
is_timeout_available = False
|
104
|
-
try:
|
105
|
-
check_cmd = "command -v timeout > /dev/null 2>&1 && echo available || echo unavailable"
|
106
|
-
self._send_command(check_cmd)
|
107
|
-
for _ in range(10): # Read up to 10 lines to find the result
|
108
|
-
line = self.process.stdout.readline().strip()
|
109
|
-
if "available" in line:
|
110
|
-
is_timeout_available = True
|
111
|
-
break
|
112
|
-
elif "unavailable" in line:
|
113
|
-
is_timeout_available = False
|
114
|
-
break
|
115
|
-
except:
|
116
|
-
is_timeout_available = False
|
117
|
-
|
118
|
-
if is_timeout_available:
|
119
|
-
# For timeout to work with shell syntax, we need to use bash -c
|
120
|
-
escaped_command = command.replace('"', '\\"')
|
121
|
-
wrapped_command = f"timeout {timeout}s bash -c \"{escaped_command}\" 2>&1; echo '{end_marker}'"
|
122
|
-
else:
|
123
|
-
wrapped_command = f"{command} 2>&1; echo '{end_marker}'"
|
124
|
-
else:
|
125
|
-
wrapped_command = f"{command} 2>&1; echo '{end_marker}'"
|
126
|
-
|
127
|
-
# Send the command
|
128
|
-
self._send_command(wrapped_command)
|
129
|
-
|
130
|
-
# Collect output until the end marker is found
|
131
|
-
output_lines = []
|
132
|
-
start_time = time.time()
|
133
|
-
max_wait = timeout if timeout is not None else 3600 # Default to 1 hour if no timeout
|
134
|
-
|
135
|
-
while time.time() - start_time < max_wait + 5: # Add buffer time
|
136
|
-
try:
|
137
|
-
line = self.process.stdout.readline().rstrip('\r\n')
|
138
|
-
if end_marker in line:
|
139
|
-
break
|
140
|
-
|
141
|
-
# Print the output to the console in real-time
|
142
|
-
if line:
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
1
|
+
import subprocess
|
2
|
+
import time
|
3
|
+
import uuid
|
4
|
+
|
5
|
+
class PersistentBash:
|
6
|
+
"""
|
7
|
+
A wrapper class that maintains a persistent Bash session.
|
8
|
+
Allows sending commands and collecting output without restarting Bash.
|
9
|
+
"""
|
10
|
+
|
11
|
+
def __init__(self, bash_path=None):
|
12
|
+
"""
|
13
|
+
Initialize a persistent Bash session.
|
14
|
+
|
15
|
+
Args:
|
16
|
+
bash_path (str, optional): Path to the Bash executable. If None, tries to detect automatically.
|
17
|
+
"""
|
18
|
+
self.process = None
|
19
|
+
self.bash_path = bash_path
|
20
|
+
|
21
|
+
# If bash_path is not provided, try to detect it
|
22
|
+
if self.bash_path is None:
|
23
|
+
# On Unix-like systems, bash is usually in the PATH
|
24
|
+
self.bash_path = "bash"
|
25
|
+
|
26
|
+
# Check if bash exists
|
27
|
+
try:
|
28
|
+
subprocess.run(["which", "bash"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
29
|
+
except subprocess.CalledProcessError as err:
|
30
|
+
raise FileNotFoundError("Could not find bash executable. Please specify the path manually.") from err
|
31
|
+
|
32
|
+
# Start the bash process
|
33
|
+
self.start_process()
|
34
|
+
|
35
|
+
def start_process(self):
|
36
|
+
"""Start the Bash process."""
|
37
|
+
# Create a subprocess with pipe for stdin, stdout, and stderr
|
38
|
+
bash_args = [self.bash_path]
|
39
|
+
|
40
|
+
self.process = subprocess.Popen(
|
41
|
+
bash_args,
|
42
|
+
stdin=subprocess.PIPE,
|
43
|
+
stdout=subprocess.PIPE,
|
44
|
+
stderr=subprocess.STDOUT, # Redirect stderr to stdout
|
45
|
+
text=True, # Use text mode for input/output
|
46
|
+
bufsize=0, # Unbuffered
|
47
|
+
universal_newlines=True, # Universal newlines mode
|
48
|
+
)
|
49
|
+
|
50
|
+
# Set up a more reliable environment
|
51
|
+
setup_commands = [
|
52
|
+
"export PS1='$ '", # Simple prompt to avoid parsing issues
|
53
|
+
"export TERM=dumb", # Disable color codes and other terminal features
|
54
|
+
"set +o history", # Disable history
|
55
|
+
"shopt -s expand_aliases", # Enable alias expansion
|
56
|
+
]
|
57
|
+
|
58
|
+
# Send setup commands
|
59
|
+
for cmd in setup_commands:
|
60
|
+
self._send_command(cmd)
|
61
|
+
|
62
|
+
# Clear initial output with a marker
|
63
|
+
marker = f"INIT_COMPLETE_{uuid.uuid4().hex}"
|
64
|
+
self._send_command(f"echo {marker}")
|
65
|
+
|
66
|
+
while True:
|
67
|
+
line = self.process.stdout.readline().strip()
|
68
|
+
if marker in line:
|
69
|
+
break
|
70
|
+
|
71
|
+
def _send_command(self, command):
|
72
|
+
"""Send a command to the Bash process without reading the output."""
|
73
|
+
if self.process is None or self.process.poll() is not None:
|
74
|
+
self.start_process()
|
75
|
+
|
76
|
+
self.process.stdin.write(command + "\n")
|
77
|
+
self.process.stdin.flush()
|
78
|
+
|
79
|
+
def execute(self, command, timeout=None):
|
80
|
+
"""
|
81
|
+
Execute a command in the Bash session and return the output.
|
82
|
+
|
83
|
+
Args:
|
84
|
+
command (str): The command to execute.
|
85
|
+
timeout (int, optional): Timeout in seconds. If None, no timeout is applied.
|
86
|
+
|
87
|
+
Returns:
|
88
|
+
str: The command output.
|
89
|
+
"""
|
90
|
+
from janito.tools.rich_console import console
|
91
|
+
|
92
|
+
if self.process is None or self.process.poll() is not None:
|
93
|
+
# Process has terminated, restart it
|
94
|
+
self.start_process()
|
95
|
+
|
96
|
+
# Create a unique marker to identify the end of output
|
97
|
+
end_marker = f"END_OF_COMMAND_{uuid.uuid4().hex}"
|
98
|
+
|
99
|
+
# Construct the wrapped command with echo markers
|
100
|
+
# Only use timeout when explicitly requested
|
101
|
+
if timeout is not None and timeout > 0:
|
102
|
+
# Check if timeout command is available
|
103
|
+
is_timeout_available = False
|
104
|
+
try:
|
105
|
+
check_cmd = "command -v timeout > /dev/null 2>&1 && echo available || echo unavailable"
|
106
|
+
self._send_command(check_cmd)
|
107
|
+
for _ in range(10): # Read up to 10 lines to find the result
|
108
|
+
line = self.process.stdout.readline().strip()
|
109
|
+
if "available" in line:
|
110
|
+
is_timeout_available = True
|
111
|
+
break
|
112
|
+
elif "unavailable" in line:
|
113
|
+
is_timeout_available = False
|
114
|
+
break
|
115
|
+
except:
|
116
|
+
is_timeout_available = False
|
117
|
+
|
118
|
+
if is_timeout_available:
|
119
|
+
# For timeout to work with shell syntax, we need to use bash -c
|
120
|
+
escaped_command = command.replace('"', '\\"')
|
121
|
+
wrapped_command = f"timeout {timeout}s bash -c \"{escaped_command}\" 2>&1; echo '{end_marker}'"
|
122
|
+
else:
|
123
|
+
wrapped_command = f"{command} 2>&1; echo '{end_marker}'"
|
124
|
+
else:
|
125
|
+
wrapped_command = f"{command} 2>&1; echo '{end_marker}'"
|
126
|
+
|
127
|
+
# Send the command
|
128
|
+
self._send_command(wrapped_command)
|
129
|
+
|
130
|
+
# Collect output until the end marker is found
|
131
|
+
output_lines = []
|
132
|
+
start_time = time.time()
|
133
|
+
max_wait = timeout if timeout is not None else 3600 # Default to 1 hour if no timeout
|
134
|
+
|
135
|
+
while time.time() - start_time < max_wait + 5: # Add buffer time
|
136
|
+
try:
|
137
|
+
line = self.process.stdout.readline().rstrip('\r\n')
|
138
|
+
if end_marker in line:
|
139
|
+
break
|
140
|
+
|
141
|
+
# Print the output to the console in real-time if not in trust mode
|
142
|
+
if line:
|
143
|
+
from janito.config import get_config
|
144
|
+
if not get_config().trust_mode:
|
145
|
+
console.print(line)
|
146
|
+
|
147
|
+
output_lines.append(line)
|
148
|
+
except Exception as e:
|
149
|
+
error_msg = f"[Error reading output: {str(e)}]"
|
150
|
+
console.print(error_msg, style="red")
|
151
|
+
output_lines.append(error_msg)
|
152
|
+
continue
|
153
|
+
|
154
|
+
# Check for timeout
|
155
|
+
if time.time() - start_time >= max_wait + 5:
|
156
|
+
timeout_msg = f"Error: Command timed out after {max_wait} seconds"
|
157
|
+
console.print(timeout_msg, style="red bold")
|
158
|
+
output_lines.append(timeout_msg)
|
159
|
+
|
160
|
+
# Try to reset the bash session after a timeout
|
161
|
+
self.close()
|
162
|
+
self.start_process()
|
163
|
+
|
164
|
+
return "\n".join(output_lines)
|
165
|
+
|
166
|
+
def close(self):
|
167
|
+
"""Close the Bash session."""
|
168
|
+
if self.process and self.process.poll() is None:
|
169
|
+
try:
|
170
|
+
self._send_command("exit")
|
171
|
+
self.process.wait(timeout=2)
|
172
|
+
except:
|
173
|
+
pass
|
174
|
+
finally:
|
175
|
+
try:
|
176
|
+
self.process.terminate()
|
177
|
+
except:
|
178
|
+
pass
|
179
|
+
|
180
|
+
self.process = None
|
181
|
+
|
182
|
+
def __del__(self):
|
183
|
+
"""Destructor to ensure the process is closed."""
|
182
184
|
self.close()
|
@@ -222,9 +222,11 @@ class PersistentBash:
|
|
222
222
|
if end_marker in line:
|
223
223
|
break
|
224
224
|
|
225
|
-
# Print the output to the console in real-time
|
225
|
+
# Print the output to the console in real-time if not in trust mode
|
226
226
|
if line:
|
227
|
-
|
227
|
+
from janito.config import get_config
|
228
|
+
if not get_config().trust_mode:
|
229
|
+
console.print(line)
|
228
230
|
|
229
231
|
output_lines.append(line)
|
230
232
|
except UnicodeDecodeError as e:
|
janito/tools/rich_console.py
CHANGED
@@ -4,6 +4,7 @@ Utility module for rich console printing in tools.
|
|
4
4
|
from rich.console import Console
|
5
5
|
from rich.text import Text
|
6
6
|
from typing import Optional
|
7
|
+
from janito.config import get_config
|
7
8
|
|
8
9
|
# Create a shared console instance
|
9
10
|
console = Console()
|
@@ -16,6 +17,9 @@ def print_info(message: str, title: Optional[str] = None):
|
|
16
17
|
message: The message to print
|
17
18
|
title: Optional title for the panel
|
18
19
|
"""
|
20
|
+
# Skip printing if trust mode is enabled
|
21
|
+
if get_config().trust_mode:
|
22
|
+
return
|
19
23
|
# Map titles to specific icons
|
20
24
|
icon_map = {
|
21
25
|
# File operations
|
@@ -82,20 +86,22 @@ def print_info(message: str, title: Optional[str] = None):
|
|
82
86
|
elif "Undoing last edit" in title:
|
83
87
|
icon = "↩️" # Undo icon
|
84
88
|
|
89
|
+
# Add indentation to all tool messages
|
90
|
+
indent = " "
|
85
91
|
text = Text(message)
|
86
92
|
if title:
|
87
93
|
# Special case for Bash Run commands
|
88
94
|
if title == "Bash Run":
|
89
95
|
console.print("\n" + "-"*50)
|
90
|
-
console.print(f"{icon} {title}", style="bold white on blue")
|
96
|
+
console.print(f"{indent}{icon} {title}", style="bold white on blue")
|
91
97
|
console.print("-"*50)
|
92
|
-
console.print(f"$ {text}", style="white on dark_blue")
|
98
|
+
console.print(f"{indent}$ {text}", style="white on dark_blue")
|
93
99
|
# Make sure we're not returning anything
|
94
100
|
return
|
95
101
|
else:
|
96
|
-
console.print(f"{icon} {message}", style="blue", end="")
|
102
|
+
console.print(f"{indent}{icon} {message}", style="blue", end="")
|
97
103
|
else:
|
98
|
-
console.print(f"{icon} {text}", style="blue", end="")
|
104
|
+
console.print(f"{indent}{icon} {text}", style="blue", end="")
|
99
105
|
|
100
106
|
def print_success(message: str, title: Optional[str] = None):
|
101
107
|
"""
|
@@ -105,6 +111,9 @@ def print_success(message: str, title: Optional[str] = None):
|
|
105
111
|
message: The message to print
|
106
112
|
title: Optional title for the panel
|
107
113
|
"""
|
114
|
+
# Skip printing if trust mode is enabled
|
115
|
+
if get_config().trust_mode:
|
116
|
+
return
|
108
117
|
text = Text(message)
|
109
118
|
if title:
|
110
119
|
console.print(f" ✅ {message}", style="green")
|
@@ -114,26 +123,54 @@ def print_success(message: str, title: Optional[str] = None):
|
|
114
123
|
def print_error(message: str, title: Optional[str] = None):
|
115
124
|
"""
|
116
125
|
Print an error message with rich formatting.
|
126
|
+
In trust mode, error messages are suppressed.
|
117
127
|
|
118
128
|
Args:
|
119
129
|
message: The message to print
|
120
130
|
title: Optional title for the panel
|
121
131
|
"""
|
132
|
+
# Skip printing if trust mode is enabled
|
133
|
+
if get_config().trust_mode:
|
134
|
+
return
|
135
|
+
|
122
136
|
text = Text(message)
|
123
|
-
|
124
|
-
|
137
|
+
|
138
|
+
# Check if message starts with question mark emoji (❓)
|
139
|
+
# If it does, use warning styling (yellow) instead of error styling (red)
|
140
|
+
starts_with_question_mark = message.startswith("❓")
|
141
|
+
|
142
|
+
if starts_with_question_mark:
|
143
|
+
# Use warning styling for question mark emoji errors
|
144
|
+
# For question mark emoji errors, don't include the title (like "Error")
|
145
|
+
# Just print the message with the emoji
|
125
146
|
if title == "File View":
|
126
|
-
console.print(f"\n
|
147
|
+
console.print(f"\n {message}", style="yellow")
|
127
148
|
else:
|
128
|
-
console.print(f"
|
149
|
+
console.print(f"{message}", style="yellow")
|
129
150
|
else:
|
130
|
-
|
151
|
+
# Regular error styling
|
152
|
+
if title:
|
153
|
+
# Special case for File View - print without header
|
154
|
+
if title == "File View":
|
155
|
+
console.print(f"\n ❌ {message}", style="red")
|
156
|
+
# Special case for Search Error
|
157
|
+
elif title == "Search Error":
|
158
|
+
console.print(f"❌ {message}", style="red")
|
159
|
+
else:
|
160
|
+
console.print(f"❌ {title} {text}", style="red")
|
161
|
+
else:
|
162
|
+
console.print(f"\n❌ {text}", style="red")
|
131
163
|
|
132
164
|
def print_warning(message: str):
|
133
165
|
"""
|
134
166
|
Print a warning message with rich formatting.
|
167
|
+
In trust mode, warning messages are suppressed.
|
135
168
|
|
136
169
|
Args:
|
137
170
|
message: The message to print
|
138
171
|
"""
|
172
|
+
# Skip printing if trust mode is enabled
|
173
|
+
if get_config().trust_mode:
|
174
|
+
return
|
175
|
+
|
139
176
|
console.print(f"⚠️ {message}", style="yellow")
|
janito/tools/search_text.py
CHANGED
@@ -37,9 +37,10 @@ def search_text(text_pattern: str, file_pattern: str = "*", root_dir: str = ".",
|
|
37
37
|
# Compile the regex pattern for better performance
|
38
38
|
try:
|
39
39
|
regex = re.compile(text_pattern)
|
40
|
-
except re.error
|
41
|
-
|
42
|
-
|
40
|
+
except re.error:
|
41
|
+
# Simplified error message without the specific regex error details
|
42
|
+
error_msg = f"Error: Invalid regex pattern '{text_pattern}'"
|
43
|
+
print_error(error_msg, "Search Error")
|
43
44
|
return error_msg, True
|
44
45
|
|
45
46
|
matching_files = []
|
@@ -60,7 +60,9 @@ def handle_str_replace(args: Dict[str, Any]) -> Tuple[str, bool]:
|
|
60
60
|
|
61
61
|
# Check if old_str exists in the content (must match EXACTLY)
|
62
62
|
if old_str not in content:
|
63
|
-
|
63
|
+
# Only print error if not in trust mode
|
64
|
+
if not get_config().trust_mode:
|
65
|
+
print_error("No exact match", "?")
|
64
66
|
return ("Error: No exact match found for replacement. Please check your text and ensure whitespaces match exactly.", True)
|
65
67
|
|
66
68
|
# Count occurrences to check for multiple matches
|
@@ -55,8 +55,8 @@ def handle_view(args: Dict[str, Any]) -> Tuple[str, bool]:
|
|
55
55
|
file_path = pathlib.Path(path)
|
56
56
|
|
57
57
|
if not file_path.exists():
|
58
|
-
print_error(f"
|
59
|
-
return (f"
|
58
|
+
print_error(f"❓ (not found)", "Error")
|
59
|
+
return (f"❓ (not found)", True)
|
60
60
|
|
61
61
|
# If the path is a directory, list non-hidden files and directories up to 2 levels deep
|
62
62
|
if file_path.is_dir():
|
@@ -94,9 +94,12 @@ def handle_view(args: Dict[str, Any]) -> Tuple[str, bool]:
|
|
94
94
|
# Directory listings should not be truncated
|
95
95
|
file_dir_count = len(result)
|
96
96
|
output = "\n".join(result)
|
97
|
-
|
98
|
-
|
99
|
-
|
97
|
+
|
98
|
+
# Only print count if not in trust mode
|
99
|
+
if not get_config().trust_mode:
|
100
|
+
console.print(f"Found ", style="default", end="")
|
101
|
+
console.print(f"{file_dir_count}", style="cyan", end="")
|
102
|
+
console.print(" files and directories")
|
100
103
|
return (output, False)
|
101
104
|
except Exception as e:
|
102
105
|
return (f"Error listing directory {path}: {str(e)}", True)
|
@@ -144,9 +147,12 @@ def handle_view(args: Dict[str, Any]) -> Tuple[str, bool]:
|
|
144
147
|
return (truncated_content + "\n<response clipped>", False)
|
145
148
|
|
146
149
|
content_to_print = "".join(numbered_content)
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
+
|
151
|
+
# Only print line count if not in trust mode
|
152
|
+
if not get_config().trust_mode:
|
153
|
+
console.print("(", style="default", end="")
|
154
|
+
console.print(f"{len(numbered_content)}", style="cyan", end="")
|
155
|
+
console.print(")")
|
150
156
|
# Return the content as a string without any Rich objects
|
151
157
|
return (content_to_print, False)
|
152
158
|
except Exception as e:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: janito
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.13.0
|
4
4
|
Summary: Janito CLI tool
|
5
5
|
Project-URL: Homepage, https://github.com/joaompinto/janito
|
6
6
|
Author-email: João Pinto <lamego.pinto@gmail.com>
|
@@ -35,6 +35,8 @@ Janito is a powerful AI-assisted command-line interface (CLI) tool built with Py
|
|
35
35
|
- 🌐 Web page fetching with content extraction capabilities
|
36
36
|
- 🔄 Parameter profiles for optimizing Claude's behavior for different tasks
|
37
37
|
- 📋 Line delta tracking to monitor net changes in files
|
38
|
+
- 💬 Conversation history with ability to resume previous conversations
|
39
|
+
- 🔇 Trust mode for concise output without tool details
|
38
40
|
|
39
41
|
## 🛠️ System Requirements
|
40
42
|
|
@@ -108,14 +110,24 @@ janito --show-tokens "Explain what is in the project"
|
|
108
110
|
# Use a specific parameter profile for creative tasks
|
109
111
|
janito --profile creative "Write a fun description for our project"
|
110
112
|
|
111
|
-
#
|
112
|
-
janito --
|
113
|
+
# Use trust mode for concise output without tool details
|
114
|
+
janito --trust "Optimize the HTML code"
|
115
|
+
# Or use the short alias
|
116
|
+
janito -t "Optimize the HTML code"
|
117
|
+
|
118
|
+
# Continue the most recent conversation
|
119
|
+
janito --continue "Please add one more line"
|
120
|
+
|
121
|
+
# Continue a specific conversation using its message ID
|
122
|
+
# (Janito displays the message ID after each conversation)
|
123
|
+
janito --continue abc123def "Let's refine that code"
|
113
124
|
|
114
125
|
# Show current configuration and available profiles
|
115
126
|
janito --show-config
|
116
127
|
|
117
128
|
# You can press Ctrl+C at any time to interrupt a query
|
118
129
|
# Janito will still display token and tool usage information
|
130
|
+
# Even interrupted conversations can be continued with --continue
|
119
131
|
```
|
120
132
|
|
121
133
|
## 🔧 Available Tools
|
@@ -134,7 +146,7 @@ Janito comes with several built-in tools:
|
|
134
146
|
Janito includes a comprehensive token usage tracking system that helps you monitor API costs:
|
135
147
|
|
136
148
|
- **Basic tracking**: By default, Janito displays a summary of token usage and cost after each query
|
137
|
-
- **Detailed reporting**: Use the `--show-tokens`
|
149
|
+
- **Detailed reporting**: Use the `--show-tokens` flag to see detailed breakdowns including:
|
138
150
|
- Input and output token counts
|
139
151
|
- Per-tool token usage statistics
|
140
152
|
- Precise cost calculations
|
@@ -147,6 +159,11 @@ janito --show-tokens "Write a Python function to sort a list"
|
|
147
159
|
|
148
160
|
# Basic usage (shows simplified token usage summary)
|
149
161
|
janito "Explain Docker containers"
|
162
|
+
|
163
|
+
# Use trust mode for concise output without tool details
|
164
|
+
janito --trust "Create a simple Python script"
|
165
|
+
# Or use the short alias
|
166
|
+
janito -t "Create a simple Python script"
|
150
167
|
```
|
151
168
|
|
152
169
|
The usage tracker automatically calculates cache savings, showing you how much you're saving by reusing previous responses.
|
@@ -169,6 +186,64 @@ janito --profile creative "Write a poem about coding"
|
|
169
186
|
janito --show-config
|
170
187
|
```
|
171
188
|
|
189
|
+
## 🔇 Trust Mode
|
190
|
+
|
191
|
+
Janito offers a trust mode that suppresses tool outputs for a more concise execution experience:
|
192
|
+
|
193
|
+
### How It Works
|
194
|
+
|
195
|
+
- When enabled with `--trust` or `-t`, Janito suppresses informational and success messages from tools
|
196
|
+
- Only essential output and error messages are displayed
|
197
|
+
- The final result from Claude is still shown in full
|
198
|
+
- Trust mode is a per-session setting and not saved to your configuration
|
199
|
+
|
200
|
+
### Using Trust Mode
|
201
|
+
|
202
|
+
```bash
|
203
|
+
# Enable trust mode with the full flag
|
204
|
+
janito --trust "Create a Python script that reads a CSV file"
|
205
|
+
|
206
|
+
# Or use the short alias
|
207
|
+
janito -t "Create a Python script that reads a CSV file"
|
208
|
+
```
|
209
|
+
|
210
|
+
This feature is particularly useful for:
|
211
|
+
- Experienced users who don't need to see every step of the process
|
212
|
+
- Batch processing or scripting where concise output is preferred
|
213
|
+
- Focusing on results rather than the process
|
214
|
+
- Creating cleaner output for documentation or sharing
|
215
|
+
|
216
|
+
## 💬 Conversation History
|
217
|
+
|
218
|
+
Janito automatically saves your conversation history, allowing you to continue previous discussions:
|
219
|
+
|
220
|
+
### How It Works
|
221
|
+
|
222
|
+
- Each conversation is saved with a unique message ID in `.janito/last_messages/`
|
223
|
+
- The most recent conversation is also saved as `.janito/last_message.json` for backward compatibility
|
224
|
+
- After each conversation, Janito displays the command to continue that specific conversation
|
225
|
+
|
226
|
+
### Using the Continue Feature
|
227
|
+
|
228
|
+
```bash
|
229
|
+
# Continue the most recent conversation
|
230
|
+
janito --continue "Add more details to your previous response"
|
231
|
+
|
232
|
+
# Continue a specific conversation using its ID
|
233
|
+
janito --continue abc123def "Let's modify that code you suggested"
|
234
|
+
```
|
235
|
+
|
236
|
+
The `--continue` flag (or `-c` for short) allows you to:
|
237
|
+
- Resume the most recent conversation when used without an ID
|
238
|
+
- Resume a specific conversation when provided with a message ID
|
239
|
+
- Maintain context across multiple interactions for complex tasks
|
240
|
+
|
241
|
+
This feature is particularly useful for:
|
242
|
+
- Multi-step development tasks
|
243
|
+
- Iterative code improvements
|
244
|
+
- Continuing discussions after system interruptions
|
245
|
+
- Maintaining context when working on complex problems
|
246
|
+
|
172
247
|
## ⚙️ Dependencies
|
173
248
|
|
174
249
|
Janito automatically installs the following dependencies:
|
@@ -177,6 +252,28 @@ Janito automatically installs the following dependencies:
|
|
177
252
|
- claudine - For Claude AI integration
|
178
253
|
- Additional packages for file handling and web content extraction
|
179
254
|
|
255
|
+
## 🛠️ Command-Line Options
|
256
|
+
|
257
|
+
Janito offers a variety of command-line options to customize its behavior:
|
258
|
+
|
259
|
+
```
|
260
|
+
--verbose, -v Enable verbose mode with detailed output
|
261
|
+
--show-tokens Show detailed token usage and pricing information
|
262
|
+
--workspace, -w TEXT Set the workspace directory
|
263
|
+
--set-config TEXT Configuration string in format 'key=value', e.g., 'temperature=0.7'
|
264
|
+
--show-config Show current configuration
|
265
|
+
--reset-config Reset configuration by removing the config file
|
266
|
+
--set-api-key TEXT Set the Anthropic API key globally in the user's home directory
|
267
|
+
--ask Enable ask mode which disables tools that perform changes
|
268
|
+
--trust, -t Enable trust mode which suppresses tool outputs for concise execution
|
269
|
+
--temperature FLOAT Set the temperature for model generation (0.0 to 1.0)
|
270
|
+
--profile TEXT Use a predefined parameter profile (precise, balanced, conversational, creative, technical)
|
271
|
+
--role TEXT Set the assistant's role (default: 'software engineer')
|
272
|
+
--version Show the version and exit
|
273
|
+
--continue, -c TEXT Continue a previous conversation, optionally with a specific message ID
|
274
|
+
--help Show the help message and exit
|
275
|
+
```
|
276
|
+
|
180
277
|
## 🔑 API Key Configuration
|
181
278
|
|
182
279
|
You can configure your Anthropic API key in several ways:
|
@@ -1,16 +1,16 @@
|
|
1
|
-
janito/__init__.py,sha256=
|
1
|
+
janito/__init__.py,sha256=5Cnu7sPJURDSY1cnxtGvzs0MpwHUsh5zYZRNQ2LkM8k,53
|
2
2
|
janito/__main__.py,sha256=Oy-Nc1tZkpyvTKuq1R8oHSuJTkvptN6H93kIHBu7DKY,107
|
3
3
|
janito/callbacks.py,sha256=E1FPXYHZUgiEGMabYuf999PSf_Su4ByHOWlc1-hMqWE,915
|
4
|
-
janito/config.py,sha256=
|
4
|
+
janito/config.py,sha256=MHfloii_OnOVcV4pbunfdnAv8uJzxu_ytDK34Rj8yZ8,13221
|
5
5
|
janito/test_file.py,sha256=c6GWGdTYG3z-Y5XBao9Tmhmq3G-v0L37OfwLgBo8zIU,126
|
6
6
|
janito/token_report.py,sha256=Mks7o2yTxPChgQyBJNoQ5eMmrhSgEM4LKCKi2tHJbVo,9580
|
7
7
|
janito/cli/__init__.py,sha256=dVi9l3E86YyukjxQ-XSUnMZkghnNasXex-X5XAOBiwk,85
|
8
|
-
janito/cli/agent.py,sha256=
|
9
|
-
janito/cli/app.py,sha256=
|
10
|
-
janito/cli/commands.py,sha256=
|
8
|
+
janito/cli/agent.py,sha256=iFwzbZupkpXQVO0YrQmCGKKQGcqCRdAY3JLg6tZaJiQ,15940
|
9
|
+
janito/cli/app.py,sha256=czgOEWCsWlKeLWMHxPv6fxOStoL2wcC8_QZs6mMtTp8,4643
|
10
|
+
janito/cli/commands.py,sha256=pgassnKENr0DGcZT3tFXtz8MCVuEZwHgxiBktgAik6g,12682
|
11
11
|
janito/cli/output.py,sha256=mo3hUokhrD4SWexUjCbLGGQeCDUf0369DA_i9BW7HjU,933
|
12
12
|
janito/cli/utils.py,sha256=gO4NtCNwtEzYDsQesrFlqB5FtYuw87yGwo4iG3nINgw,661
|
13
|
-
janito/data/instructions_template.txt,sha256=
|
13
|
+
janito/data/instructions_template.txt,sha256=FlyGpNjl2LRBjPWLTK40oKOSH6sveHp5ud35nf_lfs8,1716
|
14
14
|
janito/tools/__init__.py,sha256=hio75FRkxLSQB13We-SCCM7Qa9lMqWCfWhEonHlTr8M,1405
|
15
15
|
janito/tools/decorators.py,sha256=Tp48n5y4LKsjyV3HeOA9wk2dV413RrEG-23kRyQVlKs,2522
|
16
16
|
janito/tools/delete_file.py,sha256=UrZ5q59SIxWfuJcqgol6yPBqL-RhO9lFCF4MqAc6o00,2252
|
@@ -18,12 +18,12 @@ janito/tools/find_files.py,sha256=c_N9ETcRPprQeuZYanwFnl-9E05ZqUYhNVoCRS5uqQg,83
|
|
18
18
|
janito/tools/move_file.py,sha256=FCs1ghalfHlXmcbAA_IlLcUll9hTOU1MMFGrTWopXvM,2741
|
19
19
|
janito/tools/prompt_user.py,sha256=OnTiWVBCbL_2MYu7oThlKr8X_pnYdG-dzxXSOgJF41c,1942
|
20
20
|
janito/tools/replace_file.py,sha256=i4GoLtS14eKSU5lYI18mJ96S0_ekeHMwlQfazg-fxrM,2296
|
21
|
-
janito/tools/rich_console.py,sha256=
|
22
|
-
janito/tools/search_text.py,sha256=
|
21
|
+
janito/tools/rich_console.py,sha256=0zWYRF8qk4N-upuwswUSEfYFAfkEYDYeCgxst-czWtY,6044
|
22
|
+
janito/tools/search_text.py,sha256=RxbnNeZ3ErCvevwbl60fQAe55QYtU-D9n13iixeDcqQ,9822
|
23
23
|
janito/tools/usage_tracker.py,sha256=IE7GVBDYsX2EDLjsVaVqTeAnT2SAYfcOJvBaH__wHdU,4613
|
24
|
-
janito/tools/bash/bash.py,sha256=
|
25
|
-
janito/tools/bash/unix_persistent_bash.py,sha256=
|
26
|
-
janito/tools/bash/win_persistent_bash.py,sha256=
|
24
|
+
janito/tools/bash/bash.py,sha256=AFm0w_z-yyYRWxuR744OFpm5iCZaZpE-pWbnKbgajp4,3665
|
25
|
+
janito/tools/bash/unix_persistent_bash.py,sha256=I59PPQiXHscPJ6Y7ev_83dLFNFWq1hKwAK9kFXdnbBY,7185
|
26
|
+
janito/tools/bash/win_persistent_bash.py,sha256=96xm_yijjc6hBYfNluLahbvR2oUuHug_JkoMah7Hy38,12894
|
27
27
|
janito/tools/fetch_webpage/__init__.py,sha256=0RG0ev-yrfo8gPzt-36WMpVdY2qtMhk2Z-mVpq-7m1o,1076
|
28
28
|
janito/tools/fetch_webpage/chunking.py,sha256=mMtrZeirZ_GnKOOzeG8BijLVpSUI0-TA5Ioqi2HKb1A,2971
|
29
29
|
janito/tools/fetch_webpage/core.py,sha256=3XDvYnC_UbQUNumwWw32hfJhjJUEaPzzoF2yhxhwxos,7764
|
@@ -36,12 +36,12 @@ janito/tools/str_replace_editor/utils.py,sha256=akiPqCHjky_RwL9OitHJJ7uQ-3fNaA8w
|
|
36
36
|
janito/tools/str_replace_editor/handlers/__init__.py,sha256=RP6JCeDRIL4R-lTpGowIoOAi64gg6VxZvJGp8Q2UOVU,373
|
37
37
|
janito/tools/str_replace_editor/handlers/create.py,sha256=s8RQE04kDAL7OLZA8WxJkDqTmJlGmCNiit4tIHnmNMo,2470
|
38
38
|
janito/tools/str_replace_editor/handlers/insert.py,sha256=eKHodm2ozKUlRMxWMLAsu9ca6unUo1jfXWwHSld-pSU,4061
|
39
|
-
janito/tools/str_replace_editor/handlers/str_replace.py,sha256=
|
39
|
+
janito/tools/str_replace_editor/handlers/str_replace.py,sha256=RciLTlA7R2PGljeeyluLcBHUAje9c1OCxm-bFE7j6iY,4473
|
40
40
|
janito/tools/str_replace_editor/handlers/undo.py,sha256=3OIdAWkpXC2iDe94_sfx_WxEFh3a1cRzoP0NtPXq1Ks,2491
|
41
|
-
janito/tools/str_replace_editor/handlers/view.py,sha256=
|
42
|
-
janito/data/instructions_template.txt,sha256=
|
43
|
-
janito-0.
|
44
|
-
janito-0.
|
45
|
-
janito-0.
|
46
|
-
janito-0.
|
47
|
-
janito-0.
|
41
|
+
janito/tools/str_replace_editor/handlers/view.py,sha256=k8F-n64bomHmDjavr5OJKC4cHhfm4_1-aMIFZdMICQo,6809
|
42
|
+
janito/data/instructions_template.txt,sha256=FlyGpNjl2LRBjPWLTK40oKOSH6sveHp5ud35nf_lfs8,1716
|
43
|
+
janito-0.13.0.dist-info/METADATA,sha256=06Vh36jWdwTcsUOpIyRkHybxssdksSoWNvPVE41y1jM,10584
|
44
|
+
janito-0.13.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
45
|
+
janito-0.13.0.dist-info/entry_points.txt,sha256=JMbF_1jg-xQddidpAYkzjOKdw70fy_ymJfcmerY2wIY,47
|
46
|
+
janito-0.13.0.dist-info/licenses/LICENSE,sha256=6-H8LXExbBIAuT4cyiE-Qy8Bad1K4pagQRVTWr6wkhk,1096
|
47
|
+
janito-0.13.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|