tzamuncode 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,125 @@
1
+ """
2
+ Authentication commands for TzamunCode CLI
3
+ """
4
+
5
+ from rich.console import Console
6
+ from rich.panel import Panel
7
+ from rich.prompt import Prompt, Confirm
8
+ from rich.table import Table
9
+ import getpass
10
+
11
+ from ..auth.auth_manager import AuthManager
12
+
13
+ console = Console()
14
+
15
+
16
+ def login_command():
17
+ """Interactive login command"""
18
+ auth = AuthManager()
19
+
20
+ # Check if already logged in
21
+ if auth.is_authenticated():
22
+ user_info = auth.get_user_info()
23
+ console.print(f"\n[yellow]Already logged in as:[/yellow] [cyan]{user_info['username']}[/cyan]")
24
+
25
+ if not Confirm.ask("\n[bold]Logout and login again?[/bold]"):
26
+ return
27
+
28
+ auth.logout()
29
+ console.print("[dim]Logged out[/dim]\n")
30
+
31
+ # Show login options
32
+ console.print(Panel.fit(
33
+ """[bold cyan]TzamunCode Authentication[/bold cyan]
34
+
35
+ Choose login method:
36
+ [bold]1.[/bold] Username & Password (TzamunAI account)
37
+ [bold]2.[/bold] API Key (from TzamunAI TCHub)
38
+ """,
39
+ border_style="blue",
40
+ title="[bold blue]Login[/bold blue]"
41
+ ))
42
+
43
+ choice = Prompt.ask("\n[bold]Select option[/bold]", choices=["1", "2"], default="2")
44
+
45
+ if choice == "1":
46
+ # Username/Password login
47
+ console.print("\n[bold blue]Login with TzamunAI Account[/bold blue]\n")
48
+
49
+ username = Prompt.ask("[bold]Username[/bold]")
50
+ password = getpass.getpass("Password: ")
51
+
52
+ console.print("\n[dim]Authenticating...[/dim]")
53
+
54
+ if auth.login_with_credentials(username, password):
55
+ console.print(f"\n[bold green]✓ Successfully logged in as {username}![/bold green]\n")
56
+ else:
57
+ console.print("\n[bold red]✗ Login failed. Please check your credentials.[/bold red]\n")
58
+
59
+ else:
60
+ # API Key login
61
+ console.print("\n[bold blue]Login with API Key[/bold blue]\n")
62
+ console.print("[dim]Get your API key from: https://app.tzamun.ai/admin (API Config tab)[/dim]\n")
63
+
64
+ api_key = Prompt.ask("[bold]API Key[/bold]", password=True)
65
+
66
+ console.print("\n[dim]Validating API key...[/dim]")
67
+
68
+ if auth.login_with_api_key(api_key):
69
+ console.print("\n[bold green]✓ Successfully authenticated with API key![/bold green]\n")
70
+ else:
71
+ console.print("\n[bold red]✗ Invalid API key. Please check and try again.[/bold red]\n")
72
+
73
+
74
+ def logout_command():
75
+ """Logout command"""
76
+ auth = AuthManager()
77
+
78
+ if not auth.is_authenticated():
79
+ console.print("\n[yellow]Not logged in[/yellow]\n")
80
+ return
81
+
82
+ user_info = auth.get_user_info()
83
+
84
+ if Confirm.ask(f"\n[bold]Logout from {user_info['username']}?[/bold]"):
85
+ auth.logout()
86
+ console.print("\n[bold green]✓ Logged out successfully[/bold green]\n")
87
+
88
+
89
+ def whoami_command():
90
+ """Show current user info"""
91
+ auth = AuthManager()
92
+
93
+ if not auth.is_authenticated():
94
+ console.print("\n[yellow]Not logged in[/yellow]")
95
+ console.print("[dim]Run 'tzamuncode login' to authenticate[/dim]\n")
96
+ return
97
+
98
+ user_info = auth.get_user_info()
99
+
100
+ table = Table(title="Current User", border_style="blue")
101
+ table.add_column("Property", style="cyan")
102
+ table.add_column("Value", style="yellow")
103
+
104
+ table.add_row("Status", "✓ Authenticated")
105
+ table.add_row("Type", user_info['type'].replace('_', ' ').title())
106
+ table.add_row("User", user_info['username'])
107
+
108
+ console.print()
109
+ console.print(table)
110
+ console.print()
111
+
112
+
113
+ def require_auth(func):
114
+ """Decorator to require authentication before running a command"""
115
+ def wrapper(*args, **kwargs):
116
+ auth = AuthManager()
117
+
118
+ if not auth.is_authenticated():
119
+ console.print("\n[bold red]✗ Authentication required[/bold red]")
120
+ console.print("[dim]Run 'tzamuncode login' to authenticate[/dim]\n")
121
+ return
122
+
123
+ return func(*args, **kwargs)
124
+
125
+ return wrapper
@@ -0,0 +1,203 @@
1
+ """
2
+ Command implementations for TzamunCode CLI
3
+ """
4
+
5
+ from rich.console import Console
6
+ from rich.panel import Panel
7
+ from rich.markdown import Markdown
8
+ from rich.prompt import Prompt, Confirm
9
+ from rich.syntax import Syntax
10
+ from rich.live import Live
11
+ from rich.spinner import Spinner
12
+ from typing import Optional
13
+ import sys
14
+
15
+ from ..models.ollama import OllamaClient
16
+ from ..utils.file_ops import FileManager
17
+
18
+ console = Console()
19
+
20
+ def chat_command(model: str, system: Optional[str] = None):
21
+ """Interactive chat with AI"""
22
+ client = OllamaClient(model=model)
23
+
24
+ console.print(Panel.fit(
25
+ f"[bold blue]TzamunCode Chat[/bold blue]\n\n"
26
+ f"Model: [cyan]{model}[/cyan]\n"
27
+ f"Type [bold]'exit'[/bold] or [bold]'quit'[/bold] to end the session",
28
+ border_style="blue"
29
+ ))
30
+
31
+ conversation_history = []
32
+
33
+ if system:
34
+ conversation_history.append({"role": "system", "content": system})
35
+
36
+ while True:
37
+ try:
38
+ user_input = Prompt.ask("\n[bold green]You[/bold green]")
39
+
40
+ if user_input.lower() in ['exit', 'quit', 'q']:
41
+ console.print("[dim]Goodbye! 👋[/dim]")
42
+ break
43
+
44
+ conversation_history.append({"role": "user", "content": user_input})
45
+
46
+ console.print("\n[bold blue]TzamunCode[/bold blue]:", end=" ")
47
+
48
+ # Stream response
49
+ response = ""
50
+ for chunk in client.chat_stream(conversation_history):
51
+ response += chunk
52
+ console.print(chunk, end="")
53
+
54
+ console.print() # New line after response
55
+
56
+ conversation_history.append({"role": "assistant", "content": response})
57
+
58
+ except KeyboardInterrupt:
59
+ console.print("\n[dim]Interrupted. Goodbye! 👋[/dim]")
60
+ break
61
+ except Exception as e:
62
+ console.print(f"\n[bold red]Error:[/bold red] {e}")
63
+
64
+ def generate_command(prompt: str, model: str, output: Optional[str] = None):
65
+ """Generate code based on prompt"""
66
+ client = OllamaClient(model=model)
67
+ file_manager = FileManager()
68
+
69
+ console.print(f"\n[bold blue]Generating:[/bold blue] {prompt}")
70
+ console.print(f"[dim]Model: {model}[/dim]\n")
71
+
72
+ # Create generation prompt
73
+ full_prompt = f"""Generate code for: {prompt}
74
+
75
+ Requirements:
76
+ - Write clean, well-documented code
77
+ - Include error handling
78
+ - Follow best practices
79
+ - Add comments explaining complex logic
80
+
81
+ Generate the complete, working code:"""
82
+
83
+ with console.status("[bold blue]Generating code...", spinner="dots"):
84
+ response = client.generate(full_prompt)
85
+
86
+ # Extract code from response
87
+ code = response.strip()
88
+
89
+ # Display generated code
90
+ console.print("\n[bold green]Generated Code:[/bold green]\n")
91
+
92
+ # Try to detect language for syntax highlighting
93
+ language = "python" # Default
94
+ if output:
95
+ if output.endswith('.js'):
96
+ language = "javascript"
97
+ elif output.endswith('.go'):
98
+ language = "go"
99
+ elif output.endswith('.rs'):
100
+ language = "rust"
101
+
102
+ syntax = Syntax(code, language, theme="monokai", line_numbers=True)
103
+ console.print(syntax)
104
+
105
+ # Save to file if output specified
106
+ if output:
107
+ if Confirm.ask(f"\n[bold]Save to {output}?[/bold]"):
108
+ file_manager.write_file(output, code)
109
+ console.print(f"[bold green]✓[/bold green] Saved to {output}")
110
+ else:
111
+ if Confirm.ask("\n[bold]Save to file?[/bold]"):
112
+ filename = Prompt.ask("Filename")
113
+ file_manager.write_file(filename, code)
114
+ console.print(f"[bold green]✓[/bold green] Saved to {filename}")
115
+
116
+ def edit_command(file_path: str, instruction: str, model: str, apply: bool = False):
117
+ """Edit a file using AI"""
118
+ client = OllamaClient(model=model)
119
+ file_manager = FileManager()
120
+
121
+ # Read current file
122
+ try:
123
+ current_content = file_manager.read_file(file_path)
124
+ except FileNotFoundError:
125
+ console.print(f"[bold red]Error:[/bold red] File not found: {file_path}")
126
+ return
127
+
128
+ console.print(f"\n[bold blue]Editing:[/bold blue] {file_path}")
129
+ console.print(f"[bold blue]Instruction:[/bold blue] {instruction}")
130
+ console.print(f"[dim]Model: {model}[/dim]\n")
131
+
132
+ # Create edit prompt
133
+ edit_prompt = f"""You are editing the file: {file_path}
134
+
135
+ Current content:
136
+ ```
137
+ {current_content}
138
+ ```
139
+
140
+ Instruction: {instruction}
141
+
142
+ Provide the complete modified file content. Only output the code, no explanations:"""
143
+
144
+ with console.status("[bold blue]Generating changes...", spinner="dots"):
145
+ new_content = client.generate(edit_prompt)
146
+
147
+ # Show diff
148
+ diff = file_manager.show_diff(file_path, new_content)
149
+
150
+ if diff:
151
+ console.print("\n[bold yellow]Changes:[/bold yellow]\n")
152
+ console.print(diff)
153
+
154
+ # Apply changes
155
+ if apply or Confirm.ask("\n[bold]Apply these changes?[/bold]"):
156
+ file_manager.write_file(file_path, new_content)
157
+ console.print(f"[bold green]✓[/bold green] Changes applied to {file_path}")
158
+ else:
159
+ console.print("[dim]Changes not applied[/dim]")
160
+ else:
161
+ console.print("[dim]No changes needed[/dim]")
162
+
163
+ def explain_command(file_path: str, model: str, detailed: bool = False):
164
+ """Explain code in a file"""
165
+ client = OllamaClient(model=model)
166
+ file_manager = FileManager()
167
+
168
+ # Read file
169
+ try:
170
+ content = file_manager.read_file(file_path)
171
+ except FileNotFoundError:
172
+ console.print(f"[bold red]Error:[/bold red] File not found: {file_path}")
173
+ return
174
+
175
+ console.print(f"\n[bold blue]Explaining:[/bold blue] {file_path}")
176
+ console.print(f"[dim]Model: {model}[/dim]\n")
177
+
178
+ # Create explanation prompt
179
+ detail_level = "detailed" if detailed else "concise"
180
+ explain_prompt = f"""Explain this code in a {detail_level} way:
181
+
182
+ File: {file_path}
183
+ ```
184
+ {content}
185
+ ```
186
+
187
+ Provide a clear explanation covering:
188
+ - What the code does
189
+ - Key functions/classes
190
+ - Important logic
191
+ - Potential improvements (if detailed)
192
+
193
+ Explanation:"""
194
+
195
+ with console.status("[bold blue]Analyzing code...", spinner="dots"):
196
+ explanation = client.generate(explain_prompt)
197
+
198
+ # Display explanation
199
+ console.print(Panel(
200
+ Markdown(explanation),
201
+ title=f"[bold blue]Explanation: {file_path}[/bold blue]",
202
+ border_style="blue"
203
+ ))
@@ -0,0 +1,312 @@
1
+ """
2
+ Enhanced interactive chat with beautiful UI and slash commands
3
+ """
4
+
5
+ from rich.console import Console
6
+ from rich.panel import Panel
7
+ from rich.table import Table
8
+ from rich.prompt import Prompt
9
+ from rich.markdown import Markdown
10
+ from rich.layout import Layout
11
+ from rich.live import Live
12
+ from rich.text import Text
13
+ from typing import Optional, List, Dict
14
+ import sys
15
+
16
+ from ..models.ollama import OllamaClient
17
+
18
+ console = Console()
19
+
20
+
21
+ class EnhancedChat:
22
+ """Enhanced chat interface with Claude Code-like UI"""
23
+
24
+ ASCII_LOGO = """
25
+ ╔╦╗╔═╗╔═╗╔╦╗╦ ╦╔╗╔╔═╗╔═╗╔╦╗╔═╗
26
+ ║ ╔═╝╠═╣║║║║ ║║║║║ ║ ║ ║║╣
27
+ ╩ ╚═╝╩ ╩╩ ╩╚═╝╝╚╝╚═╝╚═╝═╩╝╚═╝
28
+ """
29
+
30
+ SLASH_COMMANDS = {
31
+ '/help': 'Show available commands',
32
+ '/models': 'List and switch models',
33
+ '/settings': 'Show settings',
34
+ '/clear': 'Clear conversation history',
35
+ '/save': 'Save conversation',
36
+ '/load': 'Load conversation',
37
+ '/exit': 'Exit chat',
38
+ '/quit': 'Exit chat',
39
+ }
40
+
41
+ def __init__(self, model: str = "qwen2.5:32b", system: Optional[str] = None):
42
+ self.model = model
43
+ self.system = system
44
+ self.client = OllamaClient(model=model)
45
+ self.conversation_history = []
46
+ self.available_models = []
47
+ self.settings = {
48
+ 'temperature': 0.7,
49
+ 'streaming': True,
50
+ 'show_thinking': False,
51
+ }
52
+
53
+ # Load available models
54
+ try:
55
+ self.available_models = self.client.list_models()
56
+ except:
57
+ self.available_models = [model]
58
+
59
+
60
+ def show_command_menu(self):
61
+ """Show command menu when user types '/'"""
62
+ console.print()
63
+
64
+ # Professional command menu
65
+ table = Table(
66
+ title="[bold cyan]Available Commands[/bold cyan]",
67
+ border_style="blue",
68
+ show_header=True,
69
+ header_style="bold white on blue",
70
+ title_style="bold cyan"
71
+ )
72
+ table.add_column("Command", style="bold cyan", no_wrap=True, width=20)
73
+ table.add_column("Description", style="white")
74
+
75
+ for cmd, desc in self.SLASH_COMMANDS.items():
76
+ table.add_row(cmd, desc)
77
+
78
+ console.print(table)
79
+ console.print()
80
+
81
+ def show_help(self):
82
+ """Show help with all commands"""
83
+ self.show_command_menu()
84
+
85
+ def show_models(self) -> Optional[str]:
86
+ """Show model selector and return selected model"""
87
+ console.print()
88
+
89
+ # Professional model selection table
90
+ table = Table(
91
+ title="[bold cyan]Available Models[/bold cyan]",
92
+ border_style="blue",
93
+ show_header=True,
94
+ header_style="bold white on blue"
95
+ )
96
+ table.add_column("#", style="dim", width=4)
97
+ table.add_column("Model Name", style="cyan")
98
+ table.add_column("Status", style="green", width=12)
99
+
100
+ for idx, model_name in enumerate(self.available_models, 1):
101
+ status = "✓ Active" if model_name == self.model else ""
102
+ table.add_row(str(idx), model_name, status)
103
+
104
+ console.print(table)
105
+ console.print()
106
+
107
+ choice = Prompt.ask(
108
+ "[bold blue]Select model number[/bold blue] [dim](or Enter to cancel)[/dim]",
109
+ default=""
110
+ )
111
+
112
+ if choice and choice.isdigit():
113
+ idx = int(choice) - 1
114
+ if 0 <= idx < len(self.available_models):
115
+ new_model = self.available_models[idx]
116
+ self.model = new_model
117
+ self.client = OllamaClient(model=new_model)
118
+ console.print(f"\n[bold green]✓[/bold green] Switched to [cyan]{new_model}[/cyan]\n")
119
+ return new_model
120
+
121
+ console.print("[dim]Cancelled[/dim]\n")
122
+ return None
123
+
124
+ def show_settings(self):
125
+ """Show current settings"""
126
+ from .. import __version__
127
+
128
+ console.print()
129
+
130
+ # Professional settings panel
131
+ table = Table(
132
+ title="[bold cyan]Current Settings[/bold cyan]",
133
+ border_style="blue",
134
+ show_header=True,
135
+ header_style="bold white on blue"
136
+ )
137
+ table.add_column("Setting", style="bold cyan", width=20)
138
+ table.add_column("Value", style="yellow")
139
+
140
+ table.add_row("Version", __version__)
141
+ table.add_row("Company", "Tzamun Arabia IT Co. 🇸🇦")
142
+ table.add_row("Model", self.model)
143
+ table.add_row("Available Models", str(len(self.available_models)))
144
+ table.add_row("Temperature", str(self.settings['temperature']))
145
+ table.add_row("Streaming", "✓ Enabled" if self.settings['streaming'] else "✗ Disabled")
146
+
147
+ console.print(table)
148
+ console.print()
149
+
150
+ def handle_slash_command(self, command: str) -> bool:
151
+ """
152
+ Handle slash commands
153
+
154
+ Returns:
155
+ True if should continue chat, False if should exit
156
+ """
157
+ command = command.lower().strip()
158
+
159
+ if command in ['/exit', '/quit']:
160
+ return False
161
+
162
+ elif command == '/help':
163
+ self.show_help()
164
+
165
+ elif command == '/models':
166
+ self.show_models()
167
+
168
+ elif command == '/settings':
169
+ self.show_settings()
170
+
171
+ elif command == '/clear':
172
+ self.conversation_history = []
173
+ self.show_header()
174
+ console.print("[bold green]✓[/bold green] Conversation cleared\n")
175
+
176
+ elif command == '/save':
177
+ console.print("[dim]Save feature coming soon...[/dim]\n")
178
+
179
+ elif command == '/load':
180
+ console.print("[dim]Load feature coming soon...[/dim]\n")
181
+
182
+ else:
183
+ console.print(f"[bold red]Unknown command:[/bold red] {command}")
184
+ console.print("[dim]Type '/help' to see available commands[/dim]\n")
185
+
186
+ return True
187
+
188
+ def format_user_message(self, message: str):
189
+ """Format user message"""
190
+ console.print(f"\n[bold green]You[/bold green] [dim]›[/dim] {message}")
191
+
192
+ def format_ai_response(self, response: str):
193
+ """Format AI response"""
194
+ console.print(f"\n[bold blue]TzamunCode[/bold blue] [dim]›[/dim]")
195
+
196
+ # Try to render as markdown if it looks like code
197
+ if '```' in response or response.strip().startswith('#'):
198
+ console.print(Markdown(response))
199
+ else:
200
+ console.print(response)
201
+
202
+ console.print()
203
+
204
+ def run(self):
205
+ """Run the enhanced chat interface"""
206
+ # Professional header with complete information
207
+ from .. import __version__
208
+
209
+ console.print()
210
+ console.print("─" * console.width, style="dim")
211
+ console.print()
212
+
213
+ # ASCII Logo
214
+ console.print("[bold cyan]╔╦╗╔═╗╔═╗╔╦╗╦ ╦╔╗╔╔═╗╔═╗╔╦╗╔═╗[/bold cyan]")
215
+ console.print("[bold cyan] ║ ╔═╝╠═╣║║║║ ║║║║║ ║ ║ ║║╣[/bold cyan]")
216
+ console.print("[bold cyan] ╩ ╚═╝╩ ╩╩ ╩╚═╝╝╚╝╚═╝╚═╝═╩╝╚═╝[/bold cyan]")
217
+
218
+ console.print()
219
+
220
+ # Professional info panel
221
+ info_panel = Panel.fit(
222
+ f"""[bold]AI Coding Assistant[/bold]
223
+
224
+ [cyan]Version:[/cyan] {__version__}
225
+ [cyan]Company:[/cyan] Tzamun Arabia IT Co. 🇸🇦
226
+ [cyan]Model:[/cyan] {self.model}
227
+ [cyan]Models Available:[/cyan] {len(self.available_models)}
228
+
229
+ [dim]Type '/help' for commands • Type '?' for shortcuts[/dim]""",
230
+ border_style="blue",
231
+ padding=(0, 2)
232
+ )
233
+ console.print(info_panel)
234
+ console.print()
235
+ console.print("─" * console.width, style="dim")
236
+ console.print()
237
+
238
+ # Add system message if provided
239
+ if self.system:
240
+ self.conversation_history.append({"role": "system", "content": self.system})
241
+
242
+ while True:
243
+ try:
244
+ # Simple prompt
245
+ console.print()
246
+ user_input = Prompt.ask("[dim]›[/dim]", default="")
247
+
248
+ if not user_input or not user_input.strip():
249
+ continue
250
+
251
+ # Check for exit commands
252
+ if user_input.lower() in ['exit', 'quit', 'q']:
253
+ console.print("\n[dim]Goodbye! 👋[/dim]\n")
254
+ break
255
+
256
+ # Check if user just typed '/' - show command menu
257
+ if user_input.strip() == '/':
258
+ self.show_command_menu()
259
+ continue
260
+
261
+ # Check for slash commands
262
+ if user_input.startswith('/'):
263
+ should_continue = self.handle_slash_command(user_input)
264
+ if not should_continue:
265
+ console.print("\n[dim]Goodbye! 👋[/dim]\n")
266
+ break
267
+ continue
268
+
269
+ # Format and show user message
270
+ self.format_user_message(user_input)
271
+
272
+ # Add to conversation history
273
+ self.conversation_history.append({"role": "user", "content": user_input})
274
+
275
+ # Get AI response
276
+ if self.settings['streaming']:
277
+ # Stream response
278
+ console.print(f"\n[bold blue]TzamunCode[/bold blue] [dim]›[/dim] ", end="")
279
+
280
+ response = ""
281
+ for chunk in self.client.chat_stream(self.conversation_history):
282
+ response += chunk
283
+ console.print(chunk, end="")
284
+
285
+ console.print("\n")
286
+ else:
287
+ # Non-streaming response
288
+ with console.status("[bold blue]Thinking...", spinner="dots"):
289
+ response = self.client.chat(self.conversation_history)
290
+
291
+ self.format_ai_response(response)
292
+
293
+ # Add to conversation history
294
+ self.conversation_history.append({"role": "assistant", "content": response})
295
+
296
+ except KeyboardInterrupt:
297
+ console.print("\n\n[dim]Interrupted. Type 'exit' to quit or continue chatting.[/dim]\n")
298
+ continue
299
+
300
+ except EOFError:
301
+ console.print("\n[dim]Goodbye! 👋[/dim]\n")
302
+ break
303
+
304
+ except Exception as e:
305
+ console.print(f"\n[bold red]Error:[/bold red] {e}\n")
306
+ continue
307
+
308
+
309
+ def enhanced_chat_command(model: str, system: Optional[str] = None):
310
+ """Run enhanced chat interface"""
311
+ chat = EnhancedChat(model=model, system=system)
312
+ chat.run()