codrninja 0.3.0__tar.gz → 0.3.2__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 (26) hide show
  1. {codrninja-0.3.0/src/codrninja.egg-info → codrninja-0.3.2}/PKG-INFO +1 -1
  2. {codrninja-0.3.0 → codrninja-0.3.2}/pyproject.toml +1 -1
  3. {codrninja-0.3.0 → codrninja-0.3.2}/src/codrninja/__init__.py +0 -1
  4. codrninja-0.3.2/src/codrninja/tui.py +335 -0
  5. {codrninja-0.3.0 → codrninja-0.3.2/src/codrninja.egg-info}/PKG-INFO +1 -1
  6. {codrninja-0.3.0 → codrninja-0.3.2}/src/codrninja.egg-info/SOURCES.txt +0 -2
  7. codrninja-0.3.0/src/codrninja/__version__.py +0 -1
  8. codrninja-0.3.0/src/codrninja/_version.py +0 -24
  9. codrninja-0.3.0/src/codrninja/tui.py +0 -322
  10. {codrninja-0.3.0 → codrninja-0.3.2}/.gitignore +0 -0
  11. {codrninja-0.3.0 → codrninja-0.3.2}/LICENSE +0 -0
  12. {codrninja-0.3.0 → codrninja-0.3.2}/README.md +0 -0
  13. {codrninja-0.3.0 → codrninja-0.3.2}/install.sh +0 -0
  14. {codrninja-0.3.0 → codrninja-0.3.2}/setup.cfg +0 -0
  15. {codrninja-0.3.0 → codrninja-0.3.2}/src/codrninja/agent.py +0 -0
  16. {codrninja-0.3.0 → codrninja-0.3.2}/src/codrninja/cli.py +0 -0
  17. {codrninja-0.3.0 → codrninja-0.3.2}/src/codrninja/config.py +0 -0
  18. {codrninja-0.3.0 → codrninja-0.3.2}/src/codrninja/core.py +0 -0
  19. {codrninja-0.3.0 → codrninja-0.3.2}/src/codrninja/interactive.py +0 -0
  20. {codrninja-0.3.0 → codrninja-0.3.2}/src/codrninja/providers.py +0 -0
  21. {codrninja-0.3.0 → codrninja-0.3.2}/src/codrninja/tools.py +0 -0
  22. {codrninja-0.3.0 → codrninja-0.3.2}/src/codrninja.egg-info/dependency_links.txt +0 -0
  23. {codrninja-0.3.0 → codrninja-0.3.2}/src/codrninja.egg-info/entry_points.txt +0 -0
  24. {codrninja-0.3.0 → codrninja-0.3.2}/src/codrninja.egg-info/requires.txt +0 -0
  25. {codrninja-0.3.0 → codrninja-0.3.2}/src/codrninja.egg-info/top_level.txt +0 -0
  26. {codrninja-0.3.0 → codrninja-0.3.2}/tests/test_core.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codrninja
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: AI-first coding assistant for automation
5
5
  Author: 20ZollCoder
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "codrninja"
7
- version = "0.3.0"
7
+ version = "0.3.2"
8
8
  description = "AI-first coding assistant for automation"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -2,7 +2,6 @@
2
2
  codrninja: AI-first coding assistant for automation
