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.
Files changed (128) hide show
  1. {janito-0.7.0 → janito-0.9.0}/LICENSE +20 -20
  2. janito-0.9.0/MANIFEST.in +1 -0
  3. janito-0.9.0/PKG-INFO +9 -0
  4. janito-0.9.0/README.md +75 -0
  5. janito-0.9.0/janito/__init__.py +5 -0
  6. janito-0.9.0/janito/__main__.py +151 -0
  7. janito-0.9.0/janito/callbacks.py +130 -0
  8. janito-0.9.0/janito/cli.py +202 -0
  9. janito-0.9.0/janito/config.py +63 -0
  10. janito-0.9.0/janito/data/instructions.txt +6 -0
  11. janito-0.9.0/janito/test_file.py +4 -0
  12. janito-0.9.0/janito/token_report.py +73 -0
  13. janito-0.9.0/janito/tools/__init__.py +10 -0
  14. janito-0.9.0/janito/tools/decorators.py +84 -0
  15. janito-0.9.0/janito/tools/delete_file.py +44 -0
  16. janito-0.9.0/janito/tools/find_files.py +154 -0
  17. janito-0.9.0/janito/tools/search_text.py +197 -0
  18. janito-0.9.0/janito/tools/str_replace_editor/__init__.py +6 -0
  19. janito-0.9.0/janito/tools/str_replace_editor/editor.py +43 -0
  20. janito-0.9.0/janito/tools/str_replace_editor/handlers.py +338 -0
  21. janito-0.9.0/janito/tools/str_replace_editor/utils.py +88 -0
  22. janito-0.9.0/janito.egg-info/PKG-INFO +9 -0
  23. janito-0.9.0/janito.egg-info/SOURCES.txt +28 -0
  24. janito-0.9.0/janito.egg-info/dependency_links.txt +1 -0
  25. janito-0.9.0/janito.egg-info/entry_points.txt +2 -0
  26. janito-0.9.0/janito.egg-info/requires.txt +3 -0
  27. janito-0.9.0/janito.egg-info/top_level.txt +1 -0
  28. janito-0.9.0/pyproject.toml +20 -0
  29. janito-0.9.0/setup.cfg +4 -0
  30. janito-0.9.0/setup.py +7 -0
  31. janito-0.7.0/.gitignore +0 -88
  32. janito-0.7.0/PKG-INFO +0 -167
  33. janito-0.7.0/README.md +0 -142
  34. janito-0.7.0/janito/__init__.py +0 -2
  35. janito-0.7.0/janito/__main__.py +0 -142
  36. janito-0.7.0/janito/agents/__init__.py +0 -22
  37. janito-0.7.0/janito/agents/agent.py +0 -28
  38. janito-0.7.0/janito/agents/claudeai.py +0 -45
  39. janito-0.7.0/janito/agents/openai.py +0 -57
  40. janito-0.7.0/janito/agents/test.py +0 -34
  41. janito-0.7.0/janito/change/__init__.py +0 -32
  42. janito-0.7.0/janito/change/__main__.py +0 -0
  43. janito-0.7.0/janito/change/analysis/__init__.py +0 -23
  44. janito-0.7.0/janito/change/analysis/__main__.py +0 -7
  45. janito-0.7.0/janito/change/analysis/analyze.py +0 -62
  46. janito-0.7.0/janito/change/analysis/formatting.py +0 -78
  47. janito-0.7.0/janito/change/analysis/options.py +0 -81
  48. janito-0.7.0/janito/change/analysis/prompts.py +0 -90
  49. janito-0.7.0/janito/change/analysis/view/__init__.py +0 -9
  50. janito-0.7.0/janito/change/analysis/view/terminal.py +0 -181
  51. janito-0.7.0/janito/change/applier/__init__.py +0 -5
  52. janito-0.7.0/janito/change/applier/file.py +0 -58
  53. janito-0.7.0/janito/change/applier/main.py +0 -156
  54. janito-0.7.0/janito/change/applier/text.py +0 -247
  55. janito-0.7.0/janito/change/applier/workspace_dir.py +0 -58
  56. janito-0.7.0/janito/change/core.py +0 -124
  57. janito-0.7.0/janito/change/history.py +0 -44
  58. janito-0.7.0/janito/change/operations.py +0 -7
  59. janito-0.7.0/janito/change/parser.py +0 -287
  60. janito-0.7.0/janito/change/play.py +0 -54
  61. janito-0.7.0/janito/change/preview.py +0 -82
  62. janito-0.7.0/janito/change/prompts.py +0 -121
  63. janito-0.7.0/janito/change/test.py +0 -0
  64. janito-0.7.0/janito/change/validator.py +0 -269
  65. janito-0.7.0/janito/change/viewer/__init__.py +0 -11
  66. janito-0.7.0/janito/change/viewer/content.py +0 -66
  67. janito-0.7.0/janito/change/viewer/diff.py +0 -43
  68. janito-0.7.0/janito/change/viewer/panels.py +0 -533
  69. janito-0.7.0/janito/change/viewer/styling.py +0 -114
  70. janito-0.7.0/janito/change/viewer/themes.py +0 -55
  71. janito-0.7.0/janito/clear_statement_parser/clear_statement_format.txt +0 -328
  72. janito-0.7.0/janito/clear_statement_parser/examples.txt +0 -326
  73. janito-0.7.0/janito/clear_statement_parser/models.py +0 -104
  74. janito-0.7.0/janito/clear_statement_parser/parser.py +0 -496
  75. janito-0.7.0/janito/cli/__init__.py +0 -2
  76. janito-0.7.0/janito/cli/base.py +0 -30
  77. janito-0.7.0/janito/cli/commands.py +0 -88
  78. janito-0.7.0/janito/cli/functions.py +0 -111
  79. janito-0.7.0/janito/cli/history.py +0 -61
  80. janito-0.7.0/janito/cli/registry.py +0 -26
  81. janito-0.7.0/janito/common.py +0 -80
  82. janito-0.7.0/janito/config.py +0 -102
  83. janito-0.7.0/janito/demo/__init__.py +0 -4
  84. janito-0.7.0/janito/demo/data.py +0 -13
  85. janito-0.7.0/janito/demo/mock_data.py +0 -20
  86. janito-0.7.0/janito/demo/operations.py +0 -45
  87. janito-0.7.0/janito/demo/runner.py +0 -59
  88. janito-0.7.0/janito/demo/scenarios.py +0 -32
  89. janito-0.7.0/janito/prompt.py +0 -36
  90. janito-0.7.0/janito/qa.py +0 -57
  91. janito-0.7.0/janito/review.py +0 -13
  92. janito-0.7.0/janito/search_replace/README.md +0 -192
  93. janito-0.7.0/janito/search_replace/__init__.py +0 -7
  94. janito-0.7.0/janito/search_replace/__main__.py +0 -21
  95. janito-0.7.0/janito/search_replace/core.py +0 -120
  96. janito-0.7.0/janito/search_replace/logger.py +0 -35
  97. janito-0.7.0/janito/search_replace/parser.py +0 -52
  98. janito-0.7.0/janito/search_replace/play.py +0 -61
  99. janito-0.7.0/janito/search_replace/replacer.py +0 -36
  100. janito-0.7.0/janito/search_replace/searcher.py +0 -411
  101. janito-0.7.0/janito/search_replace/strategy_result.py +0 -10
  102. janito-0.7.0/janito/shell/__init__.py +0 -38
  103. janito-0.7.0/janito/shell/bus.py +0 -31
  104. janito-0.7.0/janito/shell/commands.py +0 -136
  105. janito-0.7.0/janito/shell/history.py +0 -20
  106. janito-0.7.0/janito/shell/processor.py +0 -32
  107. janito-0.7.0/janito/shell/prompt.py +0 -48
  108. janito-0.7.0/janito/shell/registry.py +0 -60
  109. janito-0.7.0/janito/tui/__init__.py +0 -21
  110. janito-0.7.0/janito/tui/base.py +0 -22
  111. janito-0.7.0/janito/tui/flows/__init__.py +0 -5
  112. janito-0.7.0/janito/tui/flows/changes.py +0 -65
  113. janito-0.7.0/janito/tui/flows/content.py +0 -128
  114. janito-0.7.0/janito/tui/flows/selection.py +0 -117
  115. janito-0.7.0/janito/tui/screens/__init__.py +0 -3
  116. janito-0.7.0/janito/tui/screens/app.py +0 -1
  117. janito-0.7.0/janito/version.py +0 -23
  118. janito-0.7.0/janito/workspace/__init__.py +0 -6
  119. janito-0.7.0/janito/workspace/analysis.py +0 -121
  120. janito-0.7.0/janito/workspace/show.py +0 -141
  121. janito-0.7.0/janito/workspace/stats.py +0 -43
  122. janito-0.7.0/janito/workspace/types.py +0 -98
  123. janito-0.7.0/janito/workspace/workset.py +0 -108
  124. janito-0.7.0/janito/workspace/workspace.py +0 -114
  125. janito-0.7.0/pyproject.toml +0 -41
  126. janito-0.7.0/setup.py +0 -4
  127. janito-0.7.0/tests/test_python_adjustments.py +0 -271
  128. janito-0.7.0/tools/release.sh +0 -87
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2024 João Pinto
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.
@@ -0,0 +1 @@
1
+ include janito/data/*.txt
janito-0.9.0/PKG-INFO ADDED
@@ -0,0 +1,9 @@
1
+ Metadata-Version: 2.2
2
+ Name: janito
3
+ Version: 0.9.0
4
+ Summary: Janito CLI tool
5
+ Requires-Python: >=3.8
6
+ License-File: LICENSE
7
+ Requires-Dist: typer>=0.9.0
8
+ Requires-Dist: rich>=13.0.0
9
+ Requires-Dist: claudine>=0.1.0
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,5 @@
1
+ """
2
+ Janito package.
3
+ """
4
+
5
+ __version__ = "0.9.0"
@@ -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)