hashcli 0.1.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.
- hashcli-0.1.0/LICENSE +21 -0
- hashcli-0.1.0/PKG-INFO +52 -0
- hashcli-0.1.0/README.md +9 -0
- hashcli-0.1.0/hashcli/__init__.py +29 -0
- hashcli-0.1.0/hashcli/command_proxy.py +240 -0
- hashcli-0.1.0/hashcli/commands/__init__.py +17 -0
- hashcli-0.1.0/hashcli/commands/clear.py +70 -0
- hashcli-0.1.0/hashcli/commands/config.py +124 -0
- hashcli-0.1.0/hashcli/commands/fix.py +54 -0
- hashcli-0.1.0/hashcli/commands/help.py +89 -0
- hashcli-0.1.0/hashcli/commands/ls.py +88 -0
- hashcli-0.1.0/hashcli/commands/model.py +143 -0
- hashcli-0.1.0/hashcli/config.py +348 -0
- hashcli-0.1.0/hashcli/history.py +451 -0
- hashcli-0.1.0/hashcli/llm_handler.py +340 -0
- hashcli-0.1.0/hashcli/main.py +321 -0
- hashcli-0.1.0/hashcli/providers/__init__.py +13 -0
- hashcli-0.1.0/hashcli/providers/anthropic_provider.py +157 -0
- hashcli-0.1.0/hashcli/providers/base.py +53 -0
- hashcli-0.1.0/hashcli/providers/google_provider.py +179 -0
- hashcli-0.1.0/hashcli/providers/openai_provider.py +111 -0
- hashcli-0.1.0/hashcli/tools/__init__.py +16 -0
- hashcli-0.1.0/hashcli/tools/base.py +60 -0
- hashcli-0.1.0/hashcli/tools/code_analysis.py +339 -0
- hashcli-0.1.0/hashcli/tools/filesystem.py +205 -0
- hashcli-0.1.0/hashcli/tools/shell.py +128 -0
- hashcli-0.1.0/hashcli/tools/web_search.py +85 -0
- hashcli-0.1.0/hashcli.egg-info/PKG-INFO +52 -0
- hashcli-0.1.0/hashcli.egg-info/SOURCES.txt +38 -0
- hashcli-0.1.0/hashcli.egg-info/dependency_links.txt +1 -0
- hashcli-0.1.0/hashcli.egg-info/entry_points.txt +2 -0
- hashcli-0.1.0/hashcli.egg-info/requires.txt +18 -0
- hashcli-0.1.0/hashcli.egg-info/top_level.txt +1 -0
- hashcli-0.1.0/pyproject.toml +151 -0
- hashcli-0.1.0/setup.cfg +4 -0
- hashcli-0.1.0/tests/test_command_proxy.py +219 -0
- hashcli-0.1.0/tests/test_config.py +180 -0
- hashcli-0.1.0/tests/test_history.py +335 -0
- hashcli-0.1.0/tests/test_integration.py +310 -0
- hashcli-0.1.0/tests/test_main.py +87 -0
hashcli-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Wensheng
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
hashcli-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hashcli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Hash - Intelligent CLI system with dual-mode functionality (LLM chat and command proxy)
|
|
5
|
+
Author: Wensheng Wang
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/wensheng/hash
|
|
8
|
+
Project-URL: Repository, https://github.com/wensheng/hash.git
|
|
9
|
+
Project-URL: Documentation, https://github.com/wensheng/hash/docs
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/wensheng/hash/issues
|
|
11
|
+
Keywords: cli,llm,assistant,terminal,ai,hash
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Classifier: Topic :: System :: System Shells
|
|
21
|
+
Classifier: Topic :: Utilities
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: openai>=1.0.0
|
|
26
|
+
Requires-Dist: google-generativeai>=0.3.0
|
|
27
|
+
Requires-Dist: anthropic>=0.28.0
|
|
28
|
+
Requires-Dist: typer[all]>=0.9.0
|
|
29
|
+
Requires-Dist: rich>=13.0.0
|
|
30
|
+
Requires-Dist: pydantic>=2.0.0
|
|
31
|
+
Requires-Dist: aiohttp>=3.8.0
|
|
32
|
+
Requires-Dist: toml>=0.10.2
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
36
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
37
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
38
|
+
Requires-Dist: isort>=5.0.0; extra == "dev"
|
|
39
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
40
|
+
Requires-Dist: flake8>=6.0.0; extra == "dev"
|
|
41
|
+
Requires-Dist: pre-commit>=3.0.0; extra == "dev"
|
|
42
|
+
Dynamic: license-file
|
|
43
|
+
|
|
44
|
+
# hash \#
|
|
45
|
+
|
|
46
|
+
Hash # (HAcker SHell).
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
With pip:
|
|
51
|
+
|
|
52
|
+
pip install hashcli
|
hashcli-0.1.0/README.md
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
|
+
]
|
|
@@ -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"""
|
|
@@ -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"""
|