3
3
  """
4
4
 
5
- __version__ = "0.1.0"
6
5
  __author__ = "Milan"
7
6
  __license__ = "MIT"
8
7
 
@@ -0,0 +1,335 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ codrninja TUI — Interactive coding assistant using prompt_toolkit.
4
+ """
5
+
6
+ import json
7
+ import os
8
+ import sys
9
+ from typing import Optional
10
+
11
+ try:
12
+ from prompt_toolkit import PromptSession
13
+ from prompt_toolkit.completion import Completer, Completion
14
+ from prompt_toolkit.styles import Style
15
+ from prompt_toolkit.key_binding import KeyBindings
16
+ HAS_PROMPT = True
17
+ except ImportError:
18
+ HAS_PROMPT = False
19
+
20
+ try:
21
+ from rich.console import Console
22
+ from rich.panel import Panel
23
+ from rich.syntax import Syntax
24
+ from rich.markdown import Markdown
25
+ from rich.status import Status
26
+ from rich.table import Table
27
+ from rich import box
28
+ HAS_RICH = True
29
+ except ImportError:
30
+ HAS_RICH = False
31
+
32
+ from codrninja.core import AICode
33
+ from codrninja.agent import Agent
34
+ from codrninja.tools import ToolRegistry
35
+
36
+
37
+ CODRNINJA_LOGO = """
38
+ ╔══════════════════════════════════════════════════════════════════════════════════════╗
39
+ ║ ║
40
+ ║ ██████╗ ██████╗ ██████╗ ██████╗ ███╗ ██╗██╗███╗ ██╗ ██╗ █████╗ ║
41
+ ║ ██╔════╝██╔═══██╗██╔══██╗██╔══██╗████╗ ██║██║████╗ ██║ ██║██╔══██╗ ║
42
+ ║ ██║ ██║ ██║██║ ██║██████╔╝██╔██╗ ██║██║██╔██╗ ██║ ██║███████║ ║
43
+ ║ ██║ ██║ ██║██║ ██║██╔══██╗██║╚██╗██║██║██║╚██╗██║██ ██║██╔══██║ ║
44
+ ║ ╚██████╗╚██████╔╝██████╔╝██║ ██║██║ ╚████║██║██║ ╚████║╚█████╔╝██║ ██║ ║
45
+ ║ ╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝╚═╝ ╚═══╝ ╚════╝ ╚═╝ ╚═╝ ║
46
+ ║ ║
47
+ ║ AI-first coding assistant for automation ║
48
+ ║ ║
49
+ ╚══════════════════════════════════════════════════════════════════════════════════════╝
50
+ """
51
+
52
+ SLASH_COMMANDS = {
53
+ "/clear": "Clear screen",
54
+ "/commit": "Git commit changes",
55
+ "/context": "Show project context",
56
+ "/edit": "Edit file by replacement",
57
+ "/exec": "Execute shell command",
58
+ "/exit": "Exit codrninja",
59
+ "/explain": "Explain code or concept",
60
+ "/files": "List files in directory",
61
+ "/help": "Show detailed help",
62
+ "/model": "Show AI configuration",
63
+ "/plan": "Plan a feature or task",
64
+ "/read": "Read file",
65
+ "/review": "Review code for issues",
66
+ "/session": "Show session info",
67
+ "/test": "Run tests",
68
+ "/write": "Write to file",
69
+ }
70
+
71
+
72
+ class SlashCompleter(Completer):
73
+ """Custom completer for slash commands."""
74
+
75
+ def get_completions(self, document, complete_event):
76
+ text = document.text
77
+ if not text.startswith('/'):
78
+ return
79
+
80
+ partial = text[1:]
81
+ for cmd, desc in SLASH_COMMANDS.items():
82
+ if cmd[1:].startswith(partial):
83
+ yield Completion(
84
+ cmd,
85
+ start_position=-len(text),
86
+ display=f"{cmd:18} {desc}",
87
+ display_meta=desc,
88
+ )
89
+
90
+
91
+ class TUI:
92
+ """Terminal User Interface using prompt_toolkit."""
93
+
94
+ def __init__(self, ai: AICode, session_name: str):
95
+ self.ai = ai
96
+ self.session_name = session_name
97
+ self.agent = Agent(ai, session_name)
98
+ self.console = Console() if HAS_RICH else None
99
+ self.tools = ToolRegistry()
100
+
101
+ def start(self):
102
+ """Start the TUI."""
103
+ if not HAS_PROMPT:
104
+ print("Error: prompt_toolkit not installed")
105
+ print("Run: pip3 install prompt_toolkit")
106
+ return
107
+
108
+ self._show_welcome()
109
+
110
+ # Create prompt session with completion
111
+ style = Style.from_dict({
112
+ 'prompt': '#00aa00 bold',
113
+ 'completion-menu': 'bg:#333333 #ffffff',
114
+ 'completion-menu.completion.current': 'bg:#ffffff #000000',
115
+ })
116
+
117
+ session = PromptSession(
118
+ completer=SlashCompleter(),
119
+ style=style,
120
+ complete_while_typing=True,
121
+ multiline=False,
122
+ )
123
+
124
+ while True:
125
+ try:
126
+ message = session.prompt('You: ')
127
+
128
+ if not message.strip():
129
+ continue
130
+
131
+ if message.lower() in ['exit', 'quit', 'q']:
132
+ print("\nGoodbye!\n")
133
+ break
134
+
135
+ if message.startswith('/'):
136
+ if not self._handle_command(message):
137
+ break
138
+ continue
139
+
140
+ self._process_message(message)
141
+
142
+ except KeyboardInterrupt:
143
+ print("\nInterrupted. Type 'exit' to quit.")
144
+ except EOFError:
145
+ break
146
+
147
+ def _show_welcome(self):
148
+ """Show welcome screen."""
149
+ if self.console:
150
+ for line in CODRNINJA_LOGO.strip().split('\n'):
151
+ self.console.print(f"[bold cyan]{line}[/bold cyan]")
152
+ self.console.print(f"\n Session: [bold yellow]{self.session_name}[/bold yellow]")
153
+ self.console.print(" Type / for commands, or just start typing\n")
154
+ else:
155
+ print(CODRNINJA_LOGO)
156
+ print(f"\n Session: {self.session_name}")
157
+ print(" Type / for commands\n")
158
+
159
+ def _process_message(self, message: str):
160
+ """Process user message."""
161
+ if self.console:
162
+ with Status("[bold yellow]Thinking...[/bold yellow]", spinner="dots", console=self.console):
163
+ result = self.agent.run(message, auto_approve=False)
164
+ else:
165
+ print("Thinking...")
166
+ result = self.agent.run(message, auto_approve=False)
167
+
168
+ if not result['success']:
169
+ if self.console:
170
+ self.console.print(f"\n[bold red]Error:[/bold red] {result.get('error', 'Unknown error')}")
171
+ else:
172
+ print(f"\nError: {result.get('error', 'Unknown error')}")
173
+ return
174
+
175
+ if self.console:
176
+ self._display_response(result['response'])
177
+ if result.get('tool_calls', 0) > 0:
178
+ self._show_tool_usage(result['tools_used'], result['iterations'])
179
+ else:
180
+ print(f"\nAI: {result['response']}")
181
+
182
+ def _handle_command(self, command: str) -> bool:
183
+ """Handle slash commands."""
184
+ parts = command.split()
185
+ cmd = parts[0].lower()
186
+
187
+ if cmd == '/exit':
188
+ print("\nGoodbye!\n")
189
+ return False
190
+ elif cmd == '/help':
191
+ print("\nCommands:")
192
+ for c, d in SLASH_COMMANDS.items():
193
+ print(f" {c:16} {d}")
194
+ print()
195
+ elif cmd == '/clear':
196
+ os.system('clear' if os.name != 'nt' else 'cls')
197
+ self._show_welcome()
198
+ elif cmd == '/files':
199
+ result = self.tools.list_files(path=".", depth=2)
200
+ if result.success:
201
+ if self.console:
202
+ self.console.print(Panel(result.output, title="📁 Files", border_style="blue"))
203
+ else:
204
+ print(f"\n{result.output}")
205
+ else:
206
+ print(f"Error: {result.error}")
207
+ elif cmd == '/model':
208
+ if self.console:
209
+ table = Table(title="AI Configuration", box=box.ROUNDED)
210
+ table.add_column("Setting", style="cyan")
211
+ table.add_column("Value", style="white")
212
+ table.add_row("Provider", self.ai.config.default_provider)
213
+ table.add_row("Model", self.ai.config.default_model)
214
+ table.add_row("Ollama URL", self.ai.config.ollama_url)
215
+ self.console.print(table)
216
+ else:
217
+ print(f"\nProvider: {self.ai.config.default_provider}")
218
+ print(f"Model: {self.ai.config.default_model}")
219
+ elif cmd == '/session':
220
+ history = self.ai.get_history(self.session_name)
221
+ if history:
222
+ print(f"\nSession: {self.session_name}")
223
+ print(f"Messages: {len(history['messages'])}")
224
+ else:
225
+ print(f"\nSession: {self.session_name} (new)")
226
+ elif cmd == '/exec':
227
+ if len(parts) > 1:
228
+ cmd_str = ' '.join(parts[1:])
229
+ if self.console:
230
+ with Status(f"[bold yellow]{cmd_str}[/bold yellow]", console=self.console):
231
+ result = self.tools.execute_command(cmd_str)
232
+ else:
233
+ result = self.tools.execute_command(cmd_str)
234
+ if result.success:
235
+ if self.console:
236
+ self.console.print(Panel(result.output, title=f"⚡ {cmd_str}", border_style="green"))
237
+ else:
238
+ print(f"\n{result.output}")
239
+ else:
240
+ print(f"Error: {result.error}")
241
+ elif cmd == '/read':
242
+ if len(parts) > 1:
243
+ result = self.tools.read_file(parts[1])
244
+ if result.success:
245
+ if self.console:
246
+ ext = os.path.splitext(parts[1])[1]
247
+ lang_map = {'.py': 'python', '.js': 'javascript', '.ts': 'typescript', '.jsx': 'javascript', '.tsx': 'typescript', '.json': 'json', '.css': 'css', '.html': 'html', '.md': 'markdown'}
248
+ lang = lang_map.get(ext, "text")
249
+ syntax = Syntax(result.output, lang, theme="monokai", line_numbers=True)
250
+ self.console.print(Panel(syntax, title=f"📄 {parts[1]}", border_style="blue"))
251
+ else:
252
+ print(f"\n{result.output}")
253
+ else:
254
+ print(f"Error: {result.error}")
255
+ elif cmd == '/plan':
256
+ topic = ' '.join(parts[1:]) if len(parts) > 1 else input("What to plan? ")
257
+ self._process_message(f"Create a detailed plan for: {topic}")
258
+ elif cmd == '/build':
259
+ topic = ' '.join(parts[1:]) if len(parts) > 1 else input("What to build? ")
260
+ self._process_message(f"Implement: {topic}")
261
+ elif cmd == '/review':
262
+ target = parts[1] if len(parts) > 1 else input("File to review? ")
263
+ result = self.tools.read_file(target)
264
+ if result.success:
265
+ self._process_message(f"Review this code:\n\n{result.output}")
266
+ else:
267
+ print(f"Error: {result.error}")
268
+ elif cmd == '/explain':
269
+ target = ' '.join(parts[1:]) if len(parts) > 1 else input("What to explain? ")
270
+ self._process_message(f"Explain: {target}")
271
+ elif cmd == '/test':
272
+ target = ' '.join(parts[1:]) if len(parts) > 1 else "npm test"
273
+ result = self.tools.execute_command(target)
274
+ if result.success:
275
+ if self.console:
276
+ self.console.print(Panel(result.output, title=f"🧪 {target}", border_style="green"))
277
+ else:
278
+ print(f"\n{result.output}")
279
+ else:
280
+ print(f"Error: {result.error}")
281
+ elif cmd == '/commit':
282
+ msg = ' '.join(parts[1:]) if len(parts) > 1 else input("Commit message? ")
283
+ self.tools.execute_command('git add -A')
284
+ result = self.tools.execute_command(f'git commit -m "{msg}"')
285
+ if result.success:
286
+ print(f"✅ Committed: {msg}")
287
+ else:
288
+ print(f"Error: {result.error}")
289
+ else:
290
+ print(f"Unknown command: {cmd}. Type /help for list.")
291
+
292
+ return True
293
+
294
+ def _display_response(self, response: str):
295
+ """Display AI response with Rich."""
296
+ parts = response.split('```')
297
+ for i, part in enumerate(parts):
298
+ if i % 2 == 0:
299
+ if part.strip():
300
+ self.console.print(f"\n[bold blue]🤖 AI[/bold blue]:")
301
+ self.console.print(Markdown(part.strip()))
302
+ else:
303
+ lines = part.split('\n')
304
+ lang = lines[0].strip() if lines else ""
305
+ code = '\n'.join(lines[1:]) if lines else part
306
+ if code.strip():
307
+ syntax = Syntax(code, lang or "text", theme="monokai", line_numbers=True)
308
+ self.console.print(syntax)
309
+
310
+ def _show_tool_usage(self, tools_used: list, iterations: int):
311
+ """Show tool usage summary."""
312
+ table = Table(title=f"Tools ({len(tools_used)} calls, {iterations} iterations)", box=box.ROUNDED)
313
+ table.add_column("Tool", style="cyan")
314
+ table.add_column("Status", style="green")
315
+ table.add_column("Result", style="white")
316
+ for tool in tools_used:
317
+ status = "✅" if tool['success'] else "❌"
318
+ result = tool['output'][:60] + "..." if len(tool['output']) > 60 else tool['output']
319
+ table.add_row(tool['tool'], status, result)
320
+ self.console.print(table)
321
+
322
+
323
+ def main():
324
+ if len(sys.argv) < 2:
325
+ print("Usage: codrninja-tui <session-name>")
326
+ sys.exit(1)
327
+
328
+ session_name = sys.argv[1]
329
+ ai = AICode()
330
+ tui = TUI(ai, session_name)
331
+ tui.start()
332
+
333
+
334
+ if __name__ == "__main__":
335
+ main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codrninja
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: AI-first coding assistant for automation
5
5
  Author: 20ZollCoder
