janito 0.4.0__tar.gz → 0.6.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.6.0/PKG-INFO +185 -0
- janito-0.6.0/README.md +160 -0
- janito-0.6.0/janito/__init__.py +2 -0
- janito-0.6.0/janito/__main__.py +135 -0
- janito-0.6.0/janito/agents/__init__.py +16 -0
- janito-0.6.0/janito/agents/agent.py +21 -0
- janito-0.4.0/janito/claude.py → janito-0.6.0/janito/agents/claudeai.py +13 -17
- janito-0.6.0/janito/agents/openai.py +53 -0
- janito-0.6.0/janito/agents/test.py +34 -0
- janito-0.6.0/janito/change/__init__.py +32 -0
- janito-0.6.0/janito/change/__main__.py +0 -0
- janito-0.6.0/janito/change/analysis/__init__.py +23 -0
- janito-0.6.0/janito/change/analysis/__main__.py +7 -0
- janito-0.6.0/janito/change/analysis/analyze.py +61 -0
- janito-0.6.0/janito/change/analysis/formatting.py +78 -0
- janito-0.6.0/janito/change/analysis/options.py +81 -0
- janito-0.6.0/janito/change/analysis/prompts.py +98 -0
- janito-0.6.0/janito/change/analysis/view/__init__.py +9 -0
- janito-0.6.0/janito/change/analysis/view/terminal.py +171 -0
- janito-0.6.0/janito/change/applier/__init__.py +5 -0
- janito-0.6.0/janito/change/applier/file.py +58 -0
- janito-0.6.0/janito/change/applier/main.py +156 -0
- janito-0.6.0/janito/change/applier/text.py +245 -0
- janito-0.6.0/janito/change/applier/workspace_dir.py +58 -0
- janito-0.6.0/janito/change/core.py +131 -0
- janito-0.6.0/janito/change/history.py +44 -0
- janito-0.6.0/janito/change/operations.py +7 -0
- janito-0.6.0/janito/change/parser.py +289 -0
- janito-0.6.0/janito/change/play.py +54 -0
- janito-0.6.0/janito/change/preview.py +82 -0
- janito-0.6.0/janito/change/prompts.py +126 -0
- janito-0.6.0/janito/change/test.py +0 -0
- janito-0.6.0/janito/change/validator.py +251 -0
- janito-0.6.0/janito/change/viewer/__init__.py +11 -0
- janito-0.6.0/janito/change/viewer/content.py +66 -0
- janito-0.6.0/janito/change/viewer/diff.py +43 -0
- janito-0.6.0/janito/change/viewer/pager.py +56 -0
- janito-0.6.0/janito/change/viewer/panels.py +555 -0
- janito-0.6.0/janito/change/viewer/styling.py +103 -0
- janito-0.6.0/janito/change/viewer/themes.py +55 -0
- janito-0.6.0/janito/clear_statement_parser/clear_statement_format.txt +328 -0
- janito-0.6.0/janito/clear_statement_parser/examples.txt +326 -0
- janito-0.6.0/janito/clear_statement_parser/models.py +104 -0
- janito-0.6.0/janito/clear_statement_parser/parser.py +496 -0
- janito-0.6.0/janito/cli/__init__.py +2 -0
- janito-0.6.0/janito/cli/base.py +30 -0
- janito-0.6.0/janito/cli/commands.py +45 -0
- janito-0.6.0/janito/cli/functions.py +111 -0
- janito-0.6.0/janito/cli/handlers/ask.py +22 -0
- janito-0.6.0/janito/cli/handlers/demo.py +22 -0
- janito-0.6.0/janito/cli/handlers/request.py +24 -0
- janito-0.6.0/janito/cli/handlers/scan.py +9 -0
- janito-0.6.0/janito/cli/history.py +61 -0
- janito-0.6.0/janito/cli/registry.py +26 -0
- janito-0.6.0/janito/common.py +54 -0
- janito-0.6.0/janito/config.py +102 -0
- janito-0.6.0/janito/demo/__init__.py +4 -0
- janito-0.6.0/janito/demo/data.py +13 -0
- janito-0.6.0/janito/demo/mock_data.py +20 -0
- janito-0.6.0/janito/demo/operations.py +45 -0
- janito-0.6.0/janito/demo/runner.py +59 -0
- janito-0.6.0/janito/demo/scenarios.py +32 -0
- janito-0.6.0/janito/prompts.py +2 -0
- {janito-0.4.0 → janito-0.6.0}/janito/qa.py +8 -5
- janito-0.6.0/janito/review.py +13 -0
- janito-0.6.0/janito/search_replace/README.md +146 -0
- janito-0.6.0/janito/search_replace/__init__.py +6 -0
- janito-0.6.0/janito/search_replace/__main__.py +21 -0
- janito-0.6.0/janito/search_replace/core.py +119 -0
- janito-0.6.0/janito/search_replace/parser.py +52 -0
- janito-0.6.0/janito/search_replace/play.py +61 -0
- janito-0.6.0/janito/search_replace/replacer.py +36 -0
- janito-0.6.0/janito/search_replace/searcher.py +299 -0
- janito-0.6.0/janito/shell/__init__.py +39 -0
- janito-0.6.0/janito/shell/bus.py +31 -0
- janito-0.6.0/janito/shell/commands.py +195 -0
- janito-0.6.0/janito/shell/handlers.py +122 -0
- janito-0.6.0/janito/shell/history.py +20 -0
- janito-0.6.0/janito/shell/processor.py +52 -0
- janito-0.6.0/janito/tui/__init__.py +21 -0
- janito-0.6.0/janito/tui/base.py +22 -0
- janito-0.6.0/janito/tui/flows/__init__.py +5 -0
- janito-0.6.0/janito/tui/flows/changes.py +65 -0
- janito-0.6.0/janito/tui/flows/content.py +128 -0
- janito-0.6.0/janito/tui/flows/selection.py +117 -0
- janito-0.6.0/janito/tui/screens/__init__.py +3 -0
- janito-0.6.0/janito/tui/screens/app.py +1 -0
- janito-0.6.0/janito/workspace/__init__.py +7 -0
- janito-0.6.0/janito/workspace/analysis.py +121 -0
- janito-0.6.0/janito/workspace/manager.py +48 -0
- janito-0.6.0/janito/workspace/scan.py +232 -0
- {janito-0.4.0 → janito-0.6.0}/pyproject.toml +1 -1
- janito-0.6.0/tests/test_python_adjustments.py +271 -0
- janito-0.6.0/tools/release.sh +87 -0
- janito-0.4.0/PKG-INFO +0 -164
- janito-0.4.0/README.md +0 -140
- janito-0.4.0/janito/__init__.py +0 -2
- janito-0.4.0/janito/__main__.py +0 -359
- janito-0.4.0/janito/analysis.py +0 -281
- janito-0.4.0/janito/changeapplier.py +0 -436
- janito-0.4.0/janito/changeviewer.py +0 -350
- janito-0.4.0/janito/common.py +0 -23
- janito-0.4.0/janito/config.py +0 -37
- janito-0.4.0/janito/console.py +0 -330
- janito-0.4.0/janito/contentchange.py +0 -84
- janito-0.4.0/janito/contextparser.py +0 -113
- janito-0.4.0/janito/fileparser.py +0 -125
- janito-0.4.0/janito/prompts.py +0 -66
- janito-0.4.0/janito/scan.py +0 -137
- janito-0.4.0/tests/conftest.py +0 -45
- janito-0.4.0/tests/test_contentchange.py +0 -68
- janito-0.4.0/tests/test_integration.py +0 -64
- janito-0.4.0/tests/test_prompts.py +0 -59
- janito-0.4.0/tests/test_scan.py +0 -98
- janito-0.4.0/tools/release.sh +0 -28
- {janito-0.4.0 → janito-0.6.0}/.gitignore +0 -0
- {janito-0.4.0 → janito-0.6.0}/LICENSE +0 -0
- {janito-0.4.0 → janito-0.6.0}/janito/version.py +0 -0
- {janito-0.4.0 → janito-0.6.0}/setup.py +0 -0
janito-0.6.0/PKG-INFO
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: janito
|
3
|
+
Version: 0.6.0
|
4
|
+
Summary: A CLI tool for software development tasks powered by AI
|
5
|
+
Project-URL: Homepage, https://github.com/joaompinto/janito
|
6
|
+
Project-URL: Repository, https://github.com/joaompinto/janito.git
|
7
|
+
Author-email: João Pinto <lamego.pinto@gmail.com>
|
8
|
+
License: MIT
|
9
|
+
License-File: LICENSE
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
11
|
+
Classifier: Environment :: Console
|
12
|
+
Classifier: Intended Audience :: Developers
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
17
|
+
Classifier: Topic :: Software Development
|
18
|
+
Requires-Python: >=3.8
|
19
|
+
Requires-Dist: anthropic
|
20
|
+
Requires-Dist: pathspec
|
21
|
+
Requires-Dist: rich
|
22
|
+
Requires-Dist: tomli
|
23
|
+
Requires-Dist: typer
|
24
|
+
Description-Content-Type: text/markdown
|
25
|
+
|
26
|
+
# Janito
|
27
|
+
|
28
|
+
[](https://badge.fury.io/py/janito)
|
29
|
+
[](https://opensource.org/licenses/MIT)
|
30
|
+
|
31
|
+
AI-powered CLI tool for code modifications and analysis. Janito helps you modify, analyze, and understand your codebase using natural language commands.
|
32
|
+
|
33
|
+
## Table of Contents
|
34
|
+
|
35
|
+
- [Features](#features)
|
36
|
+
- [Installation](#installation)
|
37
|
+
- [Prerequisites](#prerequisites)
|
38
|
+
- [Steps](#steps)
|
39
|
+
- [Usage](#usage)
|
40
|
+
- [Basic Commands](#basic-commands)
|
41
|
+
- [Common Use Cases](#common-use-cases)
|
42
|
+
- [Configuration](#configuration)
|
43
|
+
- [Environment Variables](#environment-variables)
|
44
|
+
- [Command Line Options](#command-line-options)
|
45
|
+
- [Troubleshooting](#troubleshooting)
|
46
|
+
- [Common Issues](#common-issues)
|
47
|
+
- [Error Messages](#error-messages)
|
48
|
+
- [Contributing](#contributing)
|
49
|
+
- [License](#license)
|
50
|
+
|
51
|
+
## ✨ Features
|
52
|
+
|
53
|
+
- Natural language code modifications
|
54
|
+
- Codebase analysis and question answering
|
55
|
+
- Smart search and replace with indentation awareness
|
56
|
+
- Git-aware operations
|
57
|
+
- Interactive shell mode
|
58
|
+
- Change preview and validation
|
59
|
+
- Automatic backup and restore
|
60
|
+
|
61
|
+
## 🚀 Installation
|
62
|
+
|
63
|
+
### Prerequisites
|
64
|
+
|
65
|
+
- Python 3.8 or higher
|
66
|
+
- pip package manager
|
67
|
+
- An Anthropic API key (default) or OpenAI API key
|
68
|
+
|
69
|
+
### Steps
|
70
|
+
|
71
|
+
1. Install using pip:
|
72
|
+
```bash
|
73
|
+
pip install janito
|
74
|
+
```
|
75
|
+
|
76
|
+
2. Configure your API key:
|
77
|
+
|
78
|
+
For Anthropic Claude (default):
|
79
|
+
```bash
|
80
|
+
export ANTHROPIC_API_KEY=your_api_key_here
|
81
|
+
```
|
82
|
+
|
83
|
+
For OpenAI:
|
84
|
+
```bash
|
85
|
+
export OPENAI_API_KEY=your_api_key_here
|
86
|
+
export AI_BACKEND=openai
|
87
|
+
```
|
88
|
+
|
89
|
+
## 💡 Usage
|
90
|
+
|
91
|
+
### Basic Commands
|
92
|
+
|
93
|
+
```bash
|
94
|
+
# Modify code
|
95
|
+
janito "add docstrings to this file"
|
96
|
+
|
97
|
+
# Ask questions about the codebase
|
98
|
+
janito --ask "explain the main function in this file"
|
99
|
+
|
100
|
+
# Preview files that would be analyzed
|
101
|
+
janito --scan
|
102
|
+
|
103
|
+
# Start interactive shell
|
104
|
+
janito
|
105
|
+
```
|
106
|
+
|
107
|
+
### Common Use Cases
|
108
|
+
|
109
|
+
1. Code Documentation:
|
110
|
+
```bash
|
111
|
+
janito "add type hints to all functions"
|
112
|
+
janito "improve docstrings with more details"
|
113
|
+
```
|
114
|
+
|
115
|
+
2. Code Analysis:
|
116
|
+
```bash
|
117
|
+
janito --ask "what are the main classes in this project?"
|
118
|
+
janito --ask "explain the error handling flow"
|
119
|
+
```
|
120
|
+
|
121
|
+
3. Code Refactoring:
|
122
|
+
```bash
|
123
|
+
janito "convert this class to use dataclasses"
|
124
|
+
janito "split this large function into smaller ones"
|
125
|
+
```
|
126
|
+
|
127
|
+
## ⚙️ Configuration
|
128
|
+
|
129
|
+
### Environment Variables
|
130
|
+
|
131
|
+
- `ANTHROPIC_API_KEY`: Anthropic API key
|
132
|
+
- `OPENAI_API_KEY`: OpenAI API key (if using OpenAI backend)
|
133
|
+
- `AI_BACKEND`: AI provider ('claudeai' or 'openai')
|
134
|
+
- `JANITO_TEST_CMD`: Default test command to run after changes
|
135
|
+
|
136
|
+
### Command Line Options
|
137
|
+
|
138
|
+
- `-w, --workspace_dir`: Set working directory
|
139
|
+
- `-i, --include`: Additional paths to include
|
140
|
+
- `--debug`: Show debug information
|
141
|
+
- `--verbose`: Show verbose output
|
142
|
+
- `--auto-apply`: Apply changes without confirmation
|
143
|
+
- `--history`: Display history of requests
|
144
|
+
|
145
|
+
## 🔧 Troubleshooting
|
146
|
+
|
147
|
+
### Common Issues
|
148
|
+
|
149
|
+
1. API Key Issues:
|
150
|
+
```bash
|
151
|
+
# Verify API key is set
|
152
|
+
echo $ANTHROPIC_API_KEY
|
153
|
+
|
154
|
+
# Temporarily set API key for single command
|
155
|
+
ANTHROPIC_API_KEY=your_key janito "your request"
|
156
|
+
```
|
157
|
+
|
158
|
+
2. Path Issues:
|
159
|
+
```bash
|
160
|
+
# Use absolute paths if having issues with relative paths
|
161
|
+
janito -w /full/path/to/project "your request"
|
162
|
+
|
163
|
+
# Specify additional paths explicitly
|
164
|
+
janito -i ./src -i ./tests "your request"
|
165
|
+
```
|
166
|
+
|
167
|
+
3. Debug Mode:
|
168
|
+
```bash
|
169
|
+
# Enable debug output for troubleshooting
|
170
|
+
janito --debug "your request"
|
171
|
+
```
|
172
|
+
|
173
|
+
### Error Messages
|
174
|
+
|
175
|
+
- "No command given": Provide a change request or command
|
176
|
+
- "No input provided": Check if using --input mode correctly
|
177
|
+
- "Duplicate path provided": Remove duplicate paths from includes
|
178
|
+
|
179
|
+
## 👥 Contributing
|
180
|
+
|
181
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
182
|
+
|
183
|
+
## 📄 License
|
184
|
+
|
185
|
+
MIT License - see [LICENSE](LICENSE)
|
janito-0.6.0/README.md
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
# Janito
|
2
|
+
|
3
|
+
[](https://badge.fury.io/py/janito)
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
5
|
+
|
6
|
+
AI-powered CLI tool for code modifications and analysis. Janito helps you modify, analyze, and understand your codebase using natural language commands.
|
7
|
+
|
8
|
+
## Table of Contents
|
9
|
+
|
10
|
+
- [Features](#features)
|
11
|
+
- [Installation](#installation)
|
12
|
+
- [Prerequisites](#prerequisites)
|
13
|
+
- [Steps](#steps)
|
14
|
+
- [Usage](#usage)
|
15
|
+
- [Basic Commands](#basic-commands)
|
16
|
+
- [Common Use Cases](#common-use-cases)
|
17
|
+
- [Configuration](#configuration)
|
18
|
+
- [Environment Variables](#environment-variables)
|
19
|
+
- [Command Line Options](#command-line-options)
|
20
|
+
- [Troubleshooting](#troubleshooting)
|
21
|
+
- [Common Issues](#common-issues)
|
22
|
+
- [Error Messages](#error-messages)
|
23
|
+
- [Contributing](#contributing)
|
24
|
+
- [License](#license)
|
25
|
+
|
26
|
+
## ✨ Features
|
27
|
+
|
28
|
+
- Natural language code modifications
|
29
|
+
- Codebase analysis and question answering
|
30
|
+
- Smart search and replace with indentation awareness
|
31
|
+
- Git-aware operations
|
32
|
+
- Interactive shell mode
|
33
|
+
- Change preview and validation
|
34
|
+
- Automatic backup and restore
|
35
|
+
|
36
|
+
## 🚀 Installation
|
37
|
+
|
38
|
+
### Prerequisites
|
39
|
+
|
40
|
+
- Python 3.8 or higher
|
41
|
+
- pip package manager
|
42
|
+
- An Anthropic API key (default) or OpenAI API key
|
43
|
+
|
44
|
+
### Steps
|
45
|
+
|
46
|
+
1. Install using pip:
|
47
|
+
```bash
|
48
|
+
pip install janito
|
49
|
+
```
|
50
|
+
|
51
|
+
2. Configure your API key:
|
52
|
+
|
53
|
+
For Anthropic Claude (default):
|
54
|
+
```bash
|
55
|
+
export ANTHROPIC_API_KEY=your_api_key_here
|
56
|
+
```
|
57
|
+
|
58
|
+
For OpenAI:
|
59
|
+
```bash
|
60
|
+
export OPENAI_API_KEY=your_api_key_here
|
61
|
+
export AI_BACKEND=openai
|
62
|
+
```
|
63
|
+
|
64
|
+
## 💡 Usage
|
65
|
+
|
66
|
+
### Basic Commands
|
67
|
+
|
68
|
+
```bash
|
69
|
+
# Modify code
|
70
|
+
janito "add docstrings to this file"
|
71
|
+
|
72
|
+
# Ask questions about the codebase
|
73
|
+
janito --ask "explain the main function in this file"
|
74
|
+
|
75
|
+
# Preview files that would be analyzed
|
76
|
+
janito --scan
|
77
|
+
|
78
|
+
# Start interactive shell
|
79
|
+
janito
|
80
|
+
```
|
81
|
+
|
82
|
+
### Common Use Cases
|
83
|
+
|
84
|
+
1. Code Documentation:
|
85
|
+
```bash
|
86
|
+
janito "add type hints to all functions"
|
87
|
+
janito "improve docstrings with more details"
|
88
|
+
```
|
89
|
+
|
90
|
+
2. Code Analysis:
|
91
|
+
```bash
|
92
|
+
janito --ask "what are the main classes in this project?"
|
93
|
+
janito --ask "explain the error handling flow"
|
94
|
+
```
|
95
|
+
|
96
|
+
3. Code Refactoring:
|
97
|
+
```bash
|
98
|
+
janito "convert this class to use dataclasses"
|
99
|
+
janito "split this large function into smaller ones"
|
100
|
+
```
|
101
|
+
|
102
|
+
## ⚙️ Configuration
|
103
|
+
|
104
|
+
### Environment Variables
|
105
|
+
|
106
|
+
- `ANTHROPIC_API_KEY`: Anthropic API key
|
107
|
+
- `OPENAI_API_KEY`: OpenAI API key (if using OpenAI backend)
|
108
|
+
- `AI_BACKEND`: AI provider ('claudeai' or 'openai')
|
109
|
+
- `JANITO_TEST_CMD`: Default test command to run after changes
|
110
|
+
|
111
|
+
### Command Line Options
|
112
|
+
|
113
|
+
- `-w, --workspace_dir`: Set working directory
|
114
|
+
- `-i, --include`: Additional paths to include
|
115
|
+
- `--debug`: Show debug information
|
116
|
+
- `--verbose`: Show verbose output
|
117
|
+
- `--auto-apply`: Apply changes without confirmation
|
118
|
+
- `--history`: Display history of requests
|
119
|
+
|
120
|
+
## 🔧 Troubleshooting
|
121
|
+
|
122
|
+
### Common Issues
|
123
|
+
|
124
|
+
1. API Key Issues:
|
125
|
+
```bash
|
126
|
+
# Verify API key is set
|
127
|
+
echo $ANTHROPIC_API_KEY
|
128
|
+
|
129
|
+
# Temporarily set API key for single command
|
130
|
+
ANTHROPIC_API_KEY=your_key janito "your request"
|
131
|
+
```
|
132
|
+
|
133
|
+
2. Path Issues:
|
134
|
+
```bash
|
135
|
+
# Use absolute paths if having issues with relative paths
|
136
|
+
janito -w /full/path/to/project "your request"
|
137
|
+
|
138
|
+
# Specify additional paths explicitly
|
139
|
+
janito -i ./src -i ./tests "your request"
|
140
|
+
```
|
141
|
+
|
142
|
+
3. Debug Mode:
|
143
|
+
```bash
|
144
|
+
# Enable debug output for troubleshooting
|
145
|
+
janito --debug "your request"
|
146
|
+
```
|
147
|
+
|
148
|
+
### Error Messages
|
149
|
+
|
150
|
+
- "No command given": Provide a change request or command
|
151
|
+
- "No input provided": Check if using --input mode correctly
|
152
|
+
- "Duplicate path provided": Remove duplicate paths from includes
|
153
|
+
|
154
|
+
## 👥 Contributing
|
155
|
+
|
156
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
157
|
+
|
158
|
+
## 📄 License
|
159
|
+
|
160
|
+
MIT License - see [LICENSE](LICENSE)
|
@@ -0,0 +1,135 @@
|
|
1
|
+
import typer
|
2
|
+
from typing import Optional, List, Set
|
3
|
+
from pathlib import Path
|
4
|
+
from rich.text import Text
|
5
|
+
from rich import print as rich_print
|
6
|
+
from rich.console import Console
|
7
|
+
from rich.text import Text
|
8
|
+
from .version import get_version
|
9
|
+
|
10
|
+
from janito.agents import agent
|
11
|
+
from janito.config import config
|
12
|
+
|
13
|
+
from .cli.commands import handle_request, handle_ask, handle_play, handle_scan
|
14
|
+
|
15
|
+
app = typer.Typer(add_completion=False)
|
16
|
+
|
17
|
+
def validate_paths(paths: Optional[List[Path]]) -> Optional[List[Path]]:
|
18
|
+
"""Validate include paths for duplicates.
|
19
|
+
|
20
|
+
Args:
|
21
|
+
paths: List of paths to validate, or None if no paths provided
|
22
|
+
|
23
|
+
Returns:
|
24
|
+
Validated list of paths or None if no paths provided
|
25
|
+
"""
|
26
|
+
if not paths: # This handles both None and empty list cases
|
27
|
+
return None
|
28
|
+
|
29
|
+
# Convert paths to absolute and resolve symlinks
|
30
|
+
resolved_paths: Set[Path] = set()
|
31
|
+
unique_paths: List[Path] = []
|
32
|
+
|
33
|
+
for path in paths:
|
34
|
+
resolved = path.absolute().resolve()
|
35
|
+
if resolved in resolved_paths:
|
36
|
+
error_text = Text(f"\nError: Duplicate path provided: {path} ", style="red")
|
37
|
+
rich_print(error_text)
|
38
|
+
raise typer.Exit(1)
|
39
|
+
resolved_paths.add(resolved)
|
40
|
+
unique_paths.append(path)
|
41
|
+
|
42
|
+
return unique_paths if unique_paths else None
|
43
|
+
|
44
|
+
def typer_main(
|
45
|
+
change_request: str = typer.Argument(None, help="Change request or command"),
|
46
|
+
workspace_dir: Optional[Path] = typer.Option(None, "-w", "--workspace_dir", help="Working directory", file_okay=False, dir_okay=True),
|
47
|
+
debug: bool = typer.Option(False, "--debug", help="Show debug information"),
|
48
|
+
verbose: bool = typer.Option(False, "--verbose", help="Show verbose output"),
|
49
|
+
include: Optional[List[Path]] = typer.Option(None, "-i", "--include", help="Additional paths to include"),
|
50
|
+
ask: Optional[str] = typer.Option(None, "--ask", help="Ask a question about the codebase"),
|
51
|
+
play: Optional[Path] = typer.Option(None, "--play", help="Replay a saved prompt file"),
|
52
|
+
scan: bool = typer.Option(False, "--scan", help="Preview files that would be analyzed"),
|
53
|
+
version: bool = typer.Option(False, "--version", help="Show version information"),
|
54
|
+
test_cmd: Optional[str] = typer.Option(None, "--test", help="Command to run tests after changes"),
|
55
|
+
auto_apply: bool = typer.Option(False, "--auto-apply", help="Apply changes without confirmation"),
|
56
|
+
tui: bool = typer.Option(False, "--tui", help="Use terminal user interface"),
|
57
|
+
history: bool = typer.Option(False, "--history", help="Display history of requests"),
|
58
|
+
recursive: Optional[List[Path]] = typer.Option(None, "-r", "--recursive", help="Paths to scan recursively (directories only)"),
|
59
|
+
demo: bool = typer.Option(False, "--demo", help="Run demo scenarios"),
|
60
|
+
skipwork: bool = typer.Option(False, "--skipwork", help="Skip scanning workspace_dir when using include paths"),
|
61
|
+
):
|
62
|
+
"""Janito - AI-powered code modification assistant"""
|
63
|
+
if version:
|
64
|
+
console = Console()
|
65
|
+
console.print(f"Janito version {get_version()}")
|
66
|
+
return
|
67
|
+
|
68
|
+
if demo:
|
69
|
+
from janito.cli.handlers.demo import DemoHandler
|
70
|
+
handler = DemoHandler()
|
71
|
+
handler.handle()
|
72
|
+
return
|
73
|
+
|
74
|
+
if history:
|
75
|
+
from janito.cli.history import display_history
|
76
|
+
display_history()
|
77
|
+
return
|
78
|
+
|
79
|
+
config.set_workspace_dir(workspace_dir)
|
80
|
+
config.set_debug(debug)
|
81
|
+
config.set_verbose(verbose)
|
82
|
+
config.set_auto_apply(auto_apply)
|
83
|
+
config.set_include(include)
|
84
|
+
config.set_tui(tui)
|
85
|
+
config.set_skipwork(skipwork)
|
86
|
+
|
87
|
+
# Validate skipwork usage
|
88
|
+
if skipwork and not include and not recursive:
|
89
|
+
error_text = Text("\nError: --skipwork requires at least one include path (-i or -r)", style="red")
|
90
|
+
rich_print(error_text)
|
91
|
+
raise typer.Exit(1)
|
92
|
+
|
93
|
+
if include:
|
94
|
+
resolved_paths = []
|
95
|
+
for path in include:
|
96
|
+
path = config.workspace_dir / path
|
97
|
+
resolved_paths.append(path.resolve())
|
98
|
+
config.set_include(resolved_paths)
|
99
|
+
|
100
|
+
# Validate recursive paths
|
101
|
+
if recursive:
|
102
|
+
resolved_paths = []
|
103
|
+
for path in recursive:
|
104
|
+
final_path = config.workspace_dir / path
|
105
|
+
if not path.is_dir():
|
106
|
+
error_text = Text(f"\nError: Recursive path must be a directory: {path} ", style="red")
|
107
|
+
rich_print(error_text)
|
108
|
+
raise typer.Exit(1)
|
109
|
+
resolved_paths.append(final_path.resolve())
|
110
|
+
config.set_recursive(resolved_paths)
|
111
|
+
include = include or []
|
112
|
+
include.extend(resolved_paths)
|
113
|
+
config.set_include(include)
|
114
|
+
|
115
|
+
if test_cmd:
|
116
|
+
config.set_test_cmd(test_cmd)
|
117
|
+
|
118
|
+
if ask:
|
119
|
+
handle_ask(ask)
|
120
|
+
elif play:
|
121
|
+
handle_play(play)
|
122
|
+
elif scan:
|
123
|
+
paths_to_scan = include or [config.workspace_dir]
|
124
|
+
handle_scan(paths_to_scan)
|
125
|
+
elif change_request:
|
126
|
+
handle_request(change_request)
|
127
|
+
else:
|
128
|
+
from janito.shell import start_shell
|
129
|
+
start_shell()
|
130
|
+
|
131
|
+
def main():
|
132
|
+
typer.run(typer_main)
|
133
|
+
|
134
|
+
if __name__ == "__main__":
|
135
|
+
main()
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import os
|
2
|
+
|
3
|
+
SYSTEM_PROMPT = """I am Janito, your friendly software development buddy. I help you with coding tasks while being clear and concise in my responses."""
|
4
|
+
|
5
|
+
ai_backend = os.getenv('AI_BACKEND', 'claudeai').lower()
|
6
|
+
|
7
|
+
if ai_backend == 'openai':
|
8
|
+
from .openai import OpenAIAgent as AIAgent
|
9
|
+
elif ai_backend == 'claudeai':
|
10
|
+
from .claudeai import ClaudeAIAgent as AIAgent
|
11
|
+
else:
|
12
|
+
raise ValueError(f"Unsupported AI_BACKEND: {ai_backend}")
|
13
|
+
|
14
|
+
# Create a singleton instance
|
15
|
+
agent = AIAgent(SYSTEM_PROMPT)
|
16
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
from abc import ABC, abstractmethod
|
3
|
+
from threading import Event
|
4
|
+
from typing import Optional, List, Tuple
|
5
|
+
|
6
|
+
class Agent(ABC):
|
7
|
+
"""Abstract base class for AI agents"""
|
8
|
+
def __init__(self, api_key: Optional[str] = None, system_prompt: str = None):
|
9
|
+
self.api_key = api_key
|
10
|
+
self.system_message = system_prompt
|
11
|
+
self.last_prompt = None
|
12
|
+
self.last_full_message = None
|
13
|
+
self.last_response = None
|
14
|
+
self.messages_history: List[Tuple[str, str]] = []
|
15
|
+
if system_prompt:
|
16
|
+
self.messages_history.append(("system", system_prompt))
|
17
|
+
|
18
|
+
@abstractmethod
|
19
|
+
def send_message(self, message: str, stop_event: Event = None) -> str:
|
20
|
+
"""Send message to AI service and return response"""
|
21
|
+
pass
|
@@ -2,24 +2,27 @@ import anthropic
|
|
2
2
|
import os
|
3
3
|
from typing import Optional
|
4
4
|
from threading import Event
|
5
|
+
from .agent import Agent
|
5
6
|
|
6
|
-
class
|
7
|
+
class ClaudeAIAgent(Agent):
|
7
8
|
"""Handles interaction with Claude API, including message handling"""
|
8
|
-
|
9
|
+
DEFAULT_MODEL = "claude-3-5-sonnet-20241022"
|
10
|
+
|
11
|
+
def __init__(self, system_prompt: str = None):
|
12
|
+
self.api_key = os.getenv('ANTHROPIC_API_KEY')
|
13
|
+
super().__init__(self.api_key, system_prompt)
|
9
14
|
if not system_prompt:
|
10
15
|
raise ValueError("system_prompt is required")
|
11
|
-
|
16
|
+
|
12
17
|
if not self.api_key:
|
13
18
|
raise ValueError("ANTHROPIC_API_KEY environment variable is required")
|
14
19
|
self.client = anthropic.Client(api_key=self.api_key)
|
15
|
-
self.model =
|
20
|
+
self.model = os.getenv('CLAUDE_MODEL', self.DEFAULT_MODEL)
|
16
21
|
self.system_message = system_prompt
|
17
22
|
self.last_prompt = None
|
18
23
|
self.last_full_message = None
|
19
24
|
self.last_response = None
|
20
|
-
|
21
|
-
if system_prompt:
|
22
|
-
self.messages_history.append(("system", system_prompt))
|
25
|
+
|
23
26
|
|
24
27
|
def send_message(self, message: str, stop_event: Event = None) -> str:
|
25
28
|
"""Send message to Claude API and return response"""
|
@@ -35,23 +38,16 @@ class ClaudeAPIAgent:
|
|
35
38
|
response = self.client.messages.create(
|
36
39
|
model=self.model, # Use discovered model
|
37
40
|
system=self.system_message,
|
38
|
-
max_tokens=
|
41
|
+
max_tokens=8192,
|
39
42
|
messages=[
|
40
43
|
{"role": "user", "content": message}
|
41
44
|
],
|
42
45
|
temperature=0,
|
43
46
|
)
|
44
47
|
|
45
|
-
|
46
|
-
response_text = response.content[0].text
|
47
|
-
|
48
|
-
# Only store and process response if not cancelled
|
49
|
-
if not (stop_event and stop_event.is_set()):
|
50
|
-
self.last_response = response_text
|
51
|
-
self.messages_history.append(("assistant", response_text))
|
52
|
-
|
48
|
+
|
53
49
|
# Always return the response, let caller handle cancellation
|
54
|
-
return
|
50
|
+
return response
|
55
51
|
|
56
52
|
except KeyboardInterrupt:
|
57
53
|
if stop_event:
|
@@ -0,0 +1,53 @@
|
|
1
|
+
import openai # updated import
|
2
|
+
import os
|
3
|
+
from typing import Optional
|
4
|
+
from threading import Event
|
5
|
+
from .agent import Agent
|
6
|
+
|
7
|
+
class OpenAIAgent(Agent):
|
8
|
+
"""Handles interaction with OpenAI API, including message handling"""
|
9
|
+
DEFAULT_MODEL = "o1-mini-2024-09-12"
|
10
|
+
|
11
|
+
def __init__(self, api_key: Optional[str] = None, system_prompt: str = None):
|
12
|
+
super().__init__(api_key, system_prompt)
|
13
|
+
if not system_prompt:
|
14
|
+
raise ValueError("system_prompt is required")
|
15
|
+
self.api_key = api_key or os.getenv('OPENAI_API_KEY')
|
16
|
+
if not self.api_key:
|
17
|
+
raise ValueError("OPENAI_API_KEY environment variable is required")
|
18
|
+
openai.api_key = self.api_key
|
19
|
+
openai.organization = os.getenv("OPENAI_ORG")
|
20
|
+
self.client = openai.Client() # initialized client
|
21
|
+
self.model = os.getenv('OPENAI_MODEL', "o1-mini-2024-09-12") # reverted to original default model
|
22
|
+
|
23
|
+
def send_message(self, message: str, stop_event: Event = None) -> str:
|
24
|
+
"""Send message to OpenAI API and return response"""
|
25
|
+
self.messages_history.append(("user", message))
|
26
|
+
self.last_full_message = message
|
27
|
+
|
28
|
+
try:
|
29
|
+
if stop_event and stop_event.is_set():
|
30
|
+
return ""
|
31
|
+
|
32
|
+
#messages = [{"role": "system", "content": self.system_message}]
|
33
|
+
messages = [{"role": "user", "content": message}]
|
34
|
+
|
35
|
+
response = self.client.chat.completions.create(
|
36
|
+
model=self.model,
|
37
|
+
messages=messages,
|
38
|
+
max_completion_tokens=4000,
|
39
|
+
temperature=1,
|
40
|
+
)
|
41
|
+
|
42
|
+
response_text = response.choices[0].message.content
|
43
|
+
|
44
|
+
if not (stop_event and stop_event.is_set()):
|
45
|
+
self.last_response = response_text
|
46
|
+
self.messages_history.append(("assistant", response_text))
|
47
|
+
|
48
|
+
return response_text
|
49
|
+
|
50
|
+
except KeyboardInterrupt:
|
51
|
+
if stop_event:
|
52
|
+
stop_event.set()
|
53
|
+
return ""
|
@@ -0,0 +1,34 @@
|
|
1
|
+
import unittest
|
2
|
+
import os
|
3
|
+
from unittest.mock import patch, MagicMock
|
4
|
+
from .openai import OpenAIAgent
|
5
|
+
from .claudeai import AIAgent
|
6
|
+
|
7
|
+
class TestAIAgents(unittest.TestCase):
|
8
|
+
def setUp(self):
|
9
|
+
self.system_prompt = "You are a helpful assistant."
|
10
|
+
self.test_message = "Hello, how are you?"
|
11
|
+
|
12
|
+
def test_openai_agent_initialization(self):
|
13
|
+
with patch.dict(os.environ, {'OPENAI_API_KEY': 'test_key'}):
|
14
|
+
agent = OpenAIAgent(system_prompt=self.system_prompt)
|
15
|
+
|
16
|
+
def test_claudeai_agent_initialization(self):
|
17
|
+
with patch.dict(os.environ, {'ANTHROPIC_API_KEY': 'test_key'}):
|
18
|
+
agent = AIAgent(system_prompt=self.system_prompt)
|
19
|
+
|
20
|
+
def test_openai_agent_send_message(self):
|
21
|
+
with patch('openai.OpenAI.chat.completions.create') as mock_create:
|
22
|
+
mock_response = MagicMock()
|
23
|
+
mock_response.choices[0].message.content = "I'm good, thank you!"
|
24
|
+
mock_create.return_value = mock_response
|
25
|
+
response = self.openai_agent.send_message(self.test_message)
|
26
|
+
self.assertEqual(response, "I'm good, thank you!")
|
27
|
+
|
28
|
+
def test_claudeai_agent_send_message(self):
|
29
|
+
with patch('anthropic.Client.messages.create') as mock_create:
|
30
|
+
mock_response = MagicMock()
|
31
|
+
mock_response.content[0].text = "I'm Claude, how can I assist you?"
|
32
|
+
mock_create.return_value = mock_response
|
33
|
+
response = self.claudeai_agent.send_message(self.test_message)
|
34
|
+
self.assertEqual(response, "I'm Claude, how can I assist you?")
|