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.
tzamuncode/__init__.py ADDED
@@ -0,0 +1,10 @@
1
+ """
2
+ TzamunCode - AI Coding Assistant
3
+
4
+ Privacy-first AI coding assistant powered by local models.
5
+ Built by Tzamun Arabia IT Co. πŸ‡ΈπŸ‡¦
6
+ """
7
+
8
+ __version__ = "0.1.0"
9
+ __author__ = "Tzamun Arabia IT Co."
10
+ __email__ = "info@tzamun.com"
@@ -0,0 +1 @@
1
+ """Agentic AI module for TzamunCode"""
@@ -0,0 +1,144 @@
1
+ """
2
+ Agentic Coder - Advanced AI coding agent with file access and tool calling
3
+ """
4
+
5
+ from typing import List, Dict, Optional, Any
6
+ from pathlib import Path
7
+ import json
8
+
9
+ from langchain.agents import initialize_agent, Tool, AgentType
10
+ from langchain.memory import ConversationBufferMemory
11
+ from langchain_community.llms import Ollama
12
+
13
+ from ..utils.file_ops import FileManager
14
+ from ..utils.project_scanner import ProjectScanner
15
+ from .tools import create_tools
16
+
17
+
18
+ class AgenticCoder:
19
+ """
20
+ Advanced AI coding agent with file access and autonomous capabilities.
21
+
22
+ Similar to Claude Code, this agent can:
23
+ - Read and write files autonomously
24
+ - Scan project structure
25
+ - Edit multiple files
26
+ - Execute git operations
27
+ - Search codebase
28
+ - Plan multi-step tasks
29
+ """
30
+
31
+ def __init__(
32
+ self,
33
+ model: str = "qwen2.5:32b",
34
+ base_url: str = "http://localhost:11434",
35
+ workspace: Optional[str] = None
36
+ ):
37
+ self.model = model
38
+ self.base_url = base_url
39
+ self.workspace = Path(workspace) if workspace else Path.cwd()
40
+
41
+ # Initialize components
42
+ self.file_manager = FileManager()
43
+ self.project_scanner = ProjectScanner(self.workspace)
44
+
45
+ # Initialize LLM
46
+ self.llm = Ollama(
47
+ model=model,
48
+ base_url=base_url,
49
+ temperature=0.7
50
+ )
51
+
52
+ # Create tools
53
+ self.tools = create_tools(
54
+ file_manager=self.file_manager,
55
+ project_scanner=self.project_scanner,
56
+ workspace=self.workspace
57
+ )
58
+
59
+ # Initialize memory
60
+ self.memory = ConversationBufferMemory(
61
+ memory_key="chat_history",
62
+ return_messages=True
63
+ )
64
+
65
+ # Initialize agent
66
+ self.agent = initialize_agent(
67
+ tools=self.tools,
68
+ llm=self.llm,
69
+ agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION,
70
+ verbose=True,
71
+ memory=self.memory,
72
+ handle_parsing_errors=True,
73
+ max_iterations=10
74
+ )
75
+
76
+ def execute(self, instruction: str) -> Dict[str, Any]:
77
+ """
78
+ Execute an instruction using the agentic AI.
79
+
80
+ The agent will autonomously:
81
+ - Scan project if needed
82
+ - Read relevant files
83
+ - Plan the changes
84
+ - Edit multiple files
85
+ - Provide summary
86
+
87
+ Args:
88
+ instruction: What to do (e.g., "Add authentication to this Flask app")
89
+
90
+ Returns:
91
+ Dict with result, files_changed, and summary
92
+ """
93
+ # Add context about workspace
94
+ context = f"""You are working in: {self.workspace}
95
+
96
+ Available tools:
97
+ - read_file: Read any file in the project
98
+ - write_file: Write/create files
99
+ - list_files: List files in directories
100
+ - search_code: Search for code patterns
101
+ - git_status: Check git status
102
+ - scan_project: Understand project structure
103
+
104
+ Instruction: {instruction}
105
+
106
+ Think step by step:
107
+ 1. Understand what needs to be done
108
+ 2. Scan project structure if needed
109
+ 3. Read relevant files
110
+ 4. Plan the changes
111
+ 5. Make the changes
112
+ 6. Summarize what was done
113
+ """
114
+
115
+ try:
116
+ result = self.agent.run(context)
117
+
118
+ return {
119
+ "success": True,
120
+ "result": result,
121
+ "workspace": str(self.workspace)
122
+ }
123
+ except Exception as e:
124
+ return {
125
+ "success": False,
126
+ "error": str(e),
127
+ "workspace": str(self.workspace)
128
+ }
129
+
130
+ def chat(self, message: str) -> str:
131
+ """
132
+ Chat with the agent while maintaining context.
133
+
134
+ Args:
135
+ message: User message
136
+
137
+ Returns:
138
+ Agent response
139
+ """
140
+ return self.agent.run(message)
141
+
142
+ def reset_memory(self):
143
+ """Reset conversation memory"""
144
+ self.memory.clear()
@@ -0,0 +1,159 @@
1
+ """
2
+ Tool definitions for the agentic coder
3
+ """
4
+
5
+ from langchain.tools import Tool
6
+ from pathlib import Path
7
+ from typing import List
8
+ import subprocess
9
+ import os
10
+
11
+ from ..utils.file_ops import FileManager
12
+ from ..utils.project_scanner import ProjectScanner
13
+
14
+
15
+ def create_tools(
16
+ file_manager: FileManager,
17
+ project_scanner: ProjectScanner,
18
+ workspace: Path
19
+ ) -> List[Tool]:
20
+ """Create tools for the agent"""
21
+
22
+ def read_file_tool(file_path: str) -> str:
23
+ """Read a file from the project"""
24
+ try:
25
+ full_path = workspace / file_path
26
+ return file_manager.read_file(str(full_path))
27
+ except Exception as e:
28
+ return f"Error reading file: {e}"
29
+
30
+ def write_file_tool(args: str) -> str:
31
+ """
32
+ Write content to a file.
33
+ Args should be: file_path|||content
34
+ """
35
+ try:
36
+ parts = args.split("|||", 1)
37
+ if len(parts) != 2:
38
+ return "Error: Use format 'file_path|||content'"
39
+
40
+ file_path, content = parts
41
+ full_path = workspace / file_path.strip()
42
+ file_manager.write_file(str(full_path), content.strip())
43
+ return f"Successfully wrote to {file_path}"
44
+ except Exception as e:
45
+ return f"Error writing file: {e}"
46
+
47
+ def list_files_tool(directory: str = ".") -> str:
48
+ """List files in a directory"""
49
+ try:
50
+ dir_path = workspace / directory
51
+ files = file_manager.list_files(str(dir_path))
52
+ return "\n".join(files[:50]) # Limit to 50 files
53
+ except Exception as e:
54
+ return f"Error listing files: {e}"
55
+
56
+ def search_code_tool(pattern: str) -> str:
57
+ """Search for code pattern in project"""
58
+ try:
59
+ result = subprocess.run(
60
+ ["grep", "-r", "-n", pattern, str(workspace)],
61
+ capture_output=True,
62
+ text=True,
63
+ timeout=10
64
+ )
65
+ output = result.stdout[:2000] # Limit output
66
+ return output if output else "No matches found"
67
+ except Exception as e:
68
+ return f"Error searching: {e}"
69
+
70
+ def git_status_tool(_: str = "") -> str:
71
+ """Check git status"""
72
+ try:
73
+ result = subprocess.run(
74
+ ["git", "status", "--short"],
75
+ cwd=workspace,
76
+ capture_output=True,
77
+ text=True,
78
+ timeout=5
79
+ )
80
+ return result.stdout if result.stdout else "No changes"
81
+ except Exception as e:
82
+ return f"Error checking git: {e}"
83
+
84
+ def scan_project_tool(_: str = "") -> str:
85
+ """Scan and understand project structure"""
86
+ try:
87
+ structure = project_scanner.scan()
88
+ return f"""Project Structure:
89
+ Files: {structure['total_files']}
90
+ Directories: {structure['total_dirs']}
91
+ Languages: {', '.join(structure['languages'])}
92
+
93
+ Key files:
94
+ {chr(10).join(structure['key_files'][:10])}
95
+ """
96
+ except Exception as e:
97
+ return f"Error scanning project: {e}"
98
+
99
+ def execute_command_tool(command: str) -> str:
100
+ """Execute a shell command (use with caution)"""
101
+ try:
102
+ # Whitelist safe commands
103
+ safe_commands = ['ls', 'pwd', 'cat', 'grep', 'find', 'git']
104
+ cmd_parts = command.split()
105
+ if not cmd_parts or cmd_parts[0] not in safe_commands:
106
+ return f"Error: Command '{cmd_parts[0] if cmd_parts else 'empty'}' not allowed"
107
+
108
+ result = subprocess.run(
109
+ command,
110
+ shell=True,
111
+ cwd=workspace,
112
+ capture_output=True,
113
+ text=True,
114
+ timeout=10
115
+ )
116
+ return result.stdout[:1000] # Limit output
117
+ except Exception as e:
118
+ return f"Error executing command: {e}"
119
+
120
+ # Create tool list
121
+ tools = [
122
+ Tool(
123
+ name="read_file",
124
+ func=read_file_tool,
125
+ description="Read a file from the project. Input: file path relative to project root"
126
+ ),
127
+ Tool(
128
+ name="write_file",
129
+ func=write_file_tool,
130
+ description="Write content to a file. Input: 'file_path|||content' (use ||| as separator)"
131
+ ),
132
+ Tool(
133
+ name="list_files",
134
+ func=list_files_tool,
135
+ description="List files in a directory. Input: directory path (default: current directory)"
136
+ ),
137
+ Tool(
138
+ name="search_code",
139
+ func=search_code_tool,
140
+ description="Search for a pattern in the codebase. Input: search pattern"
141
+ ),
142
+ Tool(
143
+ name="git_status",
144
+ func=git_status_tool,
145
+ description="Check git status. Input: empty string"
146
+ ),
147
+ Tool(
148
+ name="scan_project",
149
+ func=scan_project_tool,
150
+ description="Scan and understand project structure. Input: empty string"
151
+ ),
152
+ Tool(
153
+ name="execute_command",
154
+ func=execute_command_tool,
155
+ description="Execute a safe shell command. Input: command to execute"
156
+ ),
157
+ ]
158
+
159
+ return tools
@@ -0,0 +1 @@
1
+ """Authentication module for TzamunCode"""
@@ -0,0 +1,159 @@
1
+ """
2
+ Authentication Manager for TzamunCode
3
+ Supports username/password and API key authentication with TzamunAI
4
+ """
5
+
6
+ import requests
7
+ import json
8
+ from pathlib import Path
9
+ from typing import Optional, Dict
10
+ import hashlib
11
+ from datetime import datetime, timedelta
12
+
13
+
14
+ class AuthManager:
15
+ """Manage authentication for TzamunCode CLI"""
16
+
17
+ CONFIG_DIR = Path.home() / ".tzamuncode"
18
+ CONFIG_FILE = CONFIG_DIR / "auth.json"
19
+ TZAMUN_API_BASE = "http://192.168.1.141:3008" # Local Tzamun backend
20
+
21
+ def __init__(self):
22
+ self.CONFIG_DIR.mkdir(exist_ok=True)
23
+ self.auth_data = self.load_auth()
24
+
25
+ def load_auth(self) -> Dict:
26
+ """Load authentication data from config file"""
27
+ if self.CONFIG_FILE.exists():
28
+ try:
29
+ with open(self.CONFIG_FILE, 'r') as f:
30
+ return json.load(f)
31
+ except:
32
+ return {}
33
+ return {}
34
+
35
+ def save_auth(self, auth_data: Dict):
36
+ """Save authentication data to config file"""
37
+ with open(self.CONFIG_FILE, 'w') as f:
38
+ json.dump(auth_data, f, indent=2)
39
+ # Secure the file (only owner can read/write)
40
+ self.CONFIG_FILE.chmod(0o600)
41
+
42
+ def is_authenticated(self) -> bool:
43
+ """Check if user is authenticated"""
44
+ if not self.auth_data:
45
+ return False
46
+
47
+ # Check if token is expired
48
+ if 'expires_at' in self.auth_data:
49
+ expires_at = datetime.fromisoformat(self.auth_data['expires_at'])
50
+ if datetime.now() > expires_at:
51
+ return False
52
+
53
+ return 'api_key' in self.auth_data or 'token' in self.auth_data
54
+
55
+ def login_with_credentials(self, username: str, password: str) -> bool:
56
+ """
57
+ Login with username and password
58
+
59
+ Args:
60
+ username: TzamunAI username
61
+ password: TzamunAI password
62
+
63
+ Returns:
64
+ True if login successful
65
+ """
66
+ try:
67
+ # Call TzamunAI login API
68
+ response = requests.post(
69
+ f"{self.TZAMUN_API_BASE}/api/auth/login",
70
+ json={
71
+ "username": username,
72
+ "password": password
73
+ },
74
+ timeout=10
75
+ )
76
+
77
+ if response.status_code == 200:
78
+ data = response.json()
79
+
80
+ # Save auth token
81
+ self.auth_data = {
82
+ 'type': 'credentials',
83
+ 'username': username,
84
+ 'token': data.get('token'),
85
+ 'expires_at': (datetime.now() + timedelta(days=30)).isoformat()
86
+ }
87
+ self.save_auth(self.auth_data)
88
+ return True
89
+
90
+ return False
91
+
92
+ except Exception as e:
93
+ print(f"Login error: {e}")
94
+ return False
95
+
96
+ def login_with_api_key(self, api_key: str) -> bool:
97
+ """
98
+ Login with TzamunAI API key
99
+
100
+ Args:
101
+ api_key: TzamunAI API key (from TCHub)
102
+
103
+ Returns:
104
+ True if API key is valid
105
+ """
106
+ try:
107
+ # Validate API key with TzamunAI
108
+ response = requests.get(
109
+ f"{self.TZAMUN_API_BASE}/api/v1/tchub/validate",
110
+ headers={
111
+ 'X-API-Key': api_key
112
+ },
113
+ timeout=10
114
+ )
115
+
116
+ if response.status_code == 200:
117
+ # Save API key
118
+ self.auth_data = {
119
+ 'type': 'api_key',
120
+ 'api_key': api_key,
121
+ 'validated_at': datetime.now().isoformat()
122
+ }
123
+ self.save_auth(self.auth_data)
124
+ return True
125
+
126
+ return False
127
+
128
+ except Exception as e:
129
+ print(f"API key validation error: {e}")
130
+ return False
131
+
132
+ def logout(self):
133
+ """Logout and clear authentication data"""
134
+ self.auth_data = {}
135
+ if self.CONFIG_FILE.exists():
136
+ self.CONFIG_FILE.unlink()
137
+
138
+ def get_auth_header(self) -> Dict[str, str]:
139
+ """Get authentication header for API requests"""
140
+ if not self.is_authenticated():
141
+ return {}
142
+
143
+ if self.auth_data.get('type') == 'api_key':
144
+ return {'X-API-Key': self.auth_data['api_key']}
145
+ elif self.auth_data.get('type') == 'credentials':
146
+ return {'Authorization': f"Bearer {self.auth_data['token']}"}
147
+
148
+ return {}
149
+
150
+ def get_user_info(self) -> Dict:
151
+ """Get current user information"""
152
+ if not self.is_authenticated():
153
+ return {}
154
+
155
+ return {
156
+ 'type': self.auth_data.get('type'),
157
+ 'username': self.auth_data.get('username', 'API Key User'),
158
+ 'authenticated': True
159
+ }
@@ -0,0 +1 @@
1
+ """CLI module for TzamunCode"""
@@ -0,0 +1,131 @@
1
+ """
2
+ Agentic commands - Advanced AI operations with autonomous file access
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 Confirm
9
+ from rich.live import Live
10
+ from rich.status import Status
11
+ from typing import Optional
12
+ import sys
13
+
14
+ from ..agents.coder import AgenticCoder
15
+
16
+ console = Console()
17
+
18
+
19
+ def agentic_command(
20
+ instruction: str,
21
+ model: str = "qwen2.5:32b",
22
+ workspace: Optional[str] = None,
23
+ auto_apply: bool = False
24
+ ):
25
+ """
26
+ Execute an instruction using agentic AI with autonomous file access.
27
+
28
+ This is similar to Claude Code - the AI can:
29
+ - Scan your project
30
+ - Read files autonomously
31
+ - Write/edit multiple files
32
+ - Execute git operations
33
+ - Plan multi-step tasks
34
+
35
+ Args:
36
+ instruction: What to do (e.g., "Add authentication to this Flask app")
37
+ model: Model to use
38
+ workspace: Project directory (default: current directory)
39
+ auto_apply: Apply changes without confirmation
40
+ """
41
+ console.print(f"\n[bold blue]TzamunCode Agentic Mode[/bold blue]")
42
+ console.print(f"[dim]Model: {model}[/dim]")
43
+ console.print(f"[dim]Workspace: {workspace or 'current directory'}[/dim]\n")
44
+
45
+ console.print(Panel(
46
+ f"[bold]Instruction:[/bold]\n{instruction}\n\n"
47
+ "[dim]The AI will autonomously:\n"
48
+ "β€’ Scan project structure\n"
49
+ "β€’ Read relevant files\n"
50
+ "β€’ Plan changes\n"
51
+ "β€’ Edit multiple files\n"
52
+ "β€’ Provide summary[/dim]",
53
+ border_style="blue"
54
+ ))
55
+
56
+ if not auto_apply:
57
+ if not Confirm.ask("\n[bold]Allow AI to access and modify files?[/bold]"):
58
+ console.print("[dim]Cancelled[/dim]")
59
+ return
60
+
61
+ # Initialize agentic coder
62
+ with console.status("[bold blue]Initializing AI agent...", spinner="dots"):
63
+ try:
64
+ agent = AgenticCoder(
65
+ model=model,
66
+ workspace=workspace
67
+ )
68
+ except Exception as e:
69
+ console.print(f"[bold red]Error:[/bold red] Failed to initialize agent: {e}")
70
+ return
71
+
72
+ console.print("[bold green]βœ“[/bold green] Agent initialized\n")
73
+
74
+ # Execute instruction
75
+ console.print("[bold blue]AI is working...[/bold blue]\n")
76
+
77
+ try:
78
+ with Live(console=console, refresh_per_second=4) as live:
79
+ result = agent.execute(instruction)
80
+
81
+ if result['success']:
82
+ console.print("\n[bold green]βœ“ Task completed![/bold green]\n")
83
+ console.print(Panel(
84
+ Markdown(result['result']),
85
+ title="[bold blue]Summary[/bold blue]",
86
+ border_style="green"
87
+ ))
88
+ else:
89
+ console.print(f"\n[bold red]βœ— Error:[/bold red] {result.get('error', 'Unknown error')}")
90
+
91
+ except KeyboardInterrupt:
92
+ console.print("\n[dim]Interrupted by user[/dim]")
93
+ except Exception as e:
94
+ console.print(f"\n[bold red]Error:[/bold red] {e}")
95
+
96
+
97
+ def project_command(workspace: Optional[str] = None):
98
+ """
99
+ Analyze and understand the project structure.
100
+
101
+ Args:
102
+ workspace: Project directory (default: current directory)
103
+ """
104
+ from ..utils.project_scanner import ProjectScanner
105
+ from pathlib import Path
106
+
107
+ workspace_path = Path(workspace) if workspace else Path.cwd()
108
+
109
+ console.print(f"\n[bold blue]Scanning Project:[/bold blue] {workspace_path}\n")
110
+
111
+ with console.status("[bold blue]Analyzing project...", spinner="dots"):
112
+ scanner = ProjectScanner(workspace_path)
113
+ structure = scanner.scan()
114
+ project_type = scanner.get_project_type()
115
+
116
+ # Display results
117
+ console.print(Panel.fit(
118
+ f"""[bold]Project Analysis[/bold]
119
+
120
+ [bold cyan]Type:[/bold cyan] {project_type}
121
+ [bold cyan]Total Files:[/bold cyan] {structure['total_files']}
122
+ [bold cyan]Total Directories:[/bold cyan] {structure['total_dirs']}
123
+ [bold cyan]Languages:[/bold cyan] {', '.join(structure['languages']) if structure['languages'] else 'None detected'}
124
+
125
+ [bold]Key Files:[/bold]
126
+ {chr(10).join(' β€’ ' + f for f in structure['key_files'][:15])}
127
+ {f" ... and {len(structure['key_files']) - 15} more" if len(structure['key_files']) > 15 else ""}
128
+ """,
129
+ border_style="blue",
130
+ title="[bold blue]Project Structure[/bold blue]"
131
+ ))