6
6
  License: MIT
@@ -4,8 +4,6 @@ README.md
4
4
  install.sh
5
5
  pyproject.toml
6
6
  src/codrninja/__init__.py
7
- src/codrninja/__version__.py
8
- src/codrninja/_version.py
9
7
  src/codrninja/agent.py
10
8
  src/codrninja/cli.py
11
9
  src/codrninja/config.py
@@ -1 +0,0 @@
1
- __version__ = "0.3.0"
@@ -1,24 +0,0 @@
1
- # file generated by vcs-versioning
2
- # don't change, don't track in version control
3
- from __future__ import annotations
4
-
5
- __all__ = [
6
- "__version__",
7
- "__version_tuple__",
8
- "version",
9
- "version_tuple",
10
- "__commit_id__",
11
- "commit_id",
12
- ]
13
-
14
- version: str
15
- __version__: str
16
- __version_tuple__: tuple[int | str, ...]
17
- version_tuple: tuple[int | str, ...]
18
- commit_id: str | None
19
- __commit_id__: str | None
20
-
21
- __version__ = version = '0.1.dev21+g30f955b0f.d20260506'
22
- __version_tuple__ = version_tuple = (0, 1, 'dev21', 'g30f955b0f.d20260506')
23
-
24
- __commit_id__ = commit_id = 'g30f955b0f'
@@ -1,322 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- codrninja TUI — Interactive coding assistant using prompt_toolkit.
4
- Features: Arrow key navigation, tab completion, beautiful UI.
5
- """
6
-
7
- import json
8
- import os
9
- import sys
10
- from typing import Optional, List
11
-
12
- try:
13
- from prompt_toolkit import Application
14
- from prompt_toolkit.buffer import Buffer
15
- from prompt_toolkit.layout.containers import HSplit, Window, WindowAlign
16
- from prompt_toolkit.layout.controls import BufferControl, FormattedTextControl
17
- from prompt_toolkit.layout.layout import Layout
18
- from prompt_toolkit.key_binding import KeyBindings
19
- from prompt_toolkit.formatted_text import HTML
20
- from prompt_toolkit.styles import Style
21
- HAS_PROMPT = True
22
- except ImportError:
23
- HAS_PROMPT = False
24
-
25
- try:
26
- from rich.console import Console
27
- HAS_RICH = True
28
- except ImportError:
29
- HAS_RICH = False
30
-
31
- from codrninja.core import AICode
32
- from codrninja.agent import Agent
33
- from codrninja.tools import ToolRegistry
34
-
35
-
36
- # Milan's exact ASCII art for codrninja
37
- CODRNINJA_LOGO = """
38
- ╔══════════════════════════════════════════════════════════════════════════════════════╗
39
- ║ ║
40
- ║ ██████╗ ██████╗ ██████╗ ██████╗ ███╗ ██╗██╗███╗ ██╗ ██╗ █████╗ ║
41
- ║ ██╔════╝██╔═══██╗██╔══██╗██╔══██╗████╗ ██║██║████╗ ██║ ██║██╔══██╗ ║
42
- ║ ██║ ██║ ██║██║ ██║██████╔╝██╔██╗ ██║██║██╔██╗ ██║ ██║███████║ ║
43
- ║ ██║ ██║ ██║██║ ██║██╔══██╗██║╚██╗██║██║██║╚██╗██║██ ██║██╔══██║ ║
44
- ║ ╚██████╗╚██████╔╝██████╔╝██║ ██║██║ ╚████║██║██║ ╚████║╚█████╔╝██║ ██║ ║
45
- ║ ╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝╚═╝ ╚═══╝ ╚════╝ ╚═╝ ╚═╝ ║
46
- ║ ║
47
- ║ AI-first coding assistant for automation ║
48
- ║ ║
49
- ╚══════════════════════════════════════════════════════════════════════════════════════╝
50
- """
51
-
52
- # Slash commands
53
- SLASH_COMMANDS = [
54
- "/clear", "/commit", "/context", "/edit", "/exec", "/exit",
55
- "/explain", "/files", "/help", "/model", "/plan", "/read",
56
- "/review", "/session", "/test", "/write",
57
- ]
58
-
59
- COMMAND_DESCRIPTIONS = {
60
- "/clear": "Clear screen",
61
- "/commit": "Git commit changes",
62
- "/context": "Show project context",
63
- "/edit": "Edit file by replacement",
64
- "/exec": "Execute shell command",
65
- "/exit": "Exit codrninja",
66
- "/explain": "Explain code or concept",
67
- "/files": "List files in directory",
68
- "/help": "Show detailed help",
69
- "/model": "Show AI configuration",
70
- "/plan": "Plan a feature or task",
71
- "/read": "Read file",
72
- "/review": "Review code for issues",
73
- "/session": "Show session info",
74
- "/test": "Run tests",
75
- "/write": "Write to file",
76
- }
77
-
78
-
79
- class TUIPromptToolkit:
80
- """Terminal UI using prompt_toolkit for proper terminal handling."""
81
-
82
- def __init__(self, ai: AICode, session_name: str):
83
- self.ai = ai
84
- self.session_name = session_name
85
- self.agent = Agent(ai, session_name)
86
- self.console = Console() if HAS_RICH else None
87
- self.tools = ToolRegistry()
88
-
89
- def start(self):
90
- """Start the TUI."""
91
- if not HAS_PROMPT:
92
- print("Error: prompt_toolkit not installed")
93
- print("Run: pip3 install prompt_toolkit")
94
- return
95
-
96
- self._show_welcome()
97
- self._run_app()
98
-
99
- def _show_welcome(self):
100
- """Show welcome screen."""
101
- if self.console:
102
- for line in CODRNINJA_LOGO.strip().split('\n'):
103
- self.console.print(f"[bold cyan]{line}[/bold cyan]")
104
- self.console.print(f"\n Session: [bold yellow]{self.session_name}[/bold yellow]")
105
- self.console.print(" Type / for commands\n")
106
- else:
107
- print(CODRNINJA_LOGO)
108
- print(f"\n Session: {self.session_name}")
109
- print(" Type / for commands\n")
110
-
111
- def _run_app(self):
112
- """Run the prompt_toolkit application."""
113
- kb = KeyBindings()
114
-
115
- # State
116
- suggestions = []
117
- selected = 0
118
- showing_suggestions = False
119
-
120
- # Layout components
121
- input_buffer = Buffer(multiline=False)
122
- suggestion_text = FormattedTextControl("")
123
-
124
- def update_suggestions():
125
- nonlocal suggestions, selected, showing_suggestions
126
- text = input_buffer.text
127
-
128
- if text.startswith('/'):
129
- partial = text[1:]
130
- suggestions = [(cmd, COMMAND_DESCRIPTIONS.get(cmd, ""))
131
- for cmd in SLASH_COMMANDS
132
- if cmd[1:].startswith(partial)]
133
- if suggestions:
134
- selected = 0
135
- showing_suggestions = True
136
- self._render_suggestions(suggestion_text, suggestions, selected)
137
- else:
138
- showing_suggestions = False
139
- suggestion_text.text = ""
140
- else:
141
- showing_suggestions = False
142
- suggestion_text.text = ""
143
-
144
- @kb.add('c-c')
145
- @kb.add('c-d')
146
- def _(event):
147
- event.app.exit()
148
-
149
- @kb.add('up')
150
- def _(event):
151
- nonlocal selected
152
- if showing_suggestions and suggestions:
153
- selected = max(0, selected - 1)
154
- self._render_suggestions(suggestion_text, suggestions, selected)
155
-
156
- @kb.add('down')
157
- def _(event):
158
- nonlocal selected
159
- if showing_suggestions and suggestions:
160
- selected = min(len(suggestions) - 1, selected + 1)
161
- self._render_suggestions(suggestion_text, suggestions, selected)
162
-
163
- @kb.add('tab')
164
- def _(event):
165
- nonlocal showing_suggestions
166
- if showing_suggestions and suggestions:
167
- cmd = suggestions[selected][0]
168
- input_buffer.text = cmd + " "
169
- input_buffer.cursor_position = len(input_buffer.text)
170
- showing_suggestions = False
171
- suggestion_text.text = ""
172
-
173
- @kb.add('enter')
174
- def _(event):
175
- text = input_buffer.text.strip()
176
- if not text:
177
- return
178
-
179
- showing_suggestions = False
180
- suggestion_text.text = ""
181
-
182
- # Print the input
183
- if self.console:
184
- self.console.print(f"\n[bold green]You[/bold green] {text}")
185
- else:
186
- print(f"\nYou: {text}")
187
-
188
- # Process
189
- if text.startswith('/'):
190
- if not self._handle_command(text):
191
- event.app.exit()
192
- return
193
- else:
194
- self._process_message(text)
195
-
196
- # Clear input
197
- input_buffer.text = ""
198
- input_buffer.cursor_position = 0
199
-
200
- @kb.add('c-l')
201
- def _(event):
202
- os.system('clear' if os.name != 'nt' else 'cls')
203
- self._show_welcome()
204
-
205
- # Watch for text changes
206
- def on_text_changed(_):
207
- update_suggestions()
208
-
209
- input_buffer.on_text_changed += on_text_changed
210
-
211
- # Layout
212
- input_window = Window(
213
- content=BufferControl(buffer=input_buffer, focusable=True),
214
- height=1,
215
- dont_extend_height=True,
216
- )
217
-
218
- suggestion_window = Window(
219
- content=suggestion_text,
220
- height=10,
221
- dont_extend_height=True,
222
- )
223
-
224
- root_container = HSplit([
225
- input_window,
226
- suggestion_window,
227
- ])
228
-
229
- layout = Layout(root_container, focused_element=input_window)
230
-
231
- style = Style.from_dict({
232
- 'suggestion': '#666666',
233
- 'selected': 'bg:#ffffff #000000',
234
- })
235
-
236
- app = Application(
237
- layout=layout,
238
- key_bindings=kb,
239
- style=style,
240
- full_screen=False,
241
- mouse_support=False,
242
- )
243
-
244
- app.run()
245
-
246
- print("\nGoodbye!\n")
247
-
248
- def _render_suggestions(self, control, suggestions, selected):
249
- """Render the suggestion list."""
250
- lines = []
251
- for i, (cmd, desc) in enumerate(suggestions[:10]):
252
- if i == selected:
253
- lines.append(f" > {cmd:18} {desc}")
254
- else:
255
- lines.append(f" {cmd:18} {desc}")
256
- control.text = "\n".join(lines)
257
-
258
- def _process_message(self, message: str):
259
- """Process user message."""
260
- print("Thinking...")
261
- result = self.agent.run(message, auto_approve=False)
262
-
263
- if not result['success']:
264
- print(f"Error: {result.get('error', 'Unknown error')}")
265
- return
266
-
267
- print(f"\nAI: {result['response']}")
268
-
269
- def _handle_command(self, command: str) -> bool:
270
- """Handle slash commands."""
271
- parts = command.split()
272
- cmd = parts[0].lower()
273
-
274
- if cmd == '/exit':
275
- return False
276
- elif cmd == '/help':
277
- print("\nCommands:")
278
- for c, d in COMMAND_DESCRIPTIONS.items():
279
- print(f" {c:16} {d}")
280
- print()
281
- elif cmd == '/clear':
282
- os.system('clear' if os.name != 'nt' else 'cls')
283
- self._show_welcome()
284
- elif cmd == '/files':
285
- result = self.tools.list_files(path=".", depth=2)
286
- if result.success:
287
- print(f"\n{result.output}")
288
- else:
289
- print(f"Error: {result.error}")
290
- elif cmd == '/model':
291
- print(f"\nProvider: {self.ai.config.default_provider}")
292
- print(f"Model: {self.ai.config.default_model}")
293
- print(f"Ollama URL: {self.ai.config.ollama_url}")
294
- elif cmd == '/session':
295
- print(f"\nSession: {self.session_name}")
296
- elif cmd == '/exec':
297
- if len(parts) > 1:
298
- cmd_str = ' '.join(parts[1:])
299
- result = self.tools.execute_command(cmd_str)
300
- if result.success:
301
- print(f"\n{result.output}")
302
- else:
303
- print(f"Error: {result.error}")
304
- else:
305
- print(f"Unknown command: {cmd}. Type /help for list.")
306
-
307
- return True
308
-
309
-
310
- def main():
311
- if len(sys.argv) < 2:
312
- print("Usage: codrninja-tui <session-name>")
313
- sys.exit(1)
314
-
315
- session_name = sys.argv[1]
316
- ai = AICode()
317
- tui = TUIPromptToolkit(ai, session_name)
318
- tui.start()
319
-
320
-
321
- if __name__ == "__main__":
322
- main()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes