feat: 新增现代化命令替代功能 v0.2.0

🎯 主要功能:
- 智能检测系统中已安装的现代化命令行工具
- 支持 44 种默认命令映射 (ls→eza, cat→bat, find→fd 等)
- 灵活的环境变量配置系统
- AI 提示词集成,优先推荐现代化工具
- 完整的配置状态显示

⚙️ 配置方式:
- 环境变量: AI_SHELL_MODERN_COMMANDS="ls:eza,cat:bat"
- .env 文件配置支持
- 动态配置检测和合并

🔧 技术实现:
- 新增 get_modern_commands() 配置管理
- 新增 get_available_modern_commands() 系统检测
- 新增 generate_modern_commands_prompt() 提示词生成
- 更新 AI agent 系统提示词
- 完善配置显示和测试脚本

📊 支持的现代化工具:
文件操作: eza, bat, fd, tree
文本处理: rg, sd, choose, delta
系统监控: procs, htop, ncdu, duf
网络工具: gping, httpie, aria2c
编辑器: nvim, micro
其他: zoxide, trash, ouch, pnpm
This commit is contained in:
2025-07-12 22:32:09 +08:00
parent 51cc93d408
commit c4d1510ce9
9 changed files with 660 additions and 24 deletions

View File

@ -4,7 +4,7 @@ AI Shell - AI-powered shell command generator using DeepSeek V3
A command-line tool that generates shell commands from natural language descriptions.
"""
__version__ = "0.1.0"
__version__ = "0.2.0"
__author__ = "AI Shell Team"
__email__ = "ai-shell@example.com"
__description__ = "AI-powered shell command generator using DeepSeek V3"

View File

@ -6,42 +6,48 @@ from textwrap import dedent
from pydantic_ai import Agent
from pydantic_ai.models.openai import OpenAIModel
from .config import get_model, setup_environment
from .config import get_model, setup_environment, generate_modern_commands_prompt
from .models import Answer
# System prompt for the AI agent
SYSTEM_PROMPT = dedent(
"""\
You are a professional developer specializing in shell commands.
Your task is to generate the correct shell commands based on the
user's request.
def create_system_prompt() -> str:
"""Create system prompt with modern commands preferences"""
base_prompt = dedent(
"""\
You are a professional developer specializing in shell commands.
Your task is to generate the correct shell commands based on the
user's request.
IMPORTANT: ALWAYS USE THE SAME LANGUAGE AS THE USER PROMPT IN
YOUR RESPONSE.
IMPORTANT: ALWAYS USE THE SAME LANGUAGE AS THE USER PROMPT IN
YOUR RESPONSE.
Process:
Process:
1. Think Aloud: Use the `think` function to explain your reasoning.
Justify why you chose a particular command, considering efficiency,
safety, and best practices.
1. Think Aloud: Use the `think` function to explain your reasoning.
Justify why you chose a particular command, considering efficiency,
safety, and best practices.
2. Provide the Final Command: Use the `answer` function to present
the final shell command concisely.
"""
)
2. Provide the Final Command: Use the `answer` function to present
the final shell command concisely.
"""
)
# Add modern commands preferences
modern_commands_prompt = generate_modern_commands_prompt()
return base_prompt + modern_commands_prompt
def create_agent() -> Agent:
"""Create and configure the AI agent"""
# Setup environment variables
setup_environment()
# Create OpenAI compatible model
model = OpenAIModel(get_model())
# Create agent
# Create agent with dynamic system prompt
agent = Agent(
model=model,
system_prompt=SYSTEM_PROMPT,
system_prompt=create_system_prompt(),
output_type=Answer,
)

View File

@ -61,6 +61,158 @@ def get_max_retries() -> int:
"""Get max retries from environment"""
return int(os.getenv("AI_SHELL_MAX_RETRIES", "3"))
def get_modern_commands() -> dict:
"""Get modern command alternatives configuration"""
# Default modern command alternatives
default_alternatives = {
"ls": "eza", # Modern ls replacement
"cat": "bat", # Syntax highlighting cat
"find": "fd", # Fast and user-friendly find
"grep": "rg", # Ripgrep - faster grep
"du": "ncdu", # Interactive disk usage
"df": "duf", # Modern df with better output
"ps": "procs", # Modern ps replacement
"top": "htop", # Interactive process viewer
"ping": "gping", # Ping with graph
"curl": "httpie", # User-friendly HTTP client
"wget": "aria2c", # Multi-connection downloader
"diff": "delta", # Better diff with syntax highlighting
"tree": "tree", # Keep tree as is (already modern)
"sed": "sd", # Simpler sed alternative
"awk": "choose", # Human-friendly awk alternative
"cut": "choose", # Alternative to cut
"sort": "sort", # Keep sort as is
"uniq": "uniq", # Keep uniq as is
"head": "head", # Keep head as is
"tail": "tail", # Keep tail as is
"less": "bat", # Use bat for paging too
"more": "bat", # Use bat for paging
"vim": "nvim", # Modern vim
"nano": "micro", # Modern nano alternative
"cd": "zoxide", # Smart cd with frecency
"cp": "cp", # Keep cp as is (or could use rsync)
"mv": "mv", # Keep mv as is
"rm": "trash", # Safer deletion (trash-cli)
"mkdir": "mkdir", # Keep mkdir as is
"rmdir": "rmdir", # Keep rmdir as is
"chmod": "chmod", # Keep chmod as is
"chown": "chown", # Keep chown as is
"tar": "ouch", # Universal archive tool
"zip": "ouch", # Universal archive tool
"unzip": "ouch", # Universal archive tool
"ssh": "ssh", # Keep ssh as is
"scp": "rsync", # More efficient file transfer
"rsync": "rsync", # Keep rsync as is
"git": "git", # Keep git as is
"docker": "docker", # Keep docker as is
"python": "python", # Keep python as is
"node": "node", # Keep node as is
"npm": "pnpm", # Faster npm alternative
"yarn": "pnpm", # Faster yarn alternative
}
# Try to load custom alternatives from environment or config file
custom_alternatives_str = os.getenv("AI_SHELL_MODERN_COMMANDS", "")
custom_alternatives = {}
if custom_alternatives_str:
try:
# Parse format: "old1:new1,old2:new2,old3:new3"
pairs = custom_alternatives_str.split(",")
for pair in pairs:
if ":" in pair:
old_cmd, new_cmd = pair.strip().split(":", 1)
custom_alternatives[old_cmd.strip()] = new_cmd.strip()
except Exception:
pass # Ignore parsing errors
# Merge default and custom alternatives
alternatives = default_alternatives.copy()
alternatives.update(custom_alternatives)
return alternatives
def get_available_modern_commands() -> dict:
"""Get only the modern commands that are actually installed on the system"""
import shutil
all_alternatives = get_modern_commands()
available_alternatives = {}
for old_cmd, new_cmd in all_alternatives.items():
# Check if the modern command is actually available
if shutil.which(new_cmd):
available_alternatives[old_cmd] = new_cmd
# If modern command not available, keep the original
elif shutil.which(old_cmd):
available_alternatives[old_cmd] = old_cmd
return available_alternatives
def generate_modern_commands_prompt() -> str:
"""Generate prompt text about available modern command alternatives"""
available_commands = get_available_modern_commands()
if not available_commands:
return ""
prompt_parts = [
"\n\n**IMPORTANT: Modern Command Preferences**",
"When generating shell commands, prefer these modern alternatives when available:",
""
]
# Group by categories for better readability
categories = {
"文件操作": ["ls", "cat", "find", "tree", "cp", "mv", "rm"],
"文本处理": ["grep", "sed", "awk", "cut", "sort", "uniq", "head", "tail", "less", "more", "diff"],
"系统监控": ["ps", "top", "du", "df"],
"网络工具": ["ping", "curl", "wget"],
"编辑器": ["vim", "nano"],
"导航": ["cd"],
"压缩工具": ["tar", "zip", "unzip"],
"包管理": ["npm", "yarn"],
"其他": []
}
for category, commands in categories.items():
category_commands = []
for cmd in commands:
if cmd in available_commands and available_commands[cmd] != cmd:
category_commands.append(f" - Use `{available_commands[cmd]}` instead of `{cmd}`")
if category_commands:
prompt_parts.append(f"**{category}:**")
prompt_parts.extend(category_commands)
prompt_parts.append("")
# Add remaining commands not in categories
other_commands = []
categorized_commands = set()
for commands in categories.values():
categorized_commands.update(commands)
for old_cmd, new_cmd in available_commands.items():
if old_cmd not in categorized_commands and new_cmd != old_cmd:
other_commands.append(f" - Use `{new_cmd}` instead of `{old_cmd}`")
if other_commands:
prompt_parts.append("**其他工具:**")
prompt_parts.extend(other_commands)
prompt_parts.append("")
prompt_parts.extend([
"These modern tools typically provide:",
"- Better performance and speed",
"- More intuitive syntax and output",
"- Enhanced features and functionality",
"- Better error messages and user experience",
"",
"Always prioritize these modern alternatives when they fulfill the user's requirements."
])
return "\n".join(prompt_parts)
def setup_environment() -> None:
"""Setup environment variables for OpenAI client"""
os.environ["OPENAI_API_KEY"] = get_api_key()

View File

@ -80,7 +80,8 @@ def create_parser() -> argparse.ArgumentParser:
def show_config() -> None:
"""Show current configuration"""
from .config import get_api_key, get_base_url, get_model, get_timeout, get_max_retries, validate_config
from .config import (get_api_key, get_base_url, get_model, get_timeout,
get_max_retries, validate_config, get_available_modern_commands)
print("AI Shell Configuration:")
print(f" Model: {get_model()}")
@ -110,6 +111,30 @@ def show_config() -> None:
print(" AI_SHELL_MODEL - Model name")
print(" AI_SHELL_TIMEOUT - Request timeout (seconds)")
print(" AI_SHELL_MAX_RETRIES - Maximum retry attempts")
print(" AI_SHELL_MODERN_COMMANDS - Custom modern command alternatives")
# Show modern commands configuration
modern_commands = get_available_modern_commands()
if modern_commands:
print(f"\n现代化命令替代 ({len(modern_commands)} 个可用):")
# Group commands for better display
active_alternatives = {k: v for k, v in modern_commands.items() if k != v}
unchanged_commands = {k: v for k, v in modern_commands.items() if k == v}
if active_alternatives:
print(" 已启用的替代:")
for old_cmd, new_cmd in sorted(active_alternatives.items()):
print(f" {old_cmd}{new_cmd}")
if unchanged_commands:
print(f" 保持原样: {', '.join(sorted(unchanged_commands.keys()))}")
print("\n 自定义配置格式:")
print(" export AI_SHELL_MODERN_COMMANDS=\"old1:new1,old2:new2\"")
print(" 例如: export AI_SHELL_MODERN_COMMANDS=\"ls:exa,cat:bat,find:fd\"")
else:
print("\n现代化命令替代: 未检测到可用的现代化工具")
def main() -> None:
"""Main entry point"""