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.
Files changed (119) hide show
  1. janito-0.6.0/PKG-INFO +185 -0
  2. janito-0.6.0/README.md +160 -0
  3. janito-0.6.0/janito/__init__.py +2 -0
  4. janito-0.6.0/janito/__main__.py +135 -0
  5. janito-0.6.0/janito/agents/__init__.py +16 -0
  6. janito-0.6.0/janito/agents/agent.py +21 -0
  7. janito-0.4.0/janito/claude.py → janito-0.6.0/janito/agents/claudeai.py +13 -17
  8. janito-0.6.0/janito/agents/openai.py +53 -0
  9. janito-0.6.0/janito/agents/test.py +34 -0
  10. janito-0.6.0/janito/change/__init__.py +32 -0
  11. janito-0.6.0/janito/change/__main__.py +0 -0
  12. janito-0.6.0/janito/change/analysis/__init__.py +23 -0
  13. janito-0.6.0/janito/change/analysis/__main__.py +7 -0
  14. janito-0.6.0/janito/change/analysis/analyze.py +61 -0
  15. janito-0.6.0/janito/change/analysis/formatting.py +78 -0
  16. janito-0.6.0/janito/change/analysis/options.py +81 -0
  17. janito-0.6.0/janito/change/analysis/prompts.py +98 -0
  18. janito-0.6.0/janito/change/analysis/view/__init__.py +9 -0
  19. janito-0.6.0/janito/change/analysis/view/terminal.py +171 -0
  20. janito-0.6.0/janito/change/applier/__init__.py +5 -0
  21. janito-0.6.0/janito/change/applier/file.py +58 -0
  22. janito-0.6.0/janito/change/applier/main.py +156 -0
  23. janito-0.6.0/janito/change/applier/text.py +245 -0
  24. janito-0.6.0/janito/change/applier/workspace_dir.py +58 -0
  25. janito-0.6.0/janito/change/core.py +131 -0
  26. janito-0.6.0/janito/change/history.py +44 -0
  27. janito-0.6.0/janito/change/operations.py +7 -0
  28. janito-0.6.0/janito/change/parser.py +289 -0
  29. janito-0.6.0/janito/change/play.py +54 -0
  30. janito-0.6.0/janito/change/preview.py +82 -0
  31. janito-0.6.0/janito/change/prompts.py +126 -0
  32. janito-0.6.0/janito/change/test.py +0 -0
  33. janito-0.6.0/janito/change/validator.py +251 -0
  34. janito-0.6.0/janito/change/viewer/__init__.py +11 -0
  35. janito-0.6.0/janito/change/viewer/content.py +66 -0
  36. janito-0.6.0/janito/change/viewer/diff.py +43 -0
  37. janito-0.6.0/janito/change/viewer/pager.py +56 -0
  38. janito-0.6.0/janito/change/viewer/panels.py +555 -0
  39. janito-0.6.0/janito/change/viewer/styling.py +103 -0
  40. janito-0.6.0/janito/change/viewer/themes.py +55 -0
  41. janito-0.6.0/janito/clear_statement_parser/clear_statement_format.txt +328 -0
  42. janito-0.6.0/janito/clear_statement_parser/examples.txt +326 -0
  43. janito-0.6.0/janito/clear_statement_parser/models.py +104 -0
  44. janito-0.6.0/janito/clear_statement_parser/parser.py +496 -0
  45. janito-0.6.0/janito/cli/__init__.py +2 -0
  46. janito-0.6.0/janito/cli/base.py +30 -0
  47. janito-0.6.0/janito/cli/commands.py +45 -0
  48. janito-0.6.0/janito/cli/functions.py +111 -0
  49. janito-0.6.0/janito/cli/handlers/ask.py +22 -0
  50. janito-0.6.0/janito/cli/handlers/demo.py +22 -0
  51. janito-0.6.0/janito/cli/handlers/request.py +24 -0
  52. janito-0.6.0/janito/cli/handlers/scan.py +9 -0
  53. janito-0.6.0/janito/cli/history.py +61 -0
  54. janito-0.6.0/janito/cli/registry.py +26 -0
  55. janito-0.6.0/janito/common.py +54 -0
  56. janito-0.6.0/janito/config.py +102 -0
  57. janito-0.6.0/janito/demo/__init__.py +4 -0
  58. janito-0.6.0/janito/demo/data.py +13 -0
  59. janito-0.6.0/janito/demo/mock_data.py +20 -0
  60. janito-0.6.0/janito/demo/operations.py +45 -0
  61. janito-0.6.0/janito/demo/runner.py +59 -0
  62. janito-0.6.0/janito/demo/scenarios.py +32 -0
  63. janito-0.6.0/janito/prompts.py +2 -0
  64. {janito-0.4.0 → janito-0.6.0}/janito/qa.py +8 -5
  65. janito-0.6.0/janito/review.py +13 -0
  66. janito-0.6.0/janito/search_replace/README.md +146 -0
  67. janito-0.6.0/janito/search_replace/__init__.py +6 -0
  68. janito-0.6.0/janito/search_replace/__main__.py +21 -0
  69. janito-0.6.0/janito/search_replace/core.py +119 -0
  70. janito-0.6.0/janito/search_replace/parser.py +52 -0
  71. janito-0.6.0/janito/search_replace/play.py +61 -0
  72. janito-0.6.0/janito/search_replace/replacer.py +36 -0
  73. janito-0.6.0/janito/search_replace/searcher.py +299 -0
  74. janito-0.6.0/janito/shell/__init__.py +39 -0
  75. janito-0.6.0/janito/shell/bus.py +31 -0
  76. janito-0.6.0/janito/shell/commands.py +195 -0
  77. janito-0.6.0/janito/shell/handlers.py +122 -0
  78. janito-0.6.0/janito/shell/history.py +20 -0
  79. janito-0.6.0/janito/shell/processor.py +52 -0
  80. janito-0.6.0/janito/tui/__init__.py +21 -0
  81. janito-0.6.0/janito/tui/base.py +22 -0
  82. janito-0.6.0/janito/tui/flows/__init__.py +5 -0
  83. janito-0.6.0/janito/tui/flows/changes.py +65 -0
  84. janito-0.6.0/janito/tui/flows/content.py +128 -0
  85. janito-0.6.0/janito/tui/flows/selection.py +117 -0
  86. janito-0.6.0/janito/tui/screens/__init__.py +3 -0
  87. janito-0.6.0/janito/tui/screens/app.py +1 -0
  88. janito-0.6.0/janito/workspace/__init__.py +7 -0
  89. janito-0.6.0/janito/workspace/analysis.py +121 -0
  90. janito-0.6.0/janito/workspace/manager.py +48 -0
  91. janito-0.6.0/janito/workspace/scan.py +232 -0
  92. {janito-0.4.0 → janito-0.6.0}/pyproject.toml +1 -1
  93. janito-0.6.0/tests/test_python_adjustments.py +271 -0
  94. janito-0.6.0/tools/release.sh +87 -0
  95. janito-0.4.0/PKG-INFO +0 -164
  96. janito-0.4.0/README.md +0 -140
  97. janito-0.4.0/janito/__init__.py +0 -2
  98. janito-0.4.0/janito/__main__.py +0 -359
  99. janito-0.4.0/janito/analysis.py +0 -281
  100. janito-0.4.0/janito/changeapplier.py +0 -436
  101. janito-0.4.0/janito/changeviewer.py +0 -350
  102. janito-0.4.0/janito/common.py +0 -23
  103. janito-0.4.0/janito/config.py +0 -37
  104. janito-0.4.0/janito/console.py +0 -330
  105. janito-0.4.0/janito/contentchange.py +0 -84
  106. janito-0.4.0/janito/contextparser.py +0 -113
  107. janito-0.4.0/janito/fileparser.py +0 -125
  108. janito-0.4.0/janito/prompts.py +0 -66
  109. janito-0.4.0/janito/scan.py +0 -137
  110. janito-0.4.0/tests/conftest.py +0 -45
  111. janito-0.4.0/tests/test_contentchange.py +0 -68
  112. janito-0.4.0/tests/test_integration.py +0 -64
  113. janito-0.4.0/tests/test_prompts.py +0 -59
  114. janito-0.4.0/tests/test_scan.py +0 -98
  115. janito-0.4.0/tools/release.sh +0 -28
  116. {janito-0.4.0 → janito-0.6.0}/.gitignore +0 -0
  117. {janito-0.4.0 → janito-0.6.0}/LICENSE +0 -0
  118. {janito-0.4.0 → janito-0.6.0}/janito/version.py +0 -0
  119. {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
+ [![PyPI version](https://badge.fury.io/py/janito.svg)](https://badge.fury.io/py/janito)
29
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
+ [![PyPI version](https://badge.fury.io/py/janito.svg)](https://badge.fury.io/py/janito)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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,2 @@
1
+ """Core package initialization for Janito."""
2
+
@@ -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 ClaudeAPIAgent:
7
+ class ClaudeAIAgent(Agent):
7
8
  """Handles interaction with Claude API, including message handling"""
8
- def __init__(self, api_key: Optional[str] = None, system_prompt: str = None):
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
- self.api_key = api_key or os.getenv('ANTHROPIC_API_KEY')
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 = "claude-3-5-sonnet-20241022"
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
- self.messages_history = []
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=4000,
41
+ max_tokens=8192,
39
42
  messages=[
40
43
  {"role": "user", "content": message}
41
44
  ],
42
45
  temperature=0,
43
46
  )
44
47
 
45
- # Handle response
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 response_text
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?")