Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
[project]
# This uses the PyPI-optimized README for better display on PyPI
name = "ibrahimiq-qcmd"
version = "1.0.0"
version = "1.0.15"
authors = [
{name = "Ibrahim IQ", email = "example@example.com"}
]
Expand Down
2 changes: 1 addition & 1 deletion qcmd_cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
QCMD CLI - A command-line tool that generates shell commands using Qwen2.5-Coder via Ollama.
"""

__version__ = "1.0.0"
__version__ = "1.0.15"

# Don't import modules here to avoid circular dependencies

Expand Down
37 changes: 37 additions & 0 deletions qcmd_cli/config/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""
Constants and configuration settings for QCMD.
"""

import os

# Paths
HOME_DIR = os.path.expanduser("~")
CONFIG_DIR = os.path.join(HOME_DIR, ".qcmd")
LOG_DIR = os.path.join(CONFIG_DIR, "logs")
SESSIONS_FILE = os.path.join(CONFIG_DIR, "sessions.json")

# API URLs
OLLAMA_API = os.environ.get("OLLAMA_API", "http://127.0.0.1:11434/api")

# Version
VERSION = "1.0.10"

# System prompt template for command generation
SYSTEM_PROMPT_TEMPLATE = """You are QCMD, an AI assistant specialized in Linux system administration, log analysis, and command generation.
Your goal is to provide accurate, secure, and helpful commands based on user requests.

- Generate shell commands that are valid for Linux systems.
- Prioritize safety and security in your suggestions.
- Explain complex commands when necessary.
- Always verify that commands won't harm the system.
- Suggest alternatives when relevant.

Current system context:
- User shell: {user_shell}
- OS: {os_info}
- Working directory: {cwd}

Respond only with the command(s) that would accomplish the task. Do not include explanations or markdown formatting."""

# Default number of log lines to show
DEFAULT_LOG_LINES = 100
761 changes: 522 additions & 239 deletions qcmd_cli/core/interactive_shell.py

Large diffs are not rendered by default.

85 changes: 76 additions & 9 deletions qcmd_cli/log_analysis/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,41 @@
from typing import List, Dict, Optional, Tuple, Any

# Import from local modules once they are created
# from ..config.settings import DEFAULT_MODEL
from ..config.settings import DEFAULT_MODEL
from ..ui.display import Colors

# For now, define defaults here
DEFAULT_MODEL = "llama3:latest"
def handle_log_analysis(model: str = DEFAULT_MODEL, specific_file: str = None) -> None:
"""
Handle log analysis workflow - prompting user to select log files and analyzing them.

Args:
model: Model to use for analysis
specific_file: Optional specific file to analyze
"""
if specific_file:
if os.path.exists(specific_file):
print(f"\n{Colors.GREEN}Analyzing log file: {specific_file}{Colors.END}")
analyze_log_file(specific_file, model)
else:
print(f"\n{Colors.RED}File not found: {specific_file}{Colors.END}")
return

# Import here to avoid circular imports
from .log_files import find_log_files, select_log_file

# Find log files
log_files = find_log_files()

if not log_files:
print(f"\n{Colors.YELLOW}No log files found.{Colors.END}")
print(f"Try specifying a path with: qcmd logs /path/to/logs")
return

# Let user select a log file
selected_file = select_log_file(log_files)

if selected_file:
analyze_log_file(selected_file, model)

def analyze_log_file(log_file: str, model: str = DEFAULT_MODEL, background: bool = False, analyze: bool = True) -> None:
"""
Expand All @@ -25,8 +56,25 @@ def analyze_log_file(log_file: str, model: str = DEFAULT_MODEL, background: bool
background: Whether to run in background mode
analyze: Whether to perform analysis (vs just monitoring)
"""
# Implementation will be moved from original qcmd.py
pass
# Check if file exists
if not os.path.exists(log_file):
print(f"{Colors.RED}Error: File {log_file} not found.{Colors.END}")
return

print(f"\n{Colors.CYAN}Analyzing log file: {log_file}{Colors.END}")

# Read file content
try:
content = read_large_file(log_file)
if not content:
print(f"{Colors.YELLOW}Log file is empty.{Colors.END}")
return

# Perform analysis
analyze_log_content(content, log_file, model)

except Exception as e:
print(f"{Colors.RED}Error analyzing log file: {str(e)}{Colors.END}")

def analyze_log_content(log_content: str, log_file: str, model: str = DEFAULT_MODEL) -> None:
"""
Expand All @@ -37,8 +85,21 @@ def analyze_log_content(log_content: str, log_file: str, model: str = DEFAULT_MO
log_file: Path to the log file (for reference)
model: Model to use for analysis
"""
# Implementation will be moved from original qcmd.py
pass
print(f"\n{Colors.CYAN}Analyzing log content using {model}...{Colors.END}")

# Basic implementation - in a real application, this would use an LLM via Ollama API
print(f"\n{Colors.GREEN}Log Analysis Results:{Colors.END}")
print(f"File: {log_file}")
print(f"Size: {len(log_content)} bytes")

# Count lines and errors (simple heuristic)
lines = log_content.splitlines()
error_count = sum(1 for line in lines if "error" in line.lower() or "exception" in line.lower())

print(f"Total lines: {len(lines)}")
print(f"Potential errors/exceptions: {error_count}")

# In a complete implementation, we would call the LLM to analyze the log content

def read_large_file(file_path: str, chunk_size: int = 1024 * 1024) -> str:
"""
Expand All @@ -51,5 +112,11 @@ def read_large_file(file_path: str, chunk_size: int = 1024 * 1024) -> str:
Returns:
Content of the file as a string
"""
# Implementation will be moved from original qcmd.py
pass
content = []
with open(file_path, 'r', encoding='utf-8', errors='ignore') as file:
while True:
chunk = file.read(chunk_size)
if not chunk:
break
content.append(chunk)
return "".join(content)
28 changes: 28 additions & 0 deletions qcmd_cli/ui/colors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""Terminal color codes module."""

class Colors:
"""Terminal color codes."""
RED = '\033[91m'
GREEN = '\033[92m'
YELLOW = '\033[93m'
BLUE = '\033[94m'
MAGENTA = '\033[95m'
CYAN = '\033[96m'
WHITE = '\033[97m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
END = '\033[0m'

@staticmethod
def disable():
"""Disable colors by setting all color codes to empty strings."""
Colors.RED = ''
Colors.GREEN = ''
Colors.YELLOW = ''
Colors.BLUE = ''
Colors.MAGENTA = ''
Colors.CYAN = ''
Colors.WHITE = ''
Colors.BOLD = ''
Colors.UNDERLINE = ''
Colors.END = ''
76 changes: 74 additions & 2 deletions qcmd_cli/ui/display.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import time
import re
import shutil
from typing import Dict, List, Optional
from typing import Dict, List, Optional, Any

class Colors:
"""
Expand Down Expand Up @@ -224,4 +224,76 @@ def display_help_command(current_model: str, current_temperature: float, auto_mo
- Type 'y' to execute, 'n' to reject, or edit the command
- Use !exit or Ctrl+D to quit
"""
print(help_text)
print(help_text)

def clear_screen():
"""
Clear the terminal screen.
"""
# Clear command based on OS
if os.name == 'nt': # For Windows
os.system('cls')
else: # For Linux/Mac
os.system('clear')

def display_system_status(status: Dict[str, Any]) -> None:
"""
Display detailed system status information.

Args:
status: Dictionary with system status information
"""
# Print divider line
print(f"\n{Colors.CYAN}{'-' * 80}{Colors.END}")

# System information
print(f"\n{Colors.RED}{Colors.BOLD}System Information:{Colors.END}")
print(f" {Colors.BLUE}OS:{Colors.END} {status.get('os', 'Unknown')}")
print(f" {Colors.BLUE}Python Version:{Colors.END} {status.get('python_version', 'Unknown')}")
print(f" {Colors.BLUE}QCMD Version:{Colors.END} {status.get('qcmd_version', 'Unknown')}")
print(f" {Colors.BLUE}Current Time:{Colors.END} {status.get('time', 'Unknown')}")

# Ollama information
if 'ollama' in status:
ollama = status['ollama']
print(f"\n{Colors.RED}{Colors.BOLD}Ollama Status:{Colors.END}")

# Check if Ollama is running
if ollama.get('status', '') == 'running':
print(f" {Colors.BLUE}Status:{Colors.END} {Colors.GREEN}Running{Colors.END}")
else:
print(f" {Colors.BLUE}Status:{Colors.END} {Colors.RED}Not Running{Colors.END}")
if 'error' in ollama:
print(f" {Colors.BLUE}Error:{Colors.END} {ollama['error']}")

print(f" {Colors.BLUE}API URL:{Colors.END} {ollama.get('api_url', 'Unknown')}")

# List available models
if 'models' in ollama and ollama['models']:
print(f" {Colors.BLUE}Available Models:{Colors.END}")
for model in ollama['models']:
print(f" - {model}")
elif ollama.get('status', '') == 'running':
print(f" {Colors.BLUE}Available Models:{Colors.END} No models found")

# Active monitors
if 'active_monitors' in status and status['active_monitors']:
print(f"\n{Colors.RED}{Colors.BOLD}Active Log Monitors:{Colors.END}")
for monitor in status['active_monitors']:
print(f" - {monitor}")

# Active sessions
if 'active_sessions' in status and status['active_sessions']:
print(f"\n{Colors.RED}{Colors.BOLD}Active Sessions:{Colors.END}")
for session in status['active_sessions']:
print(f" - {session}")

# Disk space
if 'disk' in status:
disk = status['disk']
print(f"\n{Colors.RED}{Colors.BOLD}Disk Space:{Colors.END}")
print(f" {Colors.BLUE}Total:{Colors.END} {disk.get('total_gb', 'Unknown')} GB")
print(f" {Colors.BLUE}Used:{Colors.END} {disk.get('used_gb', 'Unknown')} GB ({disk.get('percent_used', 'Unknown')}%)")
print(f" {Colors.BLUE}Free:{Colors.END} {disk.get('free_gb', 'Unknown')} GB")

print(f"\n{Colors.CYAN}{'-' * 80}{Colors.END}")
15 changes: 15 additions & 0 deletions qcmd_cli/utils/ollama.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
def is_ollama_running():
"""
Check if the Ollama API is running and accessible.

Returns:
bool: True if the Ollama API is running, False otherwise
"""
from qcmd_cli.config.constants import OLLAMA_API
import requests

try:
response = requests.get(f"{OLLAMA_API}/tags", timeout=2)
return response.status_code == 200
except requests.exceptions.RequestException:
return False
48 changes: 48 additions & 0 deletions qcmd_cli/utils/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,61 @@
import time
import signal
import sys
import uuid
from typing import Dict, List, Optional, Any

from ..config.settings import CONFIG_DIR

# File path for storing session info
SESSIONS_FILE = os.path.join(CONFIG_DIR, "sessions.json")

def create_session(session_info: Dict[str, Any]) -> str:
"""
Create a new session and save it to persistent storage.

Args:
session_info: Dictionary with session information

Returns:
Session ID as a string
"""
# Generate a unique session ID
session_id = str(uuid.uuid4())

# Add metadata
session_info['pid'] = os.getpid()
session_info['created_at'] = time.time()
session_info['last_activity'] = time.time()

# Save to storage
save_session(session_id, session_info)

return session_id

def update_session_activity(session_id: str) -> bool:
"""
Update the last activity timestamp for a session.

Args:
session_id: ID of the session to update

Returns:
True if successful, False otherwise
"""
try:
sessions = load_sessions()
if session_id in sessions:
sessions[session_id]['last_activity'] = time.time()

with open(SESSIONS_FILE, 'w') as f:
json.dump(sessions, f, indent=2)

return True
return False
except Exception as e:
print(f"Error updating session activity: {e}", file=sys.stderr)
return False

def save_session(session_id, session_info):
"""
Save session information to persistent storage.
Expand Down
Loading
Loading