hashcli 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.
- hashcli/__init__.py +29 -0
- hashcli/command_proxy.py +240 -0
- hashcli/commands/__init__.py +17 -0
- hashcli/commands/clear.py +70 -0
- hashcli/commands/config.py +124 -0
- hashcli/commands/fix.py +54 -0
- hashcli/commands/help.py +89 -0
- hashcli/commands/ls.py +88 -0
- hashcli/commands/model.py +143 -0
- hashcli/config.py +348 -0
- hashcli/history.py +451 -0
- hashcli/llm_handler.py +340 -0
- hashcli/main.py +321 -0
- hashcli/providers/__init__.py +13 -0
- hashcli/providers/anthropic_provider.py +157 -0
- hashcli/providers/base.py +53 -0
- hashcli/providers/google_provider.py +179 -0
- hashcli/providers/openai_provider.py +111 -0
- hashcli/tools/__init__.py +16 -0
- hashcli/tools/base.py +60 -0
- hashcli/tools/code_analysis.py +339 -0
- hashcli/tools/filesystem.py +205 -0
- hashcli/tools/shell.py +128 -0
- hashcli/tools/web_search.py +85 -0
- hashcli-0.1.0.dist-info/METADATA +52 -0
- hashcli-0.1.0.dist-info/RECORD +30 -0
- hashcli-0.1.0.dist-info/WHEEL +5 -0
- hashcli-0.1.0.dist-info/entry_points.txt +2 -0
- hashcli-0.1.0.dist-info/licenses/LICENSE +21 -0
- hashcli-0.1.0.dist-info/top_level.txt +1 -0
hashcli/__init__.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Hash (HAcker SHell) - Intelligent CLI system with dual-mode functionality.
|
|
2
|
+
|
|
3
|
+
This package provides a modern CLI that combines LLM conversational assistance
|
|
4
|
+
with command proxy functionality, operating in two distinct modes:
|
|
5
|
+
|
|
6
|
+
- LLM Chat Mode: Natural language queries for intelligent assistance
|
|
7
|
+
- Command Proxy Mode: Slash-prefixed commands for direct functionality
|
|
8
|
+
|
|
9
|
+
The system is designed for cross-platform compatibility and extensibility,
|
|
10
|
+
supporting multiple LLM providers and built-in command extensions.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from .command_proxy import CommandProxy
|
|
14
|
+
from .config import HashConfig
|
|
15
|
+
from .history import ConversationHistory
|
|
16
|
+
from .llm_handler import LLMHandler
|
|
17
|
+
from .main import app
|
|
18
|
+
|
|
19
|
+
__version__ = "0.1.0"
|
|
20
|
+
__author__ = "Hash CLI Team"
|
|
21
|
+
__email__ = "team@hashcli.dev"
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"app",
|
|
25
|
+
"HashConfig",
|
|
26
|
+
"LLMHandler",
|
|
27
|
+
"CommandProxy",
|
|
28
|
+
"ConversationHistory",
|
|
29
|
+
]
|
hashcli/command_proxy.py
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""Command proxy system for handling slash-prefixed commands."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import platform
|
|
5
|
+
import shlex
|
|
6
|
+
import subprocess
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
from typing import Any, Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
|
|
12
|
+
from .config import HashConfig
|
|
13
|
+
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Command(ABC):
|
|
18
|
+
"""Abstract base class for all commands."""
|
|
19
|
+
|
|
20
|
+
@abstractmethod
|
|
21
|
+
def execute(self, args: List[str], config: HashConfig) -> str:
|
|
22
|
+
"""Execute the command with given arguments."""
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
@abstractmethod
|
|
26
|
+
def get_help(self) -> str:
|
|
27
|
+
"""Get help text for this command."""
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
def validate_args(self, args: List[str]) -> bool:
|
|
31
|
+
"""Validate command arguments. Override if needed."""
|
|
32
|
+
return True
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class CommandProxy:
|
|
36
|
+
"""Main command proxy that routes slash commands to their handlers."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, config: HashConfig):
|
|
39
|
+
self.config = config
|
|
40
|
+
self.commands = self._register_commands()
|
|
41
|
+
|
|
42
|
+
def execute(self, command_line: str) -> str:
|
|
43
|
+
"""Execute a slash command."""
|
|
44
|
+
# Remove leading slash and parse command
|
|
45
|
+
command_line = command_line.lstrip().lstrip("/")
|
|
46
|
+
|
|
47
|
+
if not command_line:
|
|
48
|
+
return "No command specified. Use /help for available commands."
|
|
49
|
+
|
|
50
|
+
# Parse command and arguments safely
|
|
51
|
+
try:
|
|
52
|
+
parts = shlex.split(command_line)
|
|
53
|
+
except ValueError as e:
|
|
54
|
+
return f"Error parsing command: {e}"
|
|
55
|
+
|
|
56
|
+
if not parts:
|
|
57
|
+
return "No command specified. Use /help for available commands."
|
|
58
|
+
|
|
59
|
+
cmd = parts[0]
|
|
60
|
+
args = parts[1:] if len(parts) > 1 else []
|
|
61
|
+
|
|
62
|
+
# Check if command exists
|
|
63
|
+
if cmd not in self.commands:
|
|
64
|
+
return f"Unknown command: /{cmd}\nUse /help for available commands."
|
|
65
|
+
|
|
66
|
+
# Get command handler
|
|
67
|
+
handler = self.commands[cmd]
|
|
68
|
+
|
|
69
|
+
# Validate arguments
|
|
70
|
+
if not handler.validate_args(args):
|
|
71
|
+
return f"Invalid arguments for /{cmd}\n{handler.get_help()}"
|
|
72
|
+
|
|
73
|
+
# Execute command
|
|
74
|
+
try:
|
|
75
|
+
return handler.execute(args, self.config)
|
|
76
|
+
except Exception as e:
|
|
77
|
+
if self.config.show_debug:
|
|
78
|
+
import traceback
|
|
79
|
+
|
|
80
|
+
return f"Command execution error: {e}\n{traceback.format_exc()}"
|
|
81
|
+
else:
|
|
82
|
+
return f"Command execution error: {e}"
|
|
83
|
+
|
|
84
|
+
def _register_commands(self) -> Dict[str, Command]:
|
|
85
|
+
"""Register all available commands."""
|
|
86
|
+
from .commands import (
|
|
87
|
+
ClearCommand,
|
|
88
|
+
ConfigCommand,
|
|
89
|
+
FixCommand,
|
|
90
|
+
HelpCommand,
|
|
91
|
+
LSCommand,
|
|
92
|
+
ModelCommand,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
"ls": LSCommand(),
|
|
97
|
+
"dir": LSCommand(), # Windows alias
|
|
98
|
+
"clear": ClearCommand(),
|
|
99
|
+
"model": ModelCommand(),
|
|
100
|
+
"fix": FixCommand(),
|
|
101
|
+
"help": HelpCommand(),
|
|
102
|
+
"config": ConfigCommand(),
|
|
103
|
+
"history": HistoryCommand(),
|
|
104
|
+
"exit": ExitCommand(),
|
|
105
|
+
"quit": ExitCommand(),
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
def get_available_commands(self) -> List[str]:
|
|
109
|
+
"""Get list of available command names."""
|
|
110
|
+
return sorted(self.commands.keys())
|
|
111
|
+
|
|
112
|
+
def get_command_help(self, command: str) -> Optional[str]:
|
|
113
|
+
"""Get help for a specific command."""
|
|
114
|
+
if command in self.commands:
|
|
115
|
+
return self.commands[command].get_help()
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class SystemCommand(Command):
|
|
120
|
+
"""Base class for system commands that execute shell operations."""
|
|
121
|
+
|
|
122
|
+
def execute_system_command(self, cmd_args: List[str], config: HashConfig) -> str:
|
|
123
|
+
"""Execute a system command with security checks."""
|
|
124
|
+
|
|
125
|
+
# Security check: validate command against blocked list
|
|
126
|
+
cmd_str = " ".join(cmd_args)
|
|
127
|
+
for blocked in config.blocked_commands:
|
|
128
|
+
if blocked.lower() in cmd_str.lower():
|
|
129
|
+
return f"Blocked command detected: {blocked}"
|
|
130
|
+
|
|
131
|
+
# Security check: validate against allowed list if configured
|
|
132
|
+
if config.allowed_commands:
|
|
133
|
+
base_cmd = cmd_args[0] if cmd_args else ""
|
|
134
|
+
if base_cmd not in config.allowed_commands:
|
|
135
|
+
return f"Command not in allowed list: {base_cmd}"
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
# Execute command with timeout
|
|
139
|
+
result = subprocess.run(
|
|
140
|
+
cmd_args,
|
|
141
|
+
capture_output=True,
|
|
142
|
+
text=True,
|
|
143
|
+
timeout=config.command_timeout,
|
|
144
|
+
shell=False, # Never use shell=True for security
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Format output
|
|
148
|
+
output = ""
|
|
149
|
+
if result.stdout:
|
|
150
|
+
output += result.stdout
|
|
151
|
+
if result.stderr:
|
|
152
|
+
if output:
|
|
153
|
+
output += "\n"
|
|
154
|
+
output += f"stderr: {result.stderr}"
|
|
155
|
+
|
|
156
|
+
if result.returncode != 0 and not output:
|
|
157
|
+
output = f"Command failed with exit code {result.returncode}"
|
|
158
|
+
|
|
159
|
+
return output.strip()
|
|
160
|
+
|
|
161
|
+
except subprocess.TimeoutExpired:
|
|
162
|
+
return f"Command timed out after {config.command_timeout} seconds"
|
|
163
|
+
except subprocess.CalledProcessError as e:
|
|
164
|
+
return f"Command failed: {e}"
|
|
165
|
+
except FileNotFoundError:
|
|
166
|
+
return f"Command not found: {cmd_args[0] if cmd_args else 'unknown'}"
|
|
167
|
+
except Exception as e:
|
|
168
|
+
return f"Execution error: {e}"
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
# History command for conversation history management
|
|
172
|
+
class HistoryCommand(Command):
|
|
173
|
+
"""Command to manage conversation history."""
|
|
174
|
+
|
|
175
|
+
def execute(self, args: List[str], config: HashConfig) -> str:
|
|
176
|
+
from .history import ConversationHistory
|
|
177
|
+
|
|
178
|
+
if not config.history_enabled:
|
|
179
|
+
return "History is disabled in configuration."
|
|
180
|
+
|
|
181
|
+
history = ConversationHistory(config.history_dir)
|
|
182
|
+
|
|
183
|
+
if not args or args[0] == "list":
|
|
184
|
+
# List recent conversations
|
|
185
|
+
sessions = history.list_sessions()
|
|
186
|
+
if not sessions:
|
|
187
|
+
return "No conversation history found."
|
|
188
|
+
|
|
189
|
+
output = "Recent conversations:\n"
|
|
190
|
+
for session in sessions[-10:]: # Show last 10
|
|
191
|
+
output += f" {session['id']}: {session['created']} ({session['message_count']} messages)\n"
|
|
192
|
+
return output.strip()
|
|
193
|
+
|
|
194
|
+
elif args[0] == "show" and len(args) > 1:
|
|
195
|
+
# Show specific conversation
|
|
196
|
+
session_id = args[1]
|
|
197
|
+
messages = history.get_session_messages(session_id)
|
|
198
|
+
if not messages:
|
|
199
|
+
return f"No messages found for session {session_id}"
|
|
200
|
+
|
|
201
|
+
output = f"Conversation {session_id}:\n\n"
|
|
202
|
+
for msg in messages:
|
|
203
|
+
role = msg["role"].upper()
|
|
204
|
+
content = (
|
|
205
|
+
msg["content"][:200] + "..."
|
|
206
|
+
if len(msg["content"]) > 200
|
|
207
|
+
else msg["content"]
|
|
208
|
+
)
|
|
209
|
+
output += f"[{role}] {content}\n\n"
|
|
210
|
+
return output.strip()
|
|
211
|
+
|
|
212
|
+
elif args[0] == "clear":
|
|
213
|
+
# Clear all history
|
|
214
|
+
if history.clear_all_history():
|
|
215
|
+
return "All conversation history cleared."
|
|
216
|
+
else:
|
|
217
|
+
return "Failed to clear history."
|
|
218
|
+
|
|
219
|
+
else:
|
|
220
|
+
return self.get_help()
|
|
221
|
+
|
|
222
|
+
def get_help(self) -> str:
|
|
223
|
+
return """Manage conversation history:
|
|
224
|
+
/history list - List recent conversations
|
|
225
|
+
/history show <id> - Show specific conversation
|
|
226
|
+
/history clear - Clear all history"""
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
# Exit command
|
|
230
|
+
class ExitCommand(Command):
|
|
231
|
+
"""Command to exit the application."""
|
|
232
|
+
|
|
233
|
+
def execute(self, args: List[str], config: HashConfig) -> str:
|
|
234
|
+
import sys
|
|
235
|
+
|
|
236
|
+
console.print("[yellow]Goodbye![/yellow]")
|
|
237
|
+
sys.exit(0)
|
|
238
|
+
|
|
239
|
+
def get_help(self) -> str:
|
|
240
|
+
return "Exit the Hash CLI application."
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Built-in command implementations for command proxy mode."""
|
|
2
|
+
|
|
3
|
+
from .clear import ClearCommand
|
|
4
|
+
from .config import ConfigCommand
|
|
5
|
+
from .fix import FixCommand
|
|
6
|
+
from .help import HelpCommand
|
|
7
|
+
from .ls import LSCommand
|
|
8
|
+
from .model import ModelCommand
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"LSCommand",
|
|
12
|
+
"ClearCommand",
|
|
13
|
+
"ModelCommand",
|
|
14
|
+
"FixCommand",
|
|
15
|
+
"HelpCommand",
|
|
16
|
+
"ConfigCommand",
|
|
17
|
+
]
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Clear command implementation for clearing conversation history."""
|
|
2
|
+
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from ..command_proxy import Command
|
|
6
|
+
from ..config import HashConfig
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ClearCommand(Command):
|
|
10
|
+
"""Command to clear conversation history."""
|
|
11
|
+
|
|
12
|
+
def execute(self, args: List[str], config: HashConfig) -> str:
|
|
13
|
+
"""Clear conversation history."""
|
|
14
|
+
from ..history import ConversationHistory
|
|
15
|
+
|
|
16
|
+
if not config.history_enabled:
|
|
17
|
+
return "History is disabled in configuration."
|
|
18
|
+
|
|
19
|
+
# Parse arguments
|
|
20
|
+
clear_all = "--all" in args or "-a" in args
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
history = ConversationHistory(config.history_dir)
|
|
24
|
+
|
|
25
|
+
if clear_all:
|
|
26
|
+
# Clear all history
|
|
27
|
+
success = history.clear_all_history()
|
|
28
|
+
if success:
|
|
29
|
+
return "All conversation history cleared successfully."
|
|
30
|
+
else:
|
|
31
|
+
return "Failed to clear conversation history."
|
|
32
|
+
else:
|
|
33
|
+
# Clear old history (default: 30 days)
|
|
34
|
+
days = 30
|
|
35
|
+
|
|
36
|
+
# Check for custom days argument
|
|
37
|
+
for i, arg in enumerate(args):
|
|
38
|
+
if arg == "--days" or arg == "-d":
|
|
39
|
+
if i + 1 < len(args):
|
|
40
|
+
try:
|
|
41
|
+
days = int(args[i + 1])
|
|
42
|
+
except ValueError:
|
|
43
|
+
return f"Invalid days value: {args[i + 1]}"
|
|
44
|
+
break
|
|
45
|
+
|
|
46
|
+
cleared_count = history.clear_old_history(days)
|
|
47
|
+
if cleared_count > 0:
|
|
48
|
+
return f"Cleared {cleared_count} old conversations (older than {days} days)."
|
|
49
|
+
else:
|
|
50
|
+
return f"No conversations older than {days} days found."
|
|
51
|
+
|
|
52
|
+
except Exception as e:
|
|
53
|
+
if config.show_debug:
|
|
54
|
+
import traceback
|
|
55
|
+
|
|
56
|
+
return f"Error clearing history: {e}\n{traceback.format_exc()}"
|
|
57
|
+
else:
|
|
58
|
+
return f"Error clearing history: {e}"
|
|
59
|
+
|
|
60
|
+
def get_help(self) -> str:
|
|
61
|
+
"""Get help text for the clear command."""
|
|
62
|
+
return """Clear conversation history:
|
|
63
|
+
/clear - Clear conversations older than 30 days
|
|
64
|
+
/clear --days N - Clear conversations older than N days
|
|
65
|
+
/clear --all - Clear ALL conversation history
|
|
66
|
+
|
|
67
|
+
Examples:
|
|
68
|
+
/clear - Clear old conversations
|
|
69
|
+
/clear --days 7 - Clear conversations older than 7 days
|
|
70
|
+
/clear --all - Clear everything (cannot be undone)"""
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""Config command implementation for configuration management."""
|
|
2
|
+
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from ..command_proxy import Command
|
|
6
|
+
from ..config import HashConfig, save_config
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ConfigCommand(Command):
|
|
10
|
+
"""Command to show and manage configuration."""
|
|
11
|
+
|
|
12
|
+
def execute(self, args: List[str], config: HashConfig) -> str:
|
|
13
|
+
"""Show or manage configuration."""
|
|
14
|
+
|
|
15
|
+
if not args:
|
|
16
|
+
return self._show_config(config)
|
|
17
|
+
|
|
18
|
+
command = args[0].lower()
|
|
19
|
+
|
|
20
|
+
if command == "show":
|
|
21
|
+
return self._show_config(config)
|
|
22
|
+
elif command == "save":
|
|
23
|
+
return self._save_config(config)
|
|
24
|
+
elif command == "stats":
|
|
25
|
+
return self._show_stats(config)
|
|
26
|
+
else:
|
|
27
|
+
return f"Unknown config command: {command}\n{self.get_help()}"
|
|
28
|
+
|
|
29
|
+
def _show_config(self, config: HashConfig) -> str:
|
|
30
|
+
"""Show current configuration."""
|
|
31
|
+
output = "Hash CLI Configuration:\n\n"
|
|
32
|
+
|
|
33
|
+
# LLM Configuration
|
|
34
|
+
output += "[bold blue]LLM Configuration:[/bold blue]\n"
|
|
35
|
+
output += f" Provider: {config.llm_provider.value}\n"
|
|
36
|
+
output += f" Model: {config.get_current_model()}\n"
|
|
37
|
+
output += f" API Key: {'✓ Set' if config.get_current_api_key() else '✗ Not set'}\n\n"
|
|
38
|
+
|
|
39
|
+
# Tool Configuration
|
|
40
|
+
output += "[bold blue]Tool Configuration:[/bold blue]\n"
|
|
41
|
+
output += f" Command execution: {'Enabled' if config.allow_command_execution else 'Disabled'}\n"
|
|
42
|
+
output += f" Confirmation required: {'Yes' if config.require_confirmation else 'No'}\n"
|
|
43
|
+
output += f" Command timeout: {config.command_timeout}s\n"
|
|
44
|
+
output += (
|
|
45
|
+
f" Sandbox commands: {'Yes' if config.sandbox_commands else 'No'}\n\n"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# History Configuration
|
|
49
|
+
output += "[bold blue]History Configuration:[/bold blue]\n"
|
|
50
|
+
output += f" History enabled: {'Yes' if config.history_enabled else 'No'}\n"
|
|
51
|
+
if config.history_enabled:
|
|
52
|
+
output += f" History directory: {config.history_dir}\n"
|
|
53
|
+
output += f" Max history size: {config.max_history_size}\n"
|
|
54
|
+
output += f" Retention days: {config.history_retention_days}\n"
|
|
55
|
+
output += "\n"
|
|
56
|
+
|
|
57
|
+
# Output Configuration
|
|
58
|
+
output += "[bold blue]Output Configuration:[/bold blue]\n"
|
|
59
|
+
output += f" Rich output: {'Yes' if config.rich_output else 'No'}\n"
|
|
60
|
+
output += f" Debug mode: {'Yes' if config.show_debug else 'No'}\n"
|
|
61
|
+
output += f" Log level: {config.log_level.value}\n\n"
|
|
62
|
+
|
|
63
|
+
# Security Configuration
|
|
64
|
+
output += "[bold blue]Security Configuration:[/bold blue]\n"
|
|
65
|
+
if config.allowed_commands:
|
|
66
|
+
output += f" Allowed commands: {', '.join(config.allowed_commands)}\n"
|
|
67
|
+
else:
|
|
68
|
+
output += f" Allowed commands: All (no whitelist)\n"
|
|
69
|
+
output += f" Blocked commands: {', '.join(config.blocked_commands)}\n"
|
|
70
|
+
|
|
71
|
+
return output.strip()
|
|
72
|
+
|
|
73
|
+
def _save_config(self, config: HashConfig) -> str:
|
|
74
|
+
"""Save current configuration to file."""
|
|
75
|
+
try:
|
|
76
|
+
success = save_config(config)
|
|
77
|
+
if success:
|
|
78
|
+
config_path = config.history_dir.parent / "config.toml"
|
|
79
|
+
return f"Configuration saved to {config_path}"
|
|
80
|
+
else:
|
|
81
|
+
return "Failed to save configuration"
|
|
82
|
+
except Exception as e:
|
|
83
|
+
return f"Error saving configuration: {e}"
|
|
84
|
+
|
|
85
|
+
def _show_stats(self, config: HashConfig) -> str:
|
|
86
|
+
"""Show usage statistics."""
|
|
87
|
+
if not config.history_enabled:
|
|
88
|
+
return "History is disabled - no statistics available."
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
from ..history import ConversationHistory
|
|
92
|
+
|
|
93
|
+
history = ConversationHistory(config.history_dir)
|
|
94
|
+
stats = history.get_statistics()
|
|
95
|
+
|
|
96
|
+
output = "Hash CLI Usage Statistics:\n\n"
|
|
97
|
+
output += f"Total conversations: {stats['total_sessions']}\n"
|
|
98
|
+
output += f"Total messages: {stats['total_messages']}\n"
|
|
99
|
+
output += f"Recent conversations (7d): {stats['recent_sessions_7d']}\n"
|
|
100
|
+
output += f"Recent messages (7d): {stats['recent_messages_7d']}\n"
|
|
101
|
+
output += f"Database size: {stats['database_size_bytes'] / 1024:.1f} KB\\n"
|
|
102
|
+
output += f"Database location: {stats['database_path']}\\n"
|
|
103
|
+
|
|
104
|
+
if stats["total_sessions"] > 0:
|
|
105
|
+
avg_messages = stats["total_messages"] / stats["total_sessions"]
|
|
106
|
+
output += f"Average messages per conversation: {avg_messages:.1f}\\n"
|
|
107
|
+
|
|
108
|
+
return output
|
|
109
|
+
|
|
110
|
+
except Exception as e:
|
|
111
|
+
return f"Error getting statistics: {e}"
|
|
112
|
+
|
|
113
|
+
def get_help(self) -> str:
|
|
114
|
+
"""Get help text for the config command."""
|
|
115
|
+
return """Show and manage configuration:
|
|
116
|
+
/config - Show current configuration
|
|
117
|
+
/config show - Show current configuration (same as above)
|
|
118
|
+
/config save - Save current config to file
|
|
119
|
+
/config stats - Show usage statistics
|
|
120
|
+
|
|
121
|
+
Examples:
|
|
122
|
+
/config - View all settings
|
|
123
|
+
/config save - Save to ~/.hashcli/config.toml
|
|
124
|
+
/config stats - See usage statistics"""
|
hashcli/commands/fix.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Fix command implementation for coding assistance."""
|
|
2
|
+
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from ..command_proxy import Command
|
|
6
|
+
from ..config import HashConfig
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FixCommand(Command):
|
|
10
|
+
"""Command for coding-specialized assistance."""
|
|
11
|
+
|
|
12
|
+
def execute(self, args: List[str], config: HashConfig) -> str:
|
|
13
|
+
"""Execute fix command for coding assistance."""
|
|
14
|
+
|
|
15
|
+
if not args:
|
|
16
|
+
return self.get_help()
|
|
17
|
+
|
|
18
|
+
# Join all arguments into a description
|
|
19
|
+
description = " ".join(args)
|
|
20
|
+
|
|
21
|
+
# Create a specialized prompt for coding assistance
|
|
22
|
+
coding_prompt = f"""I need help with a coding issue. Please provide a practical solution:
|
|
23
|
+
|
|
24
|
+
Issue: {description}
|
|
25
|
+
|
|
26
|
+
Please provide:
|
|
27
|
+
1. A clear explanation of the problem
|
|
28
|
+
2. A concrete solution with code examples if applicable
|
|
29
|
+
3. Any relevant best practices or alternatives
|
|
30
|
+
4. Commands to run if needed (I can execute them with your guidance)
|
|
31
|
+
|
|
32
|
+
Focus on being practical and actionable."""
|
|
33
|
+
|
|
34
|
+
# This would normally trigger LLM mode with the specialized prompt
|
|
35
|
+
# For now, return a message indicating the prompt would be processed
|
|
36
|
+
return f"Coding assistance request: '{description}'\n\nThis would normally trigger an LLM conversation with specialized coding context. In a full implementation, this would seamlessly switch to LLM mode with the enhanced prompt above."
|
|
37
|
+
|
|
38
|
+
def get_help(self) -> str:
|
|
39
|
+
"""Get help text for the fix command."""
|
|
40
|
+
return """Get coding assistance for development issues:
|
|
41
|
+
/fix <description> - Get help with a coding problem
|
|
42
|
+
|
|
43
|
+
Examples:
|
|
44
|
+
/fix my python script has a syntax error
|
|
45
|
+
/fix how do I implement authentication in Express.js
|
|
46
|
+
/fix git merge conflict resolution
|
|
47
|
+
/fix optimize this slow database query
|
|
48
|
+
/fix unit test is failing with TypeError
|
|
49
|
+
|
|
50
|
+
This command provides specialized coding assistance with:
|
|
51
|
+
- Problem analysis and solutions
|
|
52
|
+
- Code examples and best practices
|
|
53
|
+
- Command suggestions for fixes
|
|
54
|
+
- Step-by-step guidance"""
|
hashcli/commands/help.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Help command implementation for showing available commands."""
|
|
2
|
+
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from ..command_proxy import Command
|
|
6
|
+
from ..config import HashConfig
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class HelpCommand(Command):
|
|
10
|
+
"""Command to show help information."""
|
|
11
|
+
|
|
12
|
+
def execute(self, args: List[str], config: HashConfig) -> str:
|
|
13
|
+
"""Show help information."""
|
|
14
|
+
|
|
15
|
+
if args and args[0] != "":
|
|
16
|
+
# Show help for specific command
|
|
17
|
+
return self._show_command_help(args[0])
|
|
18
|
+
else:
|
|
19
|
+
# Show general help
|
|
20
|
+
return self._show_general_help()
|
|
21
|
+
|
|
22
|
+
def _show_general_help(self) -> str:
|
|
23
|
+
"""Show general help with all available commands."""
|
|
24
|
+
help_text = """Hash CLI - Intelligent Terminal Assistant
|
|
25
|
+
|
|
26
|
+
DUAL MODE OPERATION:
|
|
27
|
+
hashcli <natural language> - LLM chat mode for questions & assistance
|
|
28
|
+
hashcli /<command> - Command proxy mode for direct actions
|
|
29
|
+
|
|
30
|
+
AVAILABLE COMMANDS:
|
|
31
|
+
/ls [args] - List directory contents (cross-platform)
|
|
32
|
+
/clear [options] - Clear conversation history
|
|
33
|
+
/model [options] - Switch LLM models and providers
|
|
34
|
+
/fix <description> - Get coding assistance
|
|
35
|
+
/help [command] - Show help (this message)
|
|
36
|
+
/config - Show current configuration
|
|
37
|
+
/history [options] - Manage conversation history
|
|
38
|
+
/exit, /quit - Exit the application
|
|
39
|
+
|
|
40
|
+
EXAMPLES:
|
|
41
|
+
# LLM Mode:
|
|
42
|
+
hashcli how do I find large files?
|
|
43
|
+
hashcli explain this error: permission denied
|
|
44
|
+
hashcli help me optimize this Python script
|
|
45
|
+
|
|
46
|
+
# Command Mode:
|
|
47
|
+
hashcli /ls -la
|
|
48
|
+
hashcli /model set gpt-5-mini
|
|
49
|
+
hashcli /clear --days 7
|
|
50
|
+
hashcli /fix my tests are failing
|
|
51
|
+
|
|
52
|
+
GETTING STARTED:
|
|
53
|
+
1. Set API key: export OPENAI_API_KEY="your-key"
|
|
54
|
+
2. Try: hashcli hello world
|
|
55
|
+
3. Or: hashcli /help model
|
|
56
|
+
|
|
57
|
+
For command-specific help: /help <command>"""
|
|
58
|
+
|
|
59
|
+
return help_text
|
|
60
|
+
|
|
61
|
+
def _show_command_help(self, command_name: str) -> str:
|
|
62
|
+
"""Show help for a specific command."""
|
|
63
|
+
# Import here to avoid circular imports
|
|
64
|
+
from ..command_proxy import CommandProxy
|
|
65
|
+
from ..config import HashConfig
|
|
66
|
+
|
|
67
|
+
# Create a temporary config to access command registry
|
|
68
|
+
temp_config = HashConfig()
|
|
69
|
+
proxy = CommandProxy(temp_config)
|
|
70
|
+
|
|
71
|
+
# Get help for the specific command
|
|
72
|
+
command_help = proxy.get_command_help(command_name)
|
|
73
|
+
|
|
74
|
+
if command_help:
|
|
75
|
+
return f"Help for /{command_name}:\\n\\n{command_help}"
|
|
76
|
+
else:
|
|
77
|
+
available_commands = ", ".join(proxy.get_available_commands())
|
|
78
|
+
return f"Unknown command: /{command_name}\\n\\nAvailable commands: {available_commands}\\n\\nUse '/help' for full help."
|
|
79
|
+
|
|
80
|
+
def get_help(self) -> str:
|
|
81
|
+
"""Get help text for the help command."""
|
|
82
|
+
return """Show help information:
|
|
83
|
+
/help - Show general help and all commands
|
|
84
|
+
/help <command> - Show help for specific command
|
|
85
|
+
|
|
86
|
+
Examples:
|
|
87
|
+
/help - Show this help
|
|
88
|
+
/help model - Show help for model command
|
|
89
|
+
/help ls - Show help for ls command"""
|