janito 0.7.0__tar.gz → 0.9.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.7.0 → janito-0.9.0}/LICENSE +20 -20
- janito-0.9.0/MANIFEST.in +1 -0
- janito-0.9.0/PKG-INFO +9 -0
- janito-0.9.0/README.md +75 -0
- janito-0.9.0/janito/__init__.py +5 -0
- janito-0.9.0/janito/__main__.py +151 -0
- janito-0.9.0/janito/callbacks.py +130 -0
- janito-0.9.0/janito/cli.py +202 -0
- janito-0.9.0/janito/config.py +63 -0
- janito-0.9.0/janito/data/instructions.txt +6 -0
- janito-0.9.0/janito/test_file.py +4 -0
- janito-0.9.0/janito/token_report.py +73 -0
- janito-0.9.0/janito/tools/__init__.py +10 -0
- janito-0.9.0/janito/tools/decorators.py +84 -0
- janito-0.9.0/janito/tools/delete_file.py +44 -0
- janito-0.9.0/janito/tools/find_files.py +154 -0
- janito-0.9.0/janito/tools/search_text.py +197 -0
- janito-0.9.0/janito/tools/str_replace_editor/__init__.py +6 -0
- janito-0.9.0/janito/tools/str_replace_editor/editor.py +43 -0
- janito-0.9.0/janito/tools/str_replace_editor/handlers.py +338 -0
- janito-0.9.0/janito/tools/str_replace_editor/utils.py +88 -0
- janito-0.9.0/janito.egg-info/PKG-INFO +9 -0
- janito-0.9.0/janito.egg-info/SOURCES.txt +28 -0
- janito-0.9.0/janito.egg-info/dependency_links.txt +1 -0
- janito-0.9.0/janito.egg-info/entry_points.txt +2 -0
- janito-0.9.0/janito.egg-info/requires.txt +3 -0
- janito-0.9.0/janito.egg-info/top_level.txt +1 -0
- janito-0.9.0/pyproject.toml +20 -0
- janito-0.9.0/setup.cfg +4 -0
- janito-0.9.0/setup.py +7 -0
- janito-0.7.0/.gitignore +0 -88
- janito-0.7.0/PKG-INFO +0 -167
- janito-0.7.0/README.md +0 -142
- janito-0.7.0/janito/__init__.py +0 -2
- janito-0.7.0/janito/__main__.py +0 -142
- janito-0.7.0/janito/agents/__init__.py +0 -22
- janito-0.7.0/janito/agents/agent.py +0 -28
- janito-0.7.0/janito/agents/claudeai.py +0 -45
- janito-0.7.0/janito/agents/openai.py +0 -57
- janito-0.7.0/janito/agents/test.py +0 -34
- janito-0.7.0/janito/change/__init__.py +0 -32
- janito-0.7.0/janito/change/__main__.py +0 -0
- janito-0.7.0/janito/change/analysis/__init__.py +0 -23
- janito-0.7.0/janito/change/analysis/__main__.py +0 -7
- janito-0.7.0/janito/change/analysis/analyze.py +0 -62
- janito-0.7.0/janito/change/analysis/formatting.py +0 -78
- janito-0.7.0/janito/change/analysis/options.py +0 -81
- janito-0.7.0/janito/change/analysis/prompts.py +0 -90
- janito-0.7.0/janito/change/analysis/view/__init__.py +0 -9
- janito-0.7.0/janito/change/analysis/view/terminal.py +0 -181
- janito-0.7.0/janito/change/applier/__init__.py +0 -5
- janito-0.7.0/janito/change/applier/file.py +0 -58
- janito-0.7.0/janito/change/applier/main.py +0 -156
- janito-0.7.0/janito/change/applier/text.py +0 -247
- janito-0.7.0/janito/change/applier/workspace_dir.py +0 -58
- janito-0.7.0/janito/change/core.py +0 -124
- janito-0.7.0/janito/change/history.py +0 -44
- janito-0.7.0/janito/change/operations.py +0 -7
- janito-0.7.0/janito/change/parser.py +0 -287
- janito-0.7.0/janito/change/play.py +0 -54
- janito-0.7.0/janito/change/preview.py +0 -82
- janito-0.7.0/janito/change/prompts.py +0 -121
- janito-0.7.0/janito/change/test.py +0 -0
- janito-0.7.0/janito/change/validator.py +0 -269
- janito-0.7.0/janito/change/viewer/__init__.py +0 -11
- janito-0.7.0/janito/change/viewer/content.py +0 -66
- janito-0.7.0/janito/change/viewer/diff.py +0 -43
- janito-0.7.0/janito/change/viewer/panels.py +0 -533
- janito-0.7.0/janito/change/viewer/styling.py +0 -114
- janito-0.7.0/janito/change/viewer/themes.py +0 -55
- janito-0.7.0/janito/clear_statement_parser/clear_statement_format.txt +0 -328
- janito-0.7.0/janito/clear_statement_parser/examples.txt +0 -326
- janito-0.7.0/janito/clear_statement_parser/models.py +0 -104
- janito-0.7.0/janito/clear_statement_parser/parser.py +0 -496
- janito-0.7.0/janito/cli/__init__.py +0 -2
- janito-0.7.0/janito/cli/base.py +0 -30
- janito-0.7.0/janito/cli/commands.py +0 -88
- janito-0.7.0/janito/cli/functions.py +0 -111
- janito-0.7.0/janito/cli/history.py +0 -61
- janito-0.7.0/janito/cli/registry.py +0 -26
- janito-0.7.0/janito/common.py +0 -80
- janito-0.7.0/janito/config.py +0 -102
- janito-0.7.0/janito/demo/__init__.py +0 -4
- janito-0.7.0/janito/demo/data.py +0 -13
- janito-0.7.0/janito/demo/mock_data.py +0 -20
- janito-0.7.0/janito/demo/operations.py +0 -45
- janito-0.7.0/janito/demo/runner.py +0 -59
- janito-0.7.0/janito/demo/scenarios.py +0 -32
- janito-0.7.0/janito/prompt.py +0 -36
- janito-0.7.0/janito/qa.py +0 -57
- janito-0.7.0/janito/review.py +0 -13
- janito-0.7.0/janito/search_replace/README.md +0 -192
- janito-0.7.0/janito/search_replace/__init__.py +0 -7
- janito-0.7.0/janito/search_replace/__main__.py +0 -21
- janito-0.7.0/janito/search_replace/core.py +0 -120
- janito-0.7.0/janito/search_replace/logger.py +0 -35
- janito-0.7.0/janito/search_replace/parser.py +0 -52
- janito-0.7.0/janito/search_replace/play.py +0 -61
- janito-0.7.0/janito/search_replace/replacer.py +0 -36
- janito-0.7.0/janito/search_replace/searcher.py +0 -411
- janito-0.7.0/janito/search_replace/strategy_result.py +0 -10
- janito-0.7.0/janito/shell/__init__.py +0 -38
- janito-0.7.0/janito/shell/bus.py +0 -31
- janito-0.7.0/janito/shell/commands.py +0 -136
- janito-0.7.0/janito/shell/history.py +0 -20
- janito-0.7.0/janito/shell/processor.py +0 -32
- janito-0.7.0/janito/shell/prompt.py +0 -48
- janito-0.7.0/janito/shell/registry.py +0 -60
- janito-0.7.0/janito/tui/__init__.py +0 -21
- janito-0.7.0/janito/tui/base.py +0 -22
- janito-0.7.0/janito/tui/flows/__init__.py +0 -5
- janito-0.7.0/janito/tui/flows/changes.py +0 -65
- janito-0.7.0/janito/tui/flows/content.py +0 -128
- janito-0.7.0/janito/tui/flows/selection.py +0 -117
- janito-0.7.0/janito/tui/screens/__init__.py +0 -3
- janito-0.7.0/janito/tui/screens/app.py +0 -1
- janito-0.7.0/janito/version.py +0 -23
- janito-0.7.0/janito/workspace/__init__.py +0 -6
- janito-0.7.0/janito/workspace/analysis.py +0 -121
- janito-0.7.0/janito/workspace/show.py +0 -141
- janito-0.7.0/janito/workspace/stats.py +0 -43
- janito-0.7.0/janito/workspace/types.py +0 -98
- janito-0.7.0/janito/workspace/workset.py +0 -108
- janito-0.7.0/janito/workspace/workspace.py +0 -114
- janito-0.7.0/pyproject.toml +0 -41
- janito-0.7.0/setup.py +0 -4
- janito-0.7.0/tests/test_python_adjustments.py +0 -271
- janito-0.7.0/tools/release.sh +0 -87
@@ -1,21 +1,21 @@
|
|
1
|
-
MIT License
|
2
|
-
|
3
|
-
Copyright (c)
|
4
|
-
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
7
|
-
in the Software without restriction, including without limitation the rights
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
10
|
-
furnished to do so, subject to the following conditions:
|
11
|
-
|
12
|
-
The above copyright notice and permission notice shall be included in all
|
13
|
-
copies or substantial portions of the Software.
|
14
|
-
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) [year] [copyright holder]
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
21
|
SOFTWARE.
|
janito-0.9.0/MANIFEST.in
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
include janito/data/*.txt
|
janito-0.9.0/PKG-INFO
ADDED
janito-0.9.0/README.md
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# 🤖 Janito
|
2
|
+
|
3
|
+
Janito is a powerful AI-assisted command-line interface (CLI) tool built with Python, leveraging Anthropic's Claude for intelligent code and file management.
|
4
|
+
|
5
|
+
## ✨ Features
|
6
|
+
|
7
|
+
- 🧠 Intelligent AI assistant powered by Claude
|
8
|
+
- 📁 File management capabilities
|
9
|
+
- 🔍 Smart code search and editing
|
10
|
+
- 💻 Interactive terminal interface with rich formatting
|
11
|
+
- 📊 Token usage tracking and cost reporting
|
12
|
+
|
13
|
+
## 🛠️ Installation
|
14
|
+
|
15
|
+
```bash
|
16
|
+
# Clone the repository
|
17
|
+
git clone https://github.com/joaompinto/janito.git
|
18
|
+
cd janito
|
19
|
+
|
20
|
+
# Install the package
|
21
|
+
pip install -e .
|
22
|
+
```
|
23
|
+
|
24
|
+
## 🚀 Usage
|
25
|
+
|
26
|
+
After installation, you can use the `janito` command in your terminal:
|
27
|
+
|
28
|
+
```bash
|
29
|
+
# Get help
|
30
|
+
janito --help
|
31
|
+
|
32
|
+
|
33
|
+
# Ask the AI assistant a question
|
34
|
+
janito "Suggest improvements to this project"
|
35
|
+
|
36
|
+
janito "Add a --version to the cli to report he version"
|
37
|
+
|
38
|
+
```
|
39
|
+
|
40
|
+
## 🔧 Available Tools
|
41
|
+
|
42
|
+
Janito comes with several built-in tools:
|
43
|
+
- 📄 `str_replace_editor` - View, create, and edit files
|
44
|
+
- 🔎 `find_files` - Find files matching patterns
|
45
|
+
- 🗑️ `delete_file` - Delete files
|
46
|
+
- 🔍 `search_text` - Search for text patterns in files
|
47
|
+
|
48
|
+
## ⚙️ Requirements
|
49
|
+
|
50
|
+
- Python 3.8 or higher
|
51
|
+
- Dependencies:
|
52
|
+
- typer (>=0.9.0)
|
53
|
+
- rich (>=13.0.0)
|
54
|
+
- claudine (for Claude AI integration)
|
55
|
+
|
56
|
+
## 🔑 API Key
|
57
|
+
|
58
|
+
Janito requires an Anthropic API key to function. You can:
|
59
|
+
1. Set it as an environment variable: `export ANTHROPIC_API_KEY=your_api_key`
|
60
|
+
2. Or enter it when prompted
|
61
|
+
|
62
|
+
## 💻 Development
|
63
|
+
|
64
|
+
```bash
|
65
|
+
# Create a virtual environment
|
66
|
+
python -m venv .venv
|
67
|
+
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
68
|
+
|
69
|
+
# Install development dependencies
|
70
|
+
pip install -e ".[dev]"
|
71
|
+
```
|
72
|
+
|
73
|
+
## 📜 License
|
74
|
+
|
75
|
+
[Add your license information here]
|
@@ -0,0 +1,151 @@
|
|
1
|
+
"""
|
2
|
+
Main entry point for the janito CLI.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import typer
|
6
|
+
import os
|
7
|
+
import sys
|
8
|
+
from pathlib import Path
|
9
|
+
from typing import Optional, Dict, Any, List
|
10
|
+
from rich.console import Console
|
11
|
+
from rich import print as rprint
|
12
|
+
from rich.markdown import Markdown
|
13
|
+
import claudine
|
14
|
+
from claudine.exceptions import MaxTokensExceededException, MaxRoundsExceededException
|
15
|
+
import locale
|
16
|
+
|
17
|
+
# Fix console encoding for Windows
|
18
|
+
if sys.platform == 'win32':
|
19
|
+
# Try to set UTF-8 mode for Windows 10 version 1903 or newer
|
20
|
+
os.system('chcp 65001 > NUL')
|
21
|
+
# Ensure stdout and stderr are using UTF-8
|
22
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
23
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
24
|
+
# Set locale to UTF-8
|
25
|
+
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
|
26
|
+
|
27
|
+
from janito.tools.str_replace_editor.editor import str_replace_editor
|
28
|
+
from janito.tools.find_files import find_files
|
29
|
+
from janito.tools.delete_file import delete_file
|
30
|
+
from janito.tools.search_text import search_text
|
31
|
+
from janito.config import get_config
|
32
|
+
from janito.callbacks import pre_tool_callback, post_tool_callback
|
33
|
+
from janito.token_report import generate_token_report
|
34
|
+
|
35
|
+
app = typer.Typer(help="Janito CLI tool")
|
36
|
+
|
37
|
+
@app.command()
|
38
|
+
def hello(name: str = typer.Argument("World", help="Name to greet")):
|
39
|
+
"""
|
40
|
+
Say hello to someone.
|
41
|
+
"""
|
42
|
+
rprint(f"[bold green]Hello {name}[/bold green]")
|
43
|
+
|
44
|
+
|
45
|
+
|
46
|
+
@app.callback(invoke_without_command=True)
|
47
|
+
def main(ctx: typer.Context,
|
48
|
+
query: Optional[str] = typer.Argument(None, help="Query to send to the claudine agent"),
|
49
|
+
debug: bool = typer.Option(False, "--debug", "-d", help="Enable debug mode"),
|
50
|
+
verbose: bool = typer.Option(False, "--verbose", "-v", help="Show detailed token usage and pricing information"),
|
51
|
+
workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Set the workspace directory")):
|
52
|
+
"""
|
53
|
+
Janito CLI tool. If a query is provided without a command, it will be sent to the claudine agent.
|
54
|
+
"""
|
55
|
+
console = Console()
|
56
|
+
|
57
|
+
# Set debug mode in config
|
58
|
+
get_config().debug_mode = debug
|
59
|
+
|
60
|
+
if workspace:
|
61
|
+
try:
|
62
|
+
print(f"Setting workspace directory to: {workspace}")
|
63
|
+
get_config().workspace_dir = workspace
|
64
|
+
print(f"Workspace directory set to: {get_config().workspace_dir}")
|
65
|
+
except ValueError as e:
|
66
|
+
console.print(f"[bold red]Error:[/bold red] {str(e)}")
|
67
|
+
sys.exit(1)
|
68
|
+
|
69
|
+
if ctx.invoked_subcommand is None:
|
70
|
+
# If no query provided in command line, read from stdin
|
71
|
+
if not query:
|
72
|
+
console.print("[bold blue]No query provided in command line. Reading from stdin...[/bold blue]")
|
73
|
+
query = sys.stdin.read().strip()
|
74
|
+
|
75
|
+
# Only proceed if we have a query (either from command line or stdin)
|
76
|
+
if query:
|
77
|
+
# Get API key from environment variable or ask the user
|
78
|
+
api_key = os.environ.get("ANTHROPIC_API_KEY")
|
79
|
+
if not api_key:
|
80
|
+
console.print("[bold yellow]Warning:[/bold yellow] ANTHROPIC_API_KEY environment variable not set.")
|
81
|
+
console.print("Please set it or provide your API key now:")
|
82
|
+
api_key = typer.prompt("Anthropic API Key", hide_input=True)
|
83
|
+
|
84
|
+
# Load instructions from file
|
85
|
+
import importlib.resources as pkg_resources
|
86
|
+
try:
|
87
|
+
# For Python 3.9+
|
88
|
+
try:
|
89
|
+
from importlib.resources import files
|
90
|
+
instructions = files('janito.data').joinpath('instructions.txt').read_text()
|
91
|
+
# Fallback for older Python versions
|
92
|
+
except (ImportError, AttributeError):
|
93
|
+
instructions = pkg_resources.read_text('janito.data', 'instructions.txt')
|
94
|
+
instructions = instructions.strip()
|
95
|
+
except Exception as e:
|
96
|
+
console.print(f"[bold yellow]Warning:[/bold yellow] Could not load instructions file: {str(e)}")
|
97
|
+
console.print("[dim]Using default instructions instead.[/dim]")
|
98
|
+
instructions = "You are a helpful AI assistant. Answer the user's questions to the best of your ability."
|
99
|
+
|
100
|
+
# Initialize the agent with the tools
|
101
|
+
agent = claudine.Agent(
|
102
|
+
api_key=api_key,
|
103
|
+
tools=[
|
104
|
+
delete_file,
|
105
|
+
find_files,
|
106
|
+
search_text
|
107
|
+
],
|
108
|
+
text_editor_tool=str_replace_editor,
|
109
|
+
tool_callbacks=(pre_tool_callback, post_tool_callback),
|
110
|
+
max_tokens=4096,
|
111
|
+
temperature=0.7,
|
112
|
+
instructions=instructions,
|
113
|
+
debug_mode=debug # Enable debug mode
|
114
|
+
)
|
115
|
+
|
116
|
+
# Process the query
|
117
|
+
console.print(f"[bold blue]Query:[/bold blue] {query}")
|
118
|
+
console.print("[bold blue]Generating response...[/bold blue]")
|
119
|
+
|
120
|
+
try:
|
121
|
+
response = agent.process_prompt(query)
|
122
|
+
|
123
|
+
console.print("\n[bold green]Janito:[/bold green]")
|
124
|
+
# Use rich's enhanced Markdown rendering for the response
|
125
|
+
console.print(Markdown(response, code_theme="monokai"))
|
126
|
+
|
127
|
+
except MaxTokensExceededException as e:
|
128
|
+
# Display the partial response if available
|
129
|
+
if e.response_text:
|
130
|
+
console.print("\n[bold green]Partial Janito:[/bold green]")
|
131
|
+
console.print(Markdown(e.response_text, code_theme="monokai"))
|
132
|
+
|
133
|
+
console.print("\n[bold red]Error:[/bold red] Response was truncated because it reached the maximum token limit.")
|
134
|
+
console.print("[dim]Consider increasing the max_tokens parameter or simplifying your query.[/dim]")
|
135
|
+
|
136
|
+
except MaxRoundsExceededException as e:
|
137
|
+
# Display the final response if available
|
138
|
+
if e.response_text:
|
139
|
+
console.print("\n[bold green]Janito:[/bold green]")
|
140
|
+
console.print(Markdown(e.response_text, code_theme="monokai"))
|
141
|
+
|
142
|
+
console.print(f"\n[bold red]Error:[/bold red] Maximum number of tool execution rounds ({e.rounds}) reached. Some tasks may be incomplete.")
|
143
|
+
console.print("[dim]Consider increasing the max_rounds parameter or breaking down your task into smaller steps.[/dim]")
|
144
|
+
|
145
|
+
# Show token usage report
|
146
|
+
generate_token_report(agent, verbose)
|
147
|
+
else:
|
148
|
+
console.print("[bold yellow]No query provided. Exiting.[/bold yellow]")
|
149
|
+
|
150
|
+
if __name__ == "__main__":
|
151
|
+
app()
|
@@ -0,0 +1,130 @@
|
|
1
|
+
"""
|
2
|
+
Callback functions for tool execution in janito.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from typing import Dict, Any, Tuple
|
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
|
+
def pre_tool_callback(tool_name: str, tool_input: Dict[str, Any], preamble_text: str = "") -> Tuple[Dict[str, Any], bool]:
|
17
|
+
"""
|
18
|
+
Callback function that runs before a tool is executed.
|
19
|
+
|
20
|
+
Args:
|
21
|
+
tool_name: Name of the tool being called
|
22
|
+
tool_input: Input parameters for the tool
|
23
|
+
preamble_text: Any text generated before the tool call
|
24
|
+
|
25
|
+
Returns:
|
26
|
+
Tuple of (modified tool input, whether to cancel the tool call)
|
27
|
+
"""
|
28
|
+
console = Console()
|
29
|
+
|
30
|
+
# Add debug counter only when debug mode is enabled
|
31
|
+
if get_config().debug_mode:
|
32
|
+
if not hasattr(pre_tool_callback, "counter"):
|
33
|
+
pre_tool_callback.counter = 1
|
34
|
+
console.print(f"[bold yellow]DEBUG: Starting tool call #{pre_tool_callback.counter}[/bold yellow]")
|
35
|
+
pre_tool_callback.counter += 1
|
36
|
+
|
37
|
+
# Print preamble text with enhanced markdown support if provided
|
38
|
+
if preamble_text:
|
39
|
+
# Use a single print statement to avoid extra newlines
|
40
|
+
console.print("[bold magenta]Janito:[/bold magenta] ", Markdown(preamble_text, code_theme="monokai"), end="")
|
41
|
+
|
42
|
+
# Try to find the tool function
|
43
|
+
tool_func = None
|
44
|
+
for tool in [find_files, str_replace_editor, delete_file, search_text]:
|
45
|
+
if tool.__name__ == tool_name:
|
46
|
+
tool_func = tool
|
47
|
+
break
|
48
|
+
|
49
|
+
# Create a copy of tool_input to modify for display
|
50
|
+
display_input = {}
|
51
|
+
|
52
|
+
# Maximum length for string values
|
53
|
+
max_length = 50
|
54
|
+
|
55
|
+
# Trim long string values for display
|
56
|
+
for key, value in tool_input.items():
|
57
|
+
if isinstance(value, str) and len(value) > max_length:
|
58
|
+
# For long strings, show first and last part with ellipsis in between
|
59
|
+
display_input[key] = f"{value[:20]}...{value[-20:]}" if len(value) > 45 else value[:max_length] + "..."
|
60
|
+
else:
|
61
|
+
display_input[key] = value
|
62
|
+
|
63
|
+
# If we found the tool and it has a tool_meta label, use that for display
|
64
|
+
if tool_func:
|
65
|
+
formatted_label = format_tool_label(tool_func, tool_input)
|
66
|
+
if formatted_label:
|
67
|
+
console.print("[bold cyan] Tool:[/bold cyan]", formatted_label, end=" → ")
|
68
|
+
else:
|
69
|
+
console.print("[bold cyan] Tool:[/bold cyan]", f"{tool_name} {display_input}", end=" → ")
|
70
|
+
|
71
|
+
return tool_input, True # Continue with the tool call
|
72
|
+
|
73
|
+
def post_tool_callback(tool_name: str, tool_input: Dict[str, Any], result: Any) -> Any:
|
74
|
+
"""
|
75
|
+
Callback function that runs after a tool is executed.
|
76
|
+
|
77
|
+
Args:
|
78
|
+
tool_name: Name of the tool that was called
|
79
|
+
tool_input: Input parameters for the tool
|
80
|
+
result: Result of the tool call
|
81
|
+
|
82
|
+
Returns:
|
83
|
+
Modified result
|
84
|
+
"""
|
85
|
+
console = Console()
|
86
|
+
|
87
|
+
# Add debug counter only when debug mode is enabled
|
88
|
+
if get_config().debug_mode:
|
89
|
+
if not hasattr(post_tool_callback, "counter"):
|
90
|
+
post_tool_callback.counter = 1
|
91
|
+
console.print(f"[bold green]DEBUG: Completed tool call #{post_tool_callback.counter}[/bold green]")
|
92
|
+
post_tool_callback.counter += 1
|
93
|
+
|
94
|
+
# Extract the last line of the result
|
95
|
+
if isinstance(result, tuple) and len(result) >= 1:
|
96
|
+
content, is_error = result
|
97
|
+
# Define prefix icon based on is_error
|
98
|
+
icon_prefix = "❌ " if is_error else "✅ "
|
99
|
+
|
100
|
+
if isinstance(content, str):
|
101
|
+
# For find_files, extract just the count from the last line
|
102
|
+
if tool_name == "find_files" and content.count("\n") > 0:
|
103
|
+
lines = content.strip().split('\n')
|
104
|
+
if lines and lines[-1].isdigit():
|
105
|
+
console.print(f"{icon_prefix}{lines[-1]}")
|
106
|
+
else:
|
107
|
+
# Get the last line
|
108
|
+
last_line = content.strip().split('\n')[-1]
|
109
|
+
console.print(f"{icon_prefix}{last_line}")
|
110
|
+
else:
|
111
|
+
# For other tools, just get the last line
|
112
|
+
if '\n' in content:
|
113
|
+
last_line = content.strip().split('\n')[-1]
|
114
|
+
console.print(f"{icon_prefix}{last_line}")
|
115
|
+
else:
|
116
|
+
console.print(f"{icon_prefix}{content}")
|
117
|
+
else:
|
118
|
+
console.print(f"{icon_prefix}{content}")
|
119
|
+
else:
|
120
|
+
# If result is not a tuple, convert to string and get the last line
|
121
|
+
result_str = str(result)
|
122
|
+
# Default to success icon when no error status is available
|
123
|
+
icon_prefix = "✅ "
|
124
|
+
if '\n' in result_str:
|
125
|
+
last_line = result_str.strip().split('\n')[-1]
|
126
|
+
console.print(f"{icon_prefix}{last_line}")
|
127
|
+
else:
|
128
|
+
console.print(f"{icon_prefix}{result_str}")
|
129
|
+
|
130
|
+
return result
|
@@ -0,0 +1,202 @@
|
|
1
|
+
"""
|
2
|
+
CLI functionality for the janito tool.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import os
|
6
|
+
import sys
|
7
|
+
from pathlib import Path
|
8
|
+
from typing import Optional
|
9
|
+
import typer
|
10
|
+
from rich.console import Console
|
11
|
+
from rich.markdown import Markdown
|
12
|
+
from rich import print as rprint
|
13
|
+
import claudine
|
14
|
+
from claudine.exceptions import MaxTokensExceededException, MaxRoundsExceededException
|
15
|
+
|
16
|
+
from janito.config import get_config
|
17
|
+
from janito.tools import find_files
|
18
|
+
from janito.tools.str_replace_editor.editor import str_replace_editor
|
19
|
+
from janito.tools.delete_file import delete_file
|
20
|
+
from janito.tools.search_text import search_text
|
21
|
+
from janito.callbacks import pre_tool_callback, post_tool_callback
|
22
|
+
|
23
|
+
app = typer.Typer(help="Janito CLI tool")
|
24
|
+
|
25
|
+
@app.command()
|
26
|
+
def hello(name: str = typer.Argument("World", help="Name to greet")):
|
27
|
+
"""
|
28
|
+
Say hello to someone.
|
29
|
+
"""
|
30
|
+
rprint(f"[bold green]Hello {name}[/bold green]")
|
31
|
+
|
32
|
+
|
33
|
+
|
34
|
+
def debug_tokens(agent):
|
35
|
+
"""
|
36
|
+
Display detailed token usage and pricing information.
|
37
|
+
"""
|
38
|
+
from claudine.token_tracking import MODEL_PRICING, DEFAULT_MODEL
|
39
|
+
|
40
|
+
console = Console()
|
41
|
+
usage = agent.get_token_usage()
|
42
|
+
text_usage = usage.text_usage
|
43
|
+
tools_usage = usage.tools_usage
|
44
|
+
total_usage = usage.total_usage
|
45
|
+
|
46
|
+
# Get the pricing model
|
47
|
+
pricing = MODEL_PRICING.get(DEFAULT_MODEL)
|
48
|
+
|
49
|
+
# Calculate costs manually
|
50
|
+
text_input_cost = pricing.input_tokens.calculate_cost(text_usage.input_tokens)
|
51
|
+
text_output_cost = pricing.output_tokens.calculate_cost(text_usage.output_tokens)
|
52
|
+
tools_input_cost = pricing.input_tokens.calculate_cost(tools_usage.input_tokens)
|
53
|
+
tools_output_cost = pricing.output_tokens.calculate_cost(tools_usage.output_tokens)
|
54
|
+
|
55
|
+
# Format costs
|
56
|
+
format_cost = lambda cost: f"{cost * 100:.2f}¢" if cost < 1.0 else f"${cost:.6f}"
|
57
|
+
|
58
|
+
console.print("\n[bold blue]Detailed Token Usage:[/bold blue]")
|
59
|
+
console.print(f"Text Input tokens: {text_usage.input_tokens}")
|
60
|
+
console.print(f"Text Output tokens: {text_usage.output_tokens}")
|
61
|
+
console.print(f"Text Total tokens: {text_usage.input_tokens + text_usage.output_tokens}")
|
62
|
+
console.print(f"Tool Input tokens: {tools_usage.input_tokens}")
|
63
|
+
console.print(f"Tool Output tokens: {tools_usage.output_tokens}")
|
64
|
+
console.print(f"Tool Total tokens: {tools_usage.input_tokens + tools_usage.output_tokens}")
|
65
|
+
console.print(f"Total tokens: {total_usage.input_tokens + total_usage.output_tokens}")
|
66
|
+
|
67
|
+
console.print("\n[bold blue]Pricing Information:[/bold blue]")
|
68
|
+
console.print(f"Input pricing: ${pricing.input_tokens.cost_per_million_tokens}/million tokens")
|
69
|
+
console.print(f"Output pricing: ${pricing.output_tokens.cost_per_million_tokens}/million tokens")
|
70
|
+
console.print(f"Text Input cost: {format_cost(text_input_cost)}")
|
71
|
+
console.print(f"Text Output cost: {format_cost(text_output_cost)}")
|
72
|
+
console.print(f"Text Total cost: {format_cost(text_input_cost + text_output_cost)}")
|
73
|
+
console.print(f"Tool Input cost: {format_cost(tools_input_cost)}")
|
74
|
+
console.print(f"Tool Output cost: {format_cost(tools_output_cost)}")
|
75
|
+
console.print(f"Tool Total cost: {format_cost(tools_input_cost + tools_output_cost)}")
|
76
|
+
console.print(f"Total cost: {format_cost(text_input_cost + text_output_cost + tools_input_cost + tools_output_cost)}")
|
77
|
+
|
78
|
+
# Display per-tool breakdown if available
|
79
|
+
if usage.by_tool:
|
80
|
+
console.print("\n[bold blue]Per-Tool Breakdown:[/bold blue]")
|
81
|
+
for tool_name, tool_usage in usage.by_tool.items():
|
82
|
+
tool_input_cost = pricing.input_tokens.calculate_cost(tool_usage.input_tokens)
|
83
|
+
tool_output_cost = pricing.output_tokens.calculate_cost(tool_usage.output_tokens)
|
84
|
+
console.print(f" Tool: {tool_name}")
|
85
|
+
console.print(f" Input tokens: {tool_usage.input_tokens}")
|
86
|
+
console.print(f" Output tokens: {tool_usage.output_tokens}")
|
87
|
+
console.print(f" Total tokens: {tool_usage.input_tokens + tool_usage.output_tokens}")
|
88
|
+
console.print(f" Total cost: {format_cost(tool_input_cost + tool_output_cost)}")
|
89
|
+
|
90
|
+
def process_query(query: str, debug: bool, verbose: bool):
|
91
|
+
"""
|
92
|
+
Process a query using the claudine agent.
|
93
|
+
"""
|
94
|
+
console = Console()
|
95
|
+
|
96
|
+
# Get API key from environment variable or ask the user
|
97
|
+
api_key = os.environ.get("ANTHROPIC_API_KEY")
|
98
|
+
if not api_key:
|
99
|
+
console.print("[bold yellow]Warning:[/bold yellow] ANTHROPIC_API_KEY environment variable not set.")
|
100
|
+
console.print("Please set it or provide your API key now:")
|
101
|
+
api_key = typer.prompt("Anthropic API Key", hide_input=True)
|
102
|
+
|
103
|
+
# Load instructions from file
|
104
|
+
import importlib.resources as pkg_resources
|
105
|
+
try:
|
106
|
+
# For Python 3.9+
|
107
|
+
try:
|
108
|
+
from importlib.resources import files
|
109
|
+
instructions = files('janito.data').joinpath('instructions.txt').read_text()
|
110
|
+
# Fallback for older Python versions
|
111
|
+
except (ImportError, AttributeError):
|
112
|
+
instructions = pkg_resources.read_text('janito.data', 'instructions.txt')
|
113
|
+
instructions = instructions.strip()
|
114
|
+
except Exception as e:
|
115
|
+
console.print(f"[bold yellow]Warning:[/bold yellow] Could not load instructions file: {str(e)}")
|
116
|
+
console.print("[dim]Using default instructions instead.[/dim]")
|
117
|
+
instructions = "You are a helpful AI assistant. Answer the user's questions to the best of your ability."
|
118
|
+
|
119
|
+
# Initialize the agent with the tools
|
120
|
+
agent = claudine.Agent(
|
121
|
+
api_key=api_key,
|
122
|
+
tools=[
|
123
|
+
delete_file,
|
124
|
+
find_files,
|
125
|
+
search_text
|
126
|
+
],
|
127
|
+
text_editor_tool=str_replace_editor,
|
128
|
+
tool_callbacks=(pre_tool_callback, post_tool_callback),
|
129
|
+
max_tokens=4096,
|
130
|
+
temperature=0.7,
|
131
|
+
instructions=instructions,
|
132
|
+
debug_mode=debug # Enable debug mode
|
133
|
+
)
|
134
|
+
|
135
|
+
# Process the query
|
136
|
+
console.print(f"[bold blue]Query:[/bold blue] {query}")
|
137
|
+
console.print("[bold blue]Generating response...[/bold blue]")
|
138
|
+
|
139
|
+
try:
|
140
|
+
response = agent.process_prompt(query)
|
141
|
+
|
142
|
+
console.print("\n[bold magenta]Janito:[/bold magenta] ", end="")
|
143
|
+
# Use rich's enhanced Markdown rendering for the response
|
144
|
+
console.print(Markdown(response, code_theme="monokai"))
|
145
|
+
|
146
|
+
except MaxTokensExceededException as e:
|
147
|
+
# Display the partial response if available
|
148
|
+
if e.response_text:
|
149
|
+
console.print("\n[bold magenta]Janito:[/bold magenta] ", end="")
|
150
|
+
console.print(Markdown(e.response_text, code_theme="monokai"))
|
151
|
+
|
152
|
+
console.print("\n[bold red]Error:[/bold red] Response was truncated because it reached the maximum token limit.")
|
153
|
+
console.print("[dim]Consider increasing the max_tokens parameter or simplifying your query.[/dim]")
|
154
|
+
|
155
|
+
except MaxRoundsExceededException as e:
|
156
|
+
# Display the final response if available
|
157
|
+
if e.response_text:
|
158
|
+
console.print("\n[bold magenta]Janito:[/bold magenta] ", end="")
|
159
|
+
console.print(Markdown(e.response_text, code_theme="monokai"))
|
160
|
+
|
161
|
+
console.print(f"\n[bold red]Error:[/bold red] Maximum number of tool execution rounds ({e.rounds}) reached. Some tasks may be incomplete.")
|
162
|
+
console.print("[dim]Consider increasing the max_rounds parameter or breaking down your task into smaller steps.[/dim]")
|
163
|
+
|
164
|
+
# Show token usage
|
165
|
+
usage = agent.get_token_usage()
|
166
|
+
text_usage = usage.text_usage
|
167
|
+
tools_usage = usage.tools_usage
|
168
|
+
|
169
|
+
if verbose:
|
170
|
+
debug_tokens(agent)
|
171
|
+
else:
|
172
|
+
total_tokens = text_usage.input_tokens + text_usage.output_tokens + tools_usage.input_tokens + tools_usage.output_tokens
|
173
|
+
cost_info = agent.get_cost()
|
174
|
+
cost_display = cost_info.format_total_cost() if hasattr(cost_info, 'format_total_cost') else ""
|
175
|
+
# Consolidated tokens and cost in a single line with a ruler
|
176
|
+
console.print(Rule(f"Tokens: {total_tokens} | Cost: {cost_display}", style="dim", align="center"))
|
177
|
+
|
178
|
+
@app.callback(invoke_without_command=True)
|
179
|
+
def main(ctx: typer.Context,
|
180
|
+
query: Optional[str] = typer.Argument(None, help="Query to send to the claudine agent"),
|
181
|
+
debug: bool = typer.Option(False, "--debug", "-d", help="Enable debug mode"),
|
182
|
+
verbose: bool = typer.Option(False, "--verbose", "-v", help="Show detailed token usage and pricing information"),
|
183
|
+
workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Set the workspace directory")):
|
184
|
+
"""
|
185
|
+
Janito CLI tool. If a query is provided without a command, it will be sent to the claudine agent.
|
186
|
+
"""
|
187
|
+
console = Console()
|
188
|
+
|
189
|
+
# Set debug mode in config
|
190
|
+
get_config().debug_mode = debug
|
191
|
+
|
192
|
+
if workspace:
|
193
|
+
try:
|
194
|
+
print(f"Setting workspace directory to: {workspace}")
|
195
|
+
get_config().workspace_dir = workspace
|
196
|
+
print(f"Workspace directory set to: {get_config().workspace_dir}")
|
197
|
+
except ValueError as e:
|
198
|
+
console.print(f"[bold red]Error:[/bold red] {str(e)}")
|
199
|
+
sys.exit(1)
|
200
|
+
|
201
|
+
if ctx.invoked_subcommand is None and query:
|
202
|
+
process_query(query, debug, verbose)
|