codrninja 0.3.2__tar.gz → 0.4.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 (23) hide show
  1. {codrninja-0.3.2/src/codrninja.egg-info → codrninja-0.4.0}/PKG-INFO +1 -1
  2. {codrninja-0.3.2 → codrninja-0.4.0}/pyproject.toml +1 -1
  3. {codrninja-0.3.2 → codrninja-0.4.0}/src/codrninja/agent.py +2 -2
  4. {codrninja-0.3.2 → codrninja-0.4.0}/src/codrninja/cli.py +7 -7
  5. {codrninja-0.3.2 → codrninja-0.4.0}/src/codrninja/tui.py +214 -33
  6. {codrninja-0.3.2 → codrninja-0.4.0/src/codrninja.egg-info}/PKG-INFO +1 -1
  7. {codrninja-0.3.2 → codrninja-0.4.0}/.gitignore +0 -0
  8. {codrninja-0.3.2 → codrninja-0.4.0}/LICENSE +0 -0
  9. {codrninja-0.3.2 → codrninja-0.4.0}/README.md +0 -0
  10. {codrninja-0.3.2 → codrninja-0.4.0}/install.sh +0 -0
  11. {codrninja-0.3.2 → codrninja-0.4.0}/setup.cfg +0 -0
  12. {codrninja-0.3.2 → codrninja-0.4.0}/src/codrninja/__init__.py +0 -0
  13. {codrninja-0.3.2 → codrninja-0.4.0}/src/codrninja/config.py +0 -0
  14. {codrninja-0.3.2 → codrninja-0.4.0}/src/codrninja/core.py +0 -0
  15. {codrninja-0.3.2 → codrninja-0.4.0}/src/codrninja/interactive.py +0 -0
  16. {codrninja-0.3.2 → codrninja-0.4.0}/src/codrninja/providers.py +0 -0
  17. {codrninja-0.3.2 → codrninja-0.4.0}/src/codrninja/tools.py +0 -0
  18. {codrninja-0.3.2 → codrninja-0.4.0}/src/codrninja.egg-info/SOURCES.txt +0 -0
  19. {codrninja-0.3.2 → codrninja-0.4.0}/src/codrninja.egg-info/dependency_links.txt +0 -0
  20. {codrninja-0.3.2 → codrninja-0.4.0}/src/codrninja.egg-info/entry_points.txt +0 -0
  21. {codrninja-0.3.2 → codrninja-0.4.0}/src/codrninja.egg-info/requires.txt +0 -0
  22. {codrninja-0.3.2 → codrninja-0.4.0}/src/codrninja.egg-info/top_level.txt +0 -0
  23. {codrninja-0.3.2 → codrninja-0.4.0}/tests/test_core.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codrninja
3
- Version: 0.3.2
3
+ Version: 0.4.0
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.2"
7
+ version = "0.4.0"
8
8
  description = "AI-first coding assistant for automation"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -121,7 +121,7 @@ class Agent:
121
121
 
122
122
  # Progress callback
123
123
  if self.on_progress:
124
- self.on_progress(f" {tool_name}: {result.output[:100]}...")
124
+ self.on_progress(f"[OK] {tool_name}: {result.output[:100]}...")
125
125
 
126
126
  return {
127
127
  'success': True,
@@ -186,7 +186,7 @@ class Agent:
186
186
 
187
187
  def _ask_permission(self, tool_name: str, params: Dict) -> bool:
188
188
  """Ask user for permission (interactive mode)."""
189
- print(f"\n🔒 Permission required for: {tool_name}")
189
+ print(f"\n[LOCK] Permission required for: {tool_name}")
190
190
  print(f"Params: {json.dumps(params, indent=2)}")
191
191
 
192
192
  try:
@@ -7,7 +7,7 @@ from typing import Optional
7
7
 
8
8
  from .core import AICode
9
9
  from .config import Config
10
- from .interactive import InteractiveSession
10
+ from .tui import TUI
11
11
 
12
12
 
13
13
  def main():
@@ -108,18 +108,18 @@ def main():
108
108
  output(result, True)
109
109
  else:
110
110
  if result['success']:
111
- print(f"\n Task completed in {result['iterations']} iterations")
111
+ print(f"\n[OK] Task completed in {result['iterations']} iterations")
112
112
  print(f"Tools used: {result['tool_calls']}")
113
113
  print(f"\nResponse:\n{result['response'][:500]}...")
114
114
  else:
115
- print(f"\n Error: {result.get('error', 'Unknown error')}")
115
+ print(f"\n[FAIL] Error: {result.get('error', 'Unknown error')}")
116
116
 
117
117
  # Interactive human mode commands
118
118
  elif command == "chat" or command == "interactive" or command == "i":
119
119
  session_name = sys.argv[2] if len(sys.argv) > 2 else "default"
120
120
  session = ai.create_session(session_name)
121
121
 
122
- interactive = InteractiveSession(ai, session_name)
122
+ interactive = TUI(ai, session_name)
123
123
  interactive.start()
124
124
 
125
125
  elif command == "new":
@@ -131,7 +131,7 @@ def main():
131
131
  output({"session": session_name, "status": "created"}, True)
132
132
  else:
133
133
  print(f"Created session: {session_name}")
134
- interactive = InteractiveSession(ai, session_name)
134
+ interactive = TUI(ai, session_name)
135
135
  interactive.start()
136
136
 
137
137
  elif command == "help" or command == "--help" or command == "-h":
@@ -242,11 +242,11 @@ def start_tui(session_name: str = "default"):
242
242
  tui = TUI(ai, session_name)
243
243
  tui.start()
244
244
  except ImportError:
245
- print(" Rich not installed. Install with: pip3 install rich")
245
+ print("[FAIL] Rich not installed. Install with: pip3 install rich")
246
246
  print(" Or use: codrninja chat my-session")
247
247
  sys.exit(1)
248
248
  except Exception as e:
249
- print(f" Error starting TUI: {e}")
249
+ print(f"[FAIL] Error starting TUI: {e}")
250
250
  sys.exit(1)
251
251
 
252
252
 
@@ -1,18 +1,18 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
- codrninja TUI Interactive coding assistant using prompt_toolkit.
3
+ codrninja TUI -- Interactive coding assistant using prompt_toolkit.
4
+ Features: Auto-onboarding, provider/model selection, slash commands.
4
5
  """
5
6
 
6
7
  import json
7
8
  import os
8
9
  import sys
9
- from typing import Optional
10
+ from typing import Optional, List
10
11
 
11
12
  try:
12
13
  from prompt_toolkit import PromptSession
13
14
  from prompt_toolkit.completion import Completer, Completion
14
15
  from prompt_toolkit.styles import Style
15
- from prompt_toolkit.key_binding import KeyBindings
16
16
  HAS_PROMPT = True
17
17
  except ImportError:
18
18
  HAS_PROMPT = False
@@ -35,18 +35,18 @@ from codrninja.tools import ToolRegistry
35
35
 
36
36
 
37
37
  CODRNINJA_LOGO = """
38
- ╔══════════════════════════════════════════════════════════════════════════════════════╗
39
-
40
- ██████╗ ██████╗ ██████╗ ██████╗ ███╗ ██╗██╗███╗ ██╗ ██╗ █████╗ ║
41
- ██╔════╝██╔═══██╗██╔══██╗██╔══██╗████╗ ██║██║████╗ ██║ ██║██╔══██╗ ║
42
- ██║ ██║ ██║██║ ██║██████╔╝██╔██╗ ██║██║██╔██╗ ██║ ██║███████║ ║
43
- ██║ ██║ ██║██║ ██║██╔══██╗██║╚██╗██║██║██║╚██╗██║██ ██║██╔══██║ ║
44
- ╚██████╗╚██████╔╝██████╔╝██║ ██║██║ ╚████║██║██║ ╚████║╚█████╔╝██║ ██║ ║
45
- ╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝╚═╝ ╚═══╝ ╚════╝ ╚═╝ ╚═╝ ║
46
-
47
- AI-first coding assistant for automation
48
-
49
- ╚══════════════════════════════════════════════════════════════════════════════════════╝
38
+ +======================================================================================+
39
+ | |
40
+ | ####### ####### ####### ####### # # # # # # ####### |
41
+ | # # # # # # # ## # # ## # # # |
42
+ | # # # # # # # # # # # # # # # # |
43
+ | # # # # # # # # # # # # # # ### # # |
44
+ | # # # # # # # # ## # # ## # # |
45
+ | ####### ####### ####### # # # # # # # # ####### |
46
+ | |
47
+ | AI-first coding assistant for automation |
48
+ | |
49
+ +======================================================================================+
50
50
  """
51
51
 
52
52
  SLASH_COMMANDS = {
@@ -68,6 +68,37 @@ SLASH_COMMANDS = {
68
68
  "/write": "Write to file",
69
69
  }
70
70
 
71
+ # Provider configurations
72
+ PROVIDERS = {
73
+ "ollama": {
74
+ "name": "Ollama (local, free)",
75
+ "models": ["codellama", "llama2", "mistral", "phi", "deepseek-coder"],
76
+ "needs_key": False,
77
+ "default_url": "http://localhost:11434",
78
+ },
79
+ "openai": {
80
+ "name": "OpenAI (GPT-4, GPT-3.5)",
81
+ "models": ["gpt-4", "gpt-4-turbo", "gpt-3.5-turbo"],
82
+ "needs_key": True,
83
+ "env_var": "OPENAI_API_KEY",
84
+ },
85
+ "anthropic": {
86
+ "name": "Anthropic (Claude)",
87
+ "models": ["claude-3-opus-20240229", "claude-3-sonnet-20240229", "claude-3-haiku-20240307"],
88
+ "needs_key": True,
89
+ "env_var": "ANTHROPIC_API_KEY",
90
+ },
91
+ "openrouter": {
92
+ "name": "OpenRouter (all models)",
93
+ "models": ["openai/gpt-4", "anthropic/claude-3-opus", "google/gemini-pro"],
94
+ "needs_key": True,
95
+ "env_var": "OPENROUTER_API_KEY",
96
+ },
97
+ }
98
+
99
+ CONFIG_DIR = os.path.expanduser("~/.config/codrninja")
100
+ CONFIG_FILE = os.path.join(CONFIG_DIR, "config.json")
101
+
71
102
 
72
103
  class SlashCompleter(Completer):
73
104
  """Custom completer for slash commands."""
@@ -105,6 +136,10 @@ class TUI:
105
136
  print("Run: pip3 install prompt_toolkit")
106
137
  return
107
138
 
139
+ # Check for config -- auto-onboarding if missing
140
+ if not self._has_config():
141
+ self._onboarding()
142
+
108
143
  self._show_welcome()
109
144
 
110
145
  # Create prompt session with completion
@@ -144,6 +179,140 @@ class TUI:
144
179
  except EOFError:
145
180
  break
146
181
 
182
+ def _has_config(self) -> bool:
183
+ """Check if configuration exists."""
184
+ return os.path.exists(CONFIG_FILE)
185
+
186
+ def _load_config(self) -> dict:
187
+ """Load configuration from file."""
188
+ if not self._has_config():
189
+ return {}
190
+ with open(CONFIG_FILE, 'r') as f:
191
+ return json.load(f)
192
+
193
+ def _save_config(self, config: dict):
194
+ """Save configuration to file."""
195
+ os.makedirs(CONFIG_DIR, exist_ok=True)
196
+ with open(CONFIG_FILE, 'w') as f:
197
+ json.dump(config, f, indent=2)
198
+
199
+ def _onboarding(self):
200
+ """Interactive onboarding for first-time users."""
201
+ print("\n" + "=" * 80)
202
+ print(" Welcome to codrninja!")
203
+ print(" Let's set up your AI provider.")
204
+ print("=" * 80 + "\n")
205
+
206
+ # Step 1: Provider selection
207
+ print("Step 1: Choose your AI provider\n")
208
+ providers_list = list(PROVIDERS.items())
209
+ for i, (key, info) in enumerate(providers_list, 1):
210
+ print(f" {i}. {info['name']}")
211
+ print()
212
+
213
+ while True:
214
+ choice = input("Enter choice [1-4]: ").strip()
215
+ try:
216
+ idx = int(choice) - 1
217
+ if 0 <= idx < len(providers_list):
218
+ provider_key, provider_info = providers_list[idx]
219
+ break
220
+ else:
221
+ print("Invalid choice. Please enter 1-4.")
222
+ except ValueError:
223
+ print("Please enter a number.")
224
+
225
+ print(f"\n Selected: {provider_info['name']}\n")
226
+
227
+ # Step 2: API key (if needed)
228
+ api_key = None
229
+ if provider_info.get('needs_key'):
230
+ env_var = provider_info['env_var']
231
+ existing = os.environ.get(env_var, "")
232
+
233
+ if existing:
234
+ print(f" Found {env_var} in environment.")
235
+ use_existing = input(" Use existing key? [Y/n]: ").strip().lower()
236
+ if use_existing in ['', 'y', 'yes']:
237
+ api_key = existing
238
+
239
+ if not api_key:
240
+ print(f"\n Please enter your {provider_info['name'].split('(')[0].strip()} API key:")
241
+ api_key = input(" > ").strip()
242
+
243
+ if api_key:
244
+ # Save to shell config
245
+ shell_config = self._get_shell_config()
246
+ if shell_config:
247
+ with open(shell_config, "a") as f:
248
+ f.write(f"\n# codrninja config\n")
249
+ f.write(f"export {env_var}=\"{api_key}\"\n")
250
+ print(f"\n Saved to {shell_config}")
251
+
252
+ # Step 3: Model selection
253
+ print(f"\nStep 2: Choose a model for {provider_info['name']}\n")
254
+ models = provider_info['models']
255
+ for i, model in enumerate(models, 1):
256
+ print(f" {i}. {model}")
257
+ print()
258
+
259
+ while True:
260
+ choice = input("Enter choice [1-{}]: ".format(len(models))).strip()
261
+ try:
262
+ idx = int(choice) - 1
263
+ if 0 <= idx < len(models):
264
+ model = models[idx]
265
+ break
266
+ else:
267
+ print(f"Invalid choice. Please enter 1-{len(models)}.")
268
+ except ValueError:
269
+ print("Please enter a number.")
270
+
271
+ print(f"\n Selected model: {model}\n")
272
+
273
+ # Step 4: Ollama URL (if Ollama)
274
+ ollama_url = None
275
+ if provider_key == "ollama":
276
+ default_url = provider_info['default_url']
277
+ url = input(f"Ollama URL [{default_url}]: ").strip()
278
+ ollama_url = url if url else default_url
279
+ print(f" Using: {ollama_url}\n")
280
+
281
+ # Save config
282
+ config = {
283
+ "provider": provider_key,
284
+ "model": model,
285
+ }
286
+ if ollama_url:
287
+ config["ollama_url"] = ollama_url
288
+
289
+ self._save_config(config)
290
+
291
+ # Update AI config
292
+ self.ai.config.default_provider = provider_key
293
+ self.ai.config.default_model = model
294
+ if ollama_url:
295
+ self.ai.config.ollama_url = ollama_url
296
+
297
+ print("=" * 80)
298
+ print(" Setup complete!")
299
+ print(f" Provider: {provider_info['name']}")
300
+ print(f" Model: {model}")
301
+ print("=" * 80 + "\n")
302
+ print(" You can change this later with /model")
303
+ print(" Press Enter to continue...\n")
304
+ input()
305
+
306
+ def _get_shell_config(self) -> Optional[str]:
307
+ """Get the user's shell config file path."""
308
+ if os.path.exists(os.path.expanduser("~/.zshrc")):
309
+ return os.path.expanduser("~/.zshrc")
310
+ elif os.path.exists(os.path.expanduser("~/.bashrc")):
311
+ return os.path.expanduser("~/.bashrc")
312
+ elif os.path.exists(os.path.expanduser("~/.bash_profile")):
313
+ return os.path.expanduser("~/.bash_profile")
314
+ return None
315
+
147
316
  def _show_welcome(self):
148
317
  """Show welcome screen."""
149
318
  if self.console:
@@ -199,23 +368,13 @@ class TUI:
199
368
  result = self.tools.list_files(path=".", depth=2)
200
369
  if result.success:
201
370
  if self.console:
202
- self.console.print(Panel(result.output, title="📁 Files", border_style="blue"))
371
+ self.console.print(Panel(result.output, title="Files", border_style="blue"))
203
372
  else:
204
373
  print(f"\n{result.output}")
205
374
  else:
206
375
  print(f"Error: {result.error}")
207
376
  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}")
377
+ self._show_model_config()
219
378
  elif cmd == '/session':
220
379
  history = self.ai.get_history(self.session_name)
221
380
  if history:
@@ -233,7 +392,7 @@ class TUI:
233
392
  result = self.tools.execute_command(cmd_str)
234
393
  if result.success:
235
394
  if self.console:
236
- self.console.print(Panel(result.output, title=f"{cmd_str}", border_style="green"))
395
+ self.console.print(Panel(result.output, title=f"{cmd_str}", border_style="green"))
237
396
  else:
238
397
  print(f"\n{result.output}")
239
398
  else:
@@ -247,7 +406,7 @@ class TUI:
247
406
  lang_map = {'.py': 'python', '.js': 'javascript', '.ts': 'typescript', '.jsx': 'javascript', '.tsx': 'typescript', '.json': 'json', '.css': 'css', '.html': 'html', '.md': 'markdown'}
248
407
  lang = lang_map.get(ext, "text")
249
408
  syntax = Syntax(result.output, lang, theme="monokai", line_numbers=True)
250
- self.console.print(Panel(syntax, title=f"📄 {parts[1]}", border_style="blue"))
409
+ self.console.print(Panel(syntax, title=f"{parts[1]}", border_style="blue"))
251
410
  else:
252
411
  print(f"\n{result.output}")
253
412
  else:
@@ -273,7 +432,7 @@ class TUI:
273
432
  result = self.tools.execute_command(target)
274
433
  if result.success:
275
434
  if self.console:
276
- self.console.print(Panel(result.output, title=f"🧪 {target}", border_style="green"))
435
+ self.console.print(Panel(result.output, title=f"{target}", border_style="green"))
277
436
  else:
278
437
  print(f"\n{result.output}")
279
438
  else:
@@ -283,7 +442,7 @@ class TUI:
283
442
  self.tools.execute_command('git add -A')
284
443
  result = self.tools.execute_command(f'git commit -m "{msg}"')
285
444
  if result.success:
286
- print(f" Committed: {msg}")
445
+ print(f"[OK] Committed: {msg}")
287
446
  else:
288
447
  print(f"Error: {result.error}")
289
448
  else:
@@ -291,13 +450,35 @@ class TUI:
291
450
 
292
451
  return True
293
452
 
453
+ def _show_model_config(self):
454
+ """Show and allow changing model configuration."""
455
+ config = self._load_config()
456
+ current_provider = config.get('provider', self.ai.config.default_provider)
457
+ current_model = config.get('model', self.ai.config.default_model)
458
+
459
+ print("\n" + "=" * 60)
460
+ print(" Current Configuration")
461
+ print("=" * 60)
462
+ print(f" Provider: {current_provider}")
463
+ print(f" Model: {current_model}")
464
+ if 'ollama_url' in config:
465
+ print(f" Ollama URL: {config['ollama_url']}")
466
+ print("=" * 60)
467
+
468
+ change = input("\nChange configuration? [y/N]: ").strip().lower()
469
+ if change not in ['y', 'yes']:
470
+ return
471
+
472
+ # Re-run onboarding
473
+ self._onboarding()
474
+
294
475
  def _display_response(self, response: str):
295
476
  """Display AI response with Rich."""
296
477
  parts = response.split('```')
297
478
  for i, part in enumerate(parts):
298
479
  if i % 2 == 0:
299
480
  if part.strip():
300
- self.console.print(f"\n[bold blue]🤖 AI[/bold blue]:")
481
+ self.console.print(f"\n[bold blue]AI:[/bold blue]")
301
482
  self.console.print(Markdown(part.strip()))
302
483
  else:
303
484
  lines = part.split('\n')
@@ -314,7 +495,7 @@ class TUI:
314
495
  table.add_column("Status", style="green")
315
496
  table.add_column("Result", style="white")
316
497
  for tool in tools_used:
317
- status = "" if tool['success'] else ""
498
+ status = "OK" if tool['success'] else "FAIL"
318
499
  result = tool['output'][:60] + "..." if len(tool['output']) > 60 else tool['output']
319
500
  table.add_row(tool['tool'], status, result)
320
501
  self.console.print(table)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codrninja
3
- Version: 0.3.2
3
+ Version: 0.4.0
4
4
  Summary: AI-first coding assistant for automation
5
5
  Author: 20ZollCoder
6
6
  License: MIT
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes