From 9f4023bdf094d250e72a98589713e3d7ac8fcbef Mon Sep 17 00:00:00 2001 From: abel533 Date: Sun, 5 Apr 2026 13:16:55 +0800 Subject: [PATCH] refactor: apply BaseSlashCommand to all 54 command implementations - implements SlashCommand -> extends BaseSlashCommand (all 54 files) - Replace CommandUtils.parseArgs/args null checks -> args(args) - Replace agentLoop null checks -> requireAgentLoop(context)/noSession() - Replace context.agentLoop().getToolContext() -> toolCtx(context) - Replace CommandUtils.success/error/header/subtitle/warning/info -> instance methods - Remove unused CommandUtils/AnsiStyle imports where applicable - 87 tests passing, zero compilation errors Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- simple_weather.py | 69 +++++++ .../claudecode/command/BaseSlashCommand.java | 110 +++++++++++ .../claudecode/command/impl/AgentCommand.java | 10 +- .../command/impl/BranchCommand.java | 6 +- .../claudecode/command/impl/BriefCommand.java | 12 +- .../claudecode/command/impl/ClearCommand.java | 4 +- .../command/impl/CommitCommand.java | 6 +- .../command/impl/CompactCommand.java | 8 +- .../command/impl/ConfigCommand.java | 4 +- .../command/impl/ContextCommand.java | 4 +- .../command/impl/ContextVizCommand.java | 11 +- .../claudecode/command/impl/CopyCommand.java | 4 +- .../claudecode/command/impl/CostCommand.java | 6 +- .../claudecode/command/impl/DebugCommand.java | 13 +- .../claudecode/command/impl/DiffCommand.java | 6 +- .../command/impl/DoctorCommand.java | 4 +- .../claudecode/command/impl/EnvCommand.java | 14 +- .../claudecode/command/impl/ExitCommand.java | 4 +- .../command/impl/ExportCommand.java | 6 +- .../command/impl/FeedbackCommand.java | 6 +- .../claudecode/command/impl/FilesCommand.java | 4 +- .../command/impl/HeapdumpCommand.java | 20 +- .../claudecode/command/impl/HelpCommand.java | 5 +- .../command/impl/HistoryCommand.java | 4 +- .../claudecode/command/impl/HooksCommand.java | 4 +- .../claudecode/command/impl/InitCommand.java | 5 +- .../command/impl/KeybindingsCommand.java | 4 +- .../claudecode/command/impl/McpCommand.java | 12 +- .../command/impl/MemoryCommand.java | 6 +- .../claudecode/command/impl/ModelCommand.java | 4 +- .../command/impl/OutputStyleCommand.java | 12 +- .../command/impl/PerformanceCommand.java | 17 +- .../command/impl/PermissionsCommand.java | 4 +- .../claudecode/command/impl/PlanCommand.java | 4 +- .../command/impl/PluginCommand.java | 20 +- .../command/impl/PrivacyCommand.java | 12 +- .../command/impl/ReleaseNotesCommand.java | 6 +- .../command/impl/RenameCommand.java | 6 +- .../command/impl/ResetLimitsCommand.java | 11 +- .../command/impl/ResumeCommand.java | 6 +- .../command/impl/ReviewCommand.java | 6 +- .../command/impl/RewindCommand.java | 6 +- .../command/impl/SandboxCommand.java | 14 +- .../command/impl/SecurityReviewCommand.java | 6 +- .../command/impl/SessionCommand.java | 6 +- .../command/impl/SkillsCommand.java | 8 +- .../claudecode/command/impl/StatsCommand.java | 6 +- .../command/impl/StatusCommand.java | 6 +- .../claudecode/command/impl/TagCommand.java | 6 +- .../claudecode/command/impl/TasksCommand.java | 6 +- .../claudecode/command/impl/ThemeCommand.java | 12 +- .../claudecode/command/impl/TipsCommand.java | 6 +- .../claudecode/command/impl/TraceCommand.java | 16 +- .../claudecode/command/impl/UsageCommand.java | 8 +- .../command/impl/VersionCommand.java | 7 +- .../claudecode/command/impl/VimCommand.java | 12 +- weather_example.py | 171 ++++++++++++++++++ 57 files changed, 558 insertions(+), 217 deletions(-) create mode 100644 simple_weather.py create mode 100644 src/main/java/com/claudecode/command/BaseSlashCommand.java create mode 100644 weather_example.py diff --git a/simple_weather.py b/simple_weather.py new file mode 100644 index 0000000..d7dacbd --- /dev/null +++ b/simple_weather.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +""" +简单的天气查询脚本 +使用OpenWeatherMap API +""" + +import requests +import sys + +def get_weather(api_key, city="Beijing", units="metric"): + """ + 获取指定城市的天气 + """ + url = "http://api.openweathermap.org/data/2.5/weather" + + params = { + "q": city, + "appid": api_key, + "units": units, + "lang": "zh_cn" + } + + try: + response = requests.get(url, params=params, timeout=10) + data = response.json() + + if data.get("cod") != 200: + print(f"错误: {data.get('message', '未知错误')}") + return None + + return data + except Exception as e: + print(f"请求失败: {e}") + return None + +def main(): + # 请在这里输入您的API密钥 + API_KEY = "YOUR_API_KEY_HERE" + + if API_KEY == "YOUR_API_KEY_HERE": + print("请先获取OpenWeatherMap API密钥:") + print("1. 访问 https://openweathermap.org/api") + print("2. 注册免费账户") + print("3. 获取API密钥并替换代码中的 'YOUR_API_KEY_HERE'") + print("\n免费套餐:每天60次调用") + return + + # 查询城市(可以修改为您所在的城市) + city = input("请输入城市名称(英文,如 Beijing, Shanghai, New York): ").strip() + if not city: + city = "Beijing" + + print(f"\n正在查询 {city} 的天气...") + + weather_data = get_weather(API_KEY, city) + + if weather_data: + print(f"\n=== {weather_data['name']} 天气 ===") + print(f"温度: {weather_data['main']['temp']}°C") + print(f"体感温度: {weather_data['main']['feels_like']}°C") + print(f"天气: {weather_data['weather'][0]['description']}") + print(f"湿度: {weather_data['main']['humidity']}%") + print(f"风速: {weather_data['wind']['speed']} m/s") + print(f"气压: {weather_data['main']['pressure']} hPa") + else: + print("无法获取天气数据") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/main/java/com/claudecode/command/BaseSlashCommand.java b/src/main/java/com/claudecode/command/BaseSlashCommand.java new file mode 100644 index 0000000..41a3fb9 --- /dev/null +++ b/src/main/java/com/claudecode/command/BaseSlashCommand.java @@ -0,0 +1,110 @@ +package com.claudecode.command; + +import com.claudecode.core.AgentLoop; +import com.claudecode.tool.ToolContext; + +import java.util.List; + +/** + * 命令抽象基类 —— 消除 54 个命令实现中的重复模式。 + *

+ * 提供: + *

+ * + * 子类只需实现 {@link #name()}, {@link #description()}, {@link #execute(String, CommandContext)}。 + * 可选覆盖 {@link #aliases()}。 + */ +public abstract class BaseSlashCommand implements SlashCommand { + + @Override + public List aliases() { + return List.of(); + } + + // ==================== 参数解析 ==================== + + /** + * 安全解析命令参数(null → 空字符串,去首尾空白)。 + */ + protected String args(String raw) { + return CommandUtils.parseArgs(raw); + } + + // ==================== 上下文访问 ==================== + + /** + * 检查 AgentLoop 是否可用。如果为 null,返回错误消息;否则返回 null。 + * 典型用法: + *
+     *   AgentLoop loop = requireAgentLoop(context);
+     *   if (loop == null) return noSession();
+     * 
+ */ + protected AgentLoop requireAgentLoop(CommandContext context) { + return context.agentLoop(); + } + + /** + * 标准 "无会话" 错误消息。 + */ + protected String noSession() { + return CommandUtils.error("No active session"); + } + + /** + * 快速获取 ToolContext。如果 agentLoop 为 null 则返回 null。 + */ + protected ToolContext toolCtx(CommandContext context) { + return context.agentLoop() != null ? context.agentLoop().getToolContext() : null; + } + + // ==================== 格式化输出 ==================== + + /** + * 成功消息(绿色 ✓)。 + */ + protected String success(String message) { + return CommandUtils.success(message); + } + + /** + * 错误消息(红色 ✗)。 + */ + protected String error(String message) { + return CommandUtils.error(message); + } + + /** + * 警告消息(黄色 ⚠)。 + */ + protected String warning(String message) { + return CommandUtils.warning(message); + } + + /** + * 信息消息(蓝色 ℹ)。 + */ + protected String info(String message) { + return CommandUtils.info(message); + } + + /** + * 格式化标题行。 + */ + protected String header(String emoji, String title) { + return CommandUtils.header(emoji, title); + } + + /** + * 格式化子标题。 + */ + protected String subtitle(String title) { + return CommandUtils.subtitle(title); + } +} diff --git a/src/main/java/com/claudecode/command/impl/AgentCommand.java b/src/main/java/com/claudecode/command/impl/AgentCommand.java index 0b037d2..b7298f9 100644 --- a/src/main/java/com/claudecode/command/impl/AgentCommand.java +++ b/src/main/java/com/claudecode/command/impl/AgentCommand.java @@ -1,7 +1,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import com.claudecode.core.TaskManager; @@ -20,7 +20,7 @@ import java.util.List; *
  • /agent stop-all —— 停止所有 agent
  • * */ -public class AgentCommand implements SlashCommand { +public class AgentCommand extends BaseSlashCommand { @Override public String name() { @@ -39,7 +39,7 @@ public class AgentCommand implements SlashCommand { @Override public String execute(String args, CommandContext context) { - args = args == null ? "" : args.strip(); + args = args(args); // Get TaskManager from AgentLoop's tool context TaskManager taskManager = getTaskManager(context); @@ -183,8 +183,8 @@ public class AgentCommand implements SlashCommand { private TaskManager getTaskManager(CommandContext context) { // The TaskManager is typically accessible from the tool context // via AgentLoop's tool context. Try to access it. - if (context.agentLoop() == null) return null; - var toolContext = context.agentLoop().getToolContext(); + if (requireAgentLoop(context) == null) return null; + var toolContext = toolCtx(context); if (toolContext == null) return null; return toolContext.getOrDefault("TASK_MANAGER", null); } diff --git a/src/main/java/com/claudecode/command/impl/BranchCommand.java b/src/main/java/com/claudecode/command/impl/BranchCommand.java index 2c0b354..7d4b913 100644 --- a/src/main/java/com/claudecode/command/impl/BranchCommand.java +++ b/src/main/java/com/claudecode/command/impl/BranchCommand.java @@ -1,7 +1,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import org.springframework.ai.chat.messages.Message; @@ -26,7 +26,7 @@ import java.util.Map; *
  • {@code /branch delete } —— 删除指定分支
  • * */ -public class BranchCommand implements SlashCommand { +public class BranchCommand extends BaseSlashCommand { /** 静态分支存储:分支名称 -> 消息快照 */ private static final Map branches = new LinkedHashMap<>(); @@ -43,7 +43,7 @@ public class BranchCommand implements SlashCommand { @Override public String execute(String args, CommandContext context) { - if (context.agentLoop() == null) { + if (requireAgentLoop(context) == null) { return AnsiStyle.red(" ✗ AgentLoop unavailable."); } diff --git a/src/main/java/com/claudecode/command/impl/BriefCommand.java b/src/main/java/com/claudecode/command/impl/BriefCommand.java index 13faf7d..13d3a00 100644 --- a/src/main/java/com/claudecode/command/impl/BriefCommand.java +++ b/src/main/java/com/claudecode/command/impl/BriefCommand.java @@ -1,13 +1,13 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; /** * /brief 命令 —— 切换简洁输出模式。 */ -public class BriefCommand implements SlashCommand { +public class BriefCommand extends BaseSlashCommand { @Override public String name() { return "brief"; } @@ -17,14 +17,14 @@ public class BriefCommand implements SlashCommand { @Override public String execute(String args, CommandContext context) { - if (context.agentLoop() == null) { - return AnsiStyle.red(" ✗ No active session"); + if (requireAgentLoop(context) == null) { + return noSession(); } - var toolCtx = context.agentLoop().getToolContext(); + var toolCtx = toolCtx(context); boolean current = Boolean.TRUE.equals(toolCtx.get("BRIEF_MODE")); - String trimmed = (args == null) ? "" : args.trim(); + String trimmed = args(args); boolean newMode = switch (trimmed) { case "on", "enable", "true" -> true; case "off", "disable", "false" -> false; diff --git a/src/main/java/com/claudecode/command/impl/ClearCommand.java b/src/main/java/com/claudecode/command/impl/ClearCommand.java index 8e8fe80..6688475 100644 --- a/src/main/java/com/claudecode/command/impl/ClearCommand.java +++ b/src/main/java/com/claudecode/command/impl/ClearCommand.java @@ -1,13 +1,13 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; /** * /clear 命令 —— 清除对话历史。 */ -public class ClearCommand implements SlashCommand { +public class ClearCommand extends BaseSlashCommand { @Override public String name() { diff --git a/src/main/java/com/claudecode/command/impl/CommitCommand.java b/src/main/java/com/claudecode/command/impl/CommitCommand.java index 8c9dd89..4fd45cd 100644 --- a/src/main/java/com/claudecode/command/impl/CommitCommand.java +++ b/src/main/java/com/claudecode/command/impl/CommitCommand.java @@ -1,7 +1,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import java.io.BufferedReader; @@ -22,7 +22,7 @@ import java.util.concurrent.TimeUnit; *
  • /commit --pr —— 提交 + push + 创建 PR(使用 gh CLI)
  • * */ -public class CommitCommand implements SlashCommand { +public class CommitCommand extends BaseSlashCommand { @Override public String name() { @@ -41,7 +41,7 @@ public class CommitCommand implements SlashCommand { return AnsiStyle.yellow(" ⚠ Current directory is not a Git repository"); } - args = args == null ? "" : args.strip(); + args = args(args); try { boolean addAll = args.contains("--all") || args.contains("-a"); diff --git a/src/main/java/com/claudecode/command/impl/CompactCommand.java b/src/main/java/com/claudecode/command/impl/CompactCommand.java index 50b087d..b48a47f 100644 --- a/src/main/java/com/claudecode/command/impl/CompactCommand.java +++ b/src/main/java/com/claudecode/command/impl/CompactCommand.java @@ -1,7 +1,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import com.claudecode.core.TokenTracker; import com.claudecode.core.compact.AutoCompactManager; @@ -23,7 +23,7 @@ import java.util.List; *
  • /compact --aggressive —— 激进压缩(保留更少上下文)
  • * */ -public class CompactCommand implements SlashCommand { +public class CompactCommand extends BaseSlashCommand { @Override public String name() { @@ -37,11 +37,11 @@ public class CompactCommand implements SlashCommand { @Override public String execute(String args, CommandContext context) { - if (context.agentLoop() == null) { + if (requireAgentLoop(context) == null) { return AnsiStyle.yellow(" ⚠ No active conversation to compact."); } - String argStr = (args == null) ? "" : args.strip(); + String argStr = args(args); // --stats: only show statistics if (argStr.equals("--stats") || argStr.equals("-s")) { diff --git a/src/main/java/com/claudecode/command/impl/ConfigCommand.java b/src/main/java/com/claudecode/command/impl/ConfigCommand.java index d3b1c52..68c8d64 100644 --- a/src/main/java/com/claudecode/command/impl/ConfigCommand.java +++ b/src/main/java/com/claudecode/command/impl/ConfigCommand.java @@ -1,7 +1,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import com.claudecode.permission.PermissionRuleEngine; import com.claudecode.permission.PermissionSettings; @@ -15,7 +15,7 @@ import java.util.Map; *

    * 支持查看当前配置、设置单个配置项,以及权限管理子命令。 */ -public class ConfigCommand implements SlashCommand { +public class ConfigCommand extends BaseSlashCommand { /** 支持的配置项及说明 */ private static final Map CONFIG_KEYS = Map.of( diff --git a/src/main/java/com/claudecode/command/impl/ContextCommand.java b/src/main/java/com/claudecode/command/impl/ContextCommand.java index 4c9d868..f73b1cf 100644 --- a/src/main/java/com/claudecode/command/impl/ContextCommand.java +++ b/src/main/java/com/claudecode/command/impl/ContextCommand.java @@ -1,7 +1,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import com.claudecode.context.ClaudeMdLoader; import com.claudecode.context.GitContext; @@ -14,7 +14,7 @@ import java.nio.file.Path; *

    * 展示已加载的 CLAUDE.md、Skills、Git 上下文和 Token 预算使用情况。 */ -public class ContextCommand implements SlashCommand { +public class ContextCommand extends BaseSlashCommand { @Override public String name() { diff --git a/src/main/java/com/claudecode/command/impl/ContextVizCommand.java b/src/main/java/com/claudecode/command/impl/ContextVizCommand.java index 879a578..8b4c8e7 100644 --- a/src/main/java/com/claudecode/command/impl/ContextVizCommand.java +++ b/src/main/java/com/claudecode/command/impl/ContextVizCommand.java @@ -1,8 +1,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.CommandUtils; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import com.claudecode.core.TokenEstimationService; @@ -11,7 +10,7 @@ import java.util.List; /** * /ctx-viz 命令 —— 上下文可视化(token 分布、消息结构)。 */ -public class ContextVizCommand implements SlashCommand { +public class ContextVizCommand extends BaseSlashCommand { @Override public String name() { return "ctx-viz"; } @@ -25,13 +24,13 @@ public class ContextVizCommand implements SlashCommand { @Override public String execute(String args, CommandContext context) { StringBuilder sb = new StringBuilder(); - sb.append(CommandUtils.header("📊", "Context Window Visualization")); + sb.append(header("📊", "Context Window Visualization")); - if (context.agentLoop() == null) { + if (requireAgentLoop(context) == null) { return sb.append(" No active agent loop\n").toString(); } - var toolCtx = context.agentLoop().getToolContext(); + var toolCtx = toolCtx(context); // Get or create token estimation service TokenEstimationService estimator = new TokenEstimationService(); diff --git a/src/main/java/com/claudecode/command/impl/CopyCommand.java b/src/main/java/com/claudecode/command/impl/CopyCommand.java index 29138e4..ac11039 100644 --- a/src/main/java/com/claudecode/command/impl/CopyCommand.java +++ b/src/main/java/com/claudecode/command/impl/CopyCommand.java @@ -1,7 +1,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.messages.Message; @@ -16,7 +16,7 @@ import java.util.List; * 从消息历史中提取最后一条 AssistantMessage 的文本内容, * 使用 AWT 剪贴板 API 复制。 */ -public class CopyCommand implements SlashCommand { +public class CopyCommand extends BaseSlashCommand { @Override public String name() { diff --git a/src/main/java/com/claudecode/command/impl/CostCommand.java b/src/main/java/com/claudecode/command/impl/CostCommand.java index 10f6392..ef649ef 100644 --- a/src/main/java/com/claudecode/command/impl/CostCommand.java +++ b/src/main/java/com/claudecode/command/impl/CostCommand.java @@ -2,7 +2,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; import com.claudecode.command.CommandUtils; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import com.claudecode.core.TokenTracker; @@ -12,7 +12,7 @@ import com.claudecode.core.TokenTracker; * 对应 claude-code/src/commands/cost.ts。 * 从 AgentLoop 的 TokenTracker 获取真实 Token 统计。 */ -public class CostCommand implements SlashCommand { +public class CostCommand extends BaseSlashCommand { @Override public String name() { @@ -30,7 +30,7 @@ public class CostCommand implements SlashCommand { int msgCount = context.agentLoop().getMessageHistory().size(); StringBuilder sb = new StringBuilder(); - sb.append(CommandUtils.header("💰", "Token Usage & Cost")); + sb.append(header("💰", "Token Usage & Cost")); sb.append(" ").append(AnsiStyle.bold("Model: ")).append(AnsiStyle.cyan(tracker.getModelName())).append("\n"); sb.append(" ").append(AnsiStyle.bold("API Calls: ")).append(tracker.getApiCallCount()).append("\n"); diff --git a/src/main/java/com/claudecode/command/impl/DebugCommand.java b/src/main/java/com/claudecode/command/impl/DebugCommand.java index 982f463..843f610 100644 --- a/src/main/java/com/claudecode/command/impl/DebugCommand.java +++ b/src/main/java/com/claudecode/command/impl/DebugCommand.java @@ -1,8 +1,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.CommandUtils; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import com.claudecode.core.InternalLogger; @@ -11,7 +10,7 @@ import java.util.List; /** * /debug 命令 —— 调试模式开关 + 工具调用追踪。 */ -public class DebugCommand implements SlashCommand { +public class DebugCommand extends BaseSlashCommand { @Override public String name() { return "debug"; } @@ -24,16 +23,16 @@ public class DebugCommand implements SlashCommand { @Override public String execute(String args, CommandContext context) { - String trimmed = CommandUtils.parseArgs(args); + String trimmed = args(args); StringBuilder sb = new StringBuilder(); - sb.append(CommandUtils.header("🐛", "Debug Mode")); + sb.append(header("🐛", "Debug Mode")); - if (context.agentLoop() == null) { + if (requireAgentLoop(context) == null) { return sb.append(" No active agent loop\n").toString(); } - var toolCtx = context.agentLoop().getToolContext(); + var toolCtx = toolCtx(context); if (trimmed.equals("on") || trimmed.equals("enable")) { toolCtx.set("DEBUG_MODE", true); diff --git a/src/main/java/com/claudecode/command/impl/DiffCommand.java b/src/main/java/com/claudecode/command/impl/DiffCommand.java index cf77664..8eeb07e 100644 --- a/src/main/java/com/claudecode/command/impl/DiffCommand.java +++ b/src/main/java/com/claudecode/command/impl/DiffCommand.java @@ -1,7 +1,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import java.io.BufferedReader; @@ -20,7 +20,7 @@ import java.util.concurrent.TimeUnit; *

  • --stat:仅显示文件统计(不含详细diff)
  • * */ -public class DiffCommand implements SlashCommand { +public class DiffCommand extends BaseSlashCommand { @Override public String name() { @@ -39,7 +39,7 @@ public class DiffCommand implements SlashCommand { return AnsiStyle.yellow(" ⚠ Current directory is not a Git repository"); } - args = args == null ? "" : args.strip(); + args = args(args); try { String diffOutput; diff --git a/src/main/java/com/claudecode/command/impl/DoctorCommand.java b/src/main/java/com/claudecode/command/impl/DoctorCommand.java index fb1bf27..99d0125 100644 --- a/src/main/java/com/claudecode/command/impl/DoctorCommand.java +++ b/src/main/java/com/claudecode/command/impl/DoctorCommand.java @@ -1,7 +1,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import java.io.File; @@ -21,7 +21,7 @@ import java.util.List; *
  • 磁盘空间
  • * */ -public class DoctorCommand implements SlashCommand { +public class DoctorCommand extends BaseSlashCommand { @Override public String name() { diff --git a/src/main/java/com/claudecode/command/impl/EnvCommand.java b/src/main/java/com/claudecode/command/impl/EnvCommand.java index 5da62fa..cfce953 100644 --- a/src/main/java/com/claudecode/command/impl/EnvCommand.java +++ b/src/main/java/com/claudecode/command/impl/EnvCommand.java @@ -2,7 +2,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; import com.claudecode.command.CommandUtils; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import java.io.File; @@ -13,7 +13,7 @@ import java.util.TreeMap; /** * /env 命令 —— 显示环境变量和配置信息。 */ -public class EnvCommand implements SlashCommand { +public class EnvCommand extends BaseSlashCommand { @Override public String name() { return "env"; } @@ -23,12 +23,12 @@ public class EnvCommand implements SlashCommand { @Override public String execute(String args, CommandContext context) { - String trimmed = CommandUtils.parseArgs(args); + String trimmed = args(args); StringBuilder sb = new StringBuilder(); - sb.append(CommandUtils.header("🔧", "Environment")); + sb.append(header("🔧", "Environment")); - sb.append(CommandUtils.subtitle("System")).append("\n"); + sb.append(subtitle("System")).append("\n"); sb.append(" OS: ").append(System.getProperty("os.name")).append(" ") .append(System.getProperty("os.version")).append("\n"); sb.append(" Java: ").append(System.getProperty("java.version")) @@ -38,13 +38,13 @@ public class EnvCommand implements SlashCommand { .append(" / ").append(CommandUtils.formatBytes(Runtime.getRuntime().maxMemory())).append("\n"); sb.append(" PID: ").append(ProcessHandle.current().pid()).append("\n\n"); - sb.append(CommandUtils.subtitle("Paths")).append("\n"); + sb.append(subtitle("Paths")).append("\n"); sb.append(" WorkDir: ").append(System.getProperty("user.dir")).append("\n"); sb.append(" Home: ").append(System.getProperty("user.home")).append("\n"); sb.append(" Config: ").append(System.getProperty("user.home")) .append(File.separator).append(".claude-code-java").append("\n\n"); - sb.append(CommandUtils.subtitle("Environment Variables")).append("\n"); + sb.append(subtitle("Environment Variables")).append("\n"); List relevantVars = List.of( "ANTHROPIC_API_KEY", "OPENAI_API_KEY", "CLAUDE_CODE_", "JAVA_HOME", "PATH", "SHELL", "TERM", "EDITOR" diff --git a/src/main/java/com/claudecode/command/impl/ExitCommand.java b/src/main/java/com/claudecode/command/impl/ExitCommand.java index fcd21cf..b1f2c01 100644 --- a/src/main/java/com/claudecode/command/impl/ExitCommand.java +++ b/src/main/java/com/claudecode/command/impl/ExitCommand.java @@ -1,14 +1,14 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import java.util.List; /** * /exit 命令 —— 退出应用。 */ -public class ExitCommand implements SlashCommand { +public class ExitCommand extends BaseSlashCommand { @Override public String name() { diff --git a/src/main/java/com/claudecode/command/impl/ExportCommand.java b/src/main/java/com/claudecode/command/impl/ExportCommand.java index 1cef675..a1f0e17 100644 --- a/src/main/java/com/claudecode/command/impl/ExportCommand.java +++ b/src/main/java/com/claudecode/command/impl/ExportCommand.java @@ -1,7 +1,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import org.springframework.ai.chat.messages.*; @@ -22,7 +22,7 @@ import java.util.List; *
  • /export [路径] —— 导出到指定路径
  • * */ -public class ExportCommand implements SlashCommand { +public class ExportCommand extends BaseSlashCommand { private static final DateTimeFormatter TIMESTAMP_FMT = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"); @@ -46,7 +46,7 @@ public class ExportCommand implements SlashCommand { } // 确定输出路径 - args = args == null ? "" : args.strip(); + args = args(args); Path outputPath; if (!args.isEmpty()) { outputPath = Path.of(args); diff --git a/src/main/java/com/claudecode/command/impl/FeedbackCommand.java b/src/main/java/com/claudecode/command/impl/FeedbackCommand.java index 67e40a9..4f947ca 100644 --- a/src/main/java/com/claudecode/command/impl/FeedbackCommand.java +++ b/src/main/java/com/claudecode/command/impl/FeedbackCommand.java @@ -1,7 +1,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import java.io.IOException; @@ -12,7 +12,7 @@ import java.time.Instant; /** * /feedback 命令 —— 提交反馈(本地保存)。 */ -public class FeedbackCommand implements SlashCommand { +public class FeedbackCommand extends BaseSlashCommand { @Override public String name() { return "feedback"; } @@ -22,7 +22,7 @@ public class FeedbackCommand implements SlashCommand { @Override public String execute(String args, CommandContext context) { - String trimmed = (args == null) ? "" : args.trim(); + String trimmed = args(args); if (trimmed.isEmpty()) { return AnsiStyle.yellow(" Usage: /feedback ") diff --git a/src/main/java/com/claudecode/command/impl/FilesCommand.java b/src/main/java/com/claudecode/command/impl/FilesCommand.java index fcb4e62..9e32a39 100644 --- a/src/main/java/com/claudecode/command/impl/FilesCommand.java +++ b/src/main/java/com/claudecode/command/impl/FilesCommand.java @@ -1,7 +1,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import java.io.IOException; @@ -15,7 +15,7 @@ import java.util.stream.Stream; * 对应 claude-code/src/commands/files.ts。 * 显示项目目录树(默认2层深度)。 */ -public class FilesCommand implements SlashCommand { +public class FilesCommand extends BaseSlashCommand { @Override public String name() { diff --git a/src/main/java/com/claudecode/command/impl/HeapdumpCommand.java b/src/main/java/com/claudecode/command/impl/HeapdumpCommand.java index 50c075b..71c0195 100644 --- a/src/main/java/com/claudecode/command/impl/HeapdumpCommand.java +++ b/src/main/java/com/claudecode/command/impl/HeapdumpCommand.java @@ -2,7 +2,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; import com.claudecode.command.CommandUtils; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import java.lang.management.ManagementFactory; @@ -16,7 +16,7 @@ import java.time.format.DateTimeFormatter; /** * /heapdump 命令 —— JVM 堆转储(Java 独有优势)。 */ -public class HeapdumpCommand implements SlashCommand { +public class HeapdumpCommand extends BaseSlashCommand { @Override public String name() { return "heapdump"; } @@ -26,26 +26,26 @@ public class HeapdumpCommand implements SlashCommand { @Override public String execute(String args, CommandContext context) { - String trimmed = CommandUtils.parseArgs(args); + String trimmed = args(args); StringBuilder sb = new StringBuilder(); - sb.append(CommandUtils.header("📦", "JVM Heap Dump")); + sb.append(header("📦", "JVM Heap Dump")); if (trimmed.equals("info") || trimmed.isEmpty()) { MemoryMXBean memBean = ManagementFactory.getMemoryMXBean(); MemoryUsage heap = memBean.getHeapMemoryUsage(); MemoryUsage nonHeap = memBean.getNonHeapMemoryUsage(); - sb.append(CommandUtils.subtitle("Heap Memory")).append("\n"); + sb.append(subtitle("Heap Memory")).append("\n"); sb.append(" Used: ").append(CommandUtils.formatBytes(heap.getUsed())).append("\n"); sb.append(" Committed: ").append(CommandUtils.formatBytes(heap.getCommitted())).append("\n"); sb.append(" Max: ").append(CommandUtils.formatBytes(heap.getMax())).append("\n\n"); - sb.append(CommandUtils.subtitle("Non-Heap Memory")).append("\n"); + sb.append(subtitle("Non-Heap Memory")).append("\n"); sb.append(" Used: ").append(CommandUtils.formatBytes(nonHeap.getUsed())).append("\n"); sb.append(" Committed: ").append(CommandUtils.formatBytes(nonHeap.getCommitted())).append("\n\n"); - sb.append(CommandUtils.subtitle("Memory Pools")).append("\n"); + sb.append(subtitle("Memory Pools")).append("\n"); for (MemoryPoolMXBean pool : ManagementFactory.getMemoryPoolMXBeans()) { MemoryUsage usage = pool.getUsage(); if (usage != null && usage.getUsed() > 0) { @@ -66,12 +66,12 @@ public class HeapdumpCommand implements SlashCommand { com.sun.management.HotSpotDiagnosticMXBean.class); hotspot.dumpHeap(dumpPath.toString(), true); long fileSize = dumpPath.toFile().length(); - sb.append(CommandUtils.success("Heap dump saved to:")).append("\n"); + sb.append(success("Heap dump saved to:")).append("\n"); sb.append(" ").append(AnsiStyle.cyan(dumpPath.toString())).append("\n"); sb.append(" Size: ").append(CommandUtils.formatBytes(fileSize)).append("\n\n"); sb.append(AnsiStyle.dim(" Analyze with: jhat, MAT, or VisualVM")); } catch (Exception e) { - sb.append(CommandUtils.error("Failed to create heap dump: " + e.getMessage())).append("\n"); + sb.append(error("Failed to create heap dump: " + e.getMessage())).append("\n"); sb.append(AnsiStyle.dim(" Requires HotSpot JVM (OpenJDK or Oracle JDK)")); } @@ -87,7 +87,7 @@ public class HeapdumpCommand implements SlashCommand { sb.append(" Freed: ").append(AnsiStyle.green(CommandUtils.formatBytes(Math.max(0, freed)))).append("\n"); } else { - sb.append(CommandUtils.subtitle("Subcommands")).append("\n"); + sb.append(subtitle("Subcommands")).append("\n"); sb.append(" /heapdump Show memory pool info\n"); sb.append(" /heapdump dump Generate .hprof file\n"); sb.append(" /heapdump gc Trigger garbage collection\n"); diff --git a/src/main/java/com/claudecode/command/impl/HelpCommand.java b/src/main/java/com/claudecode/command/impl/HelpCommand.java index ca8c663..1538214 100644 --- a/src/main/java/com/claudecode/command/impl/HelpCommand.java +++ b/src/main/java/com/claudecode/command/impl/HelpCommand.java @@ -1,6 +1,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.command.SlashCommand; import com.claudecode.console.AnsiStyle; @@ -17,7 +18,7 @@ import java.util.stream.Collectors; *
  • /help [command] —— 显示特定命令详情
  • * */ -public class HelpCommand implements SlashCommand { +public class HelpCommand extends BaseSlashCommand { @Override public String name() { @@ -36,7 +37,7 @@ public class HelpCommand implements SlashCommand { @Override public String execute(String args, CommandContext context) { - String query = (args == null) ? "" : args.strip(); + String query = args(args); var allCommands = context.commandRegistry().getCommands(); // If a specific command name is given, show details diff --git a/src/main/java/com/claudecode/command/impl/HistoryCommand.java b/src/main/java/com/claudecode/command/impl/HistoryCommand.java index 6b9b7a9..c603f9b 100644 --- a/src/main/java/com/claudecode/command/impl/HistoryCommand.java +++ b/src/main/java/com/claudecode/command/impl/HistoryCommand.java @@ -1,7 +1,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import com.claudecode.core.ConversationPersistence; @@ -10,7 +10,7 @@ import com.claudecode.core.ConversationPersistence; *

    * 显示最近的对话记录,包括时间、摘要和消息数量。 */ -public class HistoryCommand implements SlashCommand { +public class HistoryCommand extends BaseSlashCommand { @Override public String name() { diff --git a/src/main/java/com/claudecode/command/impl/HooksCommand.java b/src/main/java/com/claudecode/command/impl/HooksCommand.java index 3e2f60f..19fd56c 100644 --- a/src/main/java/com/claudecode/command/impl/HooksCommand.java +++ b/src/main/java/com/claudecode/command/impl/HooksCommand.java @@ -1,7 +1,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import com.claudecode.core.HookManager; import com.claudecode.core.HookManager.HookRegistration; @@ -17,7 +17,7 @@ import java.util.stream.Collectors; * 按 Hook 类型分组展示,包含每个 Hook 的名称和优先级。 * 如果没有注册任何 Hook,则显示提示信息。 */ -public class HooksCommand implements SlashCommand { +public class HooksCommand extends BaseSlashCommand { @Override public String name() { diff --git a/src/main/java/com/claudecode/command/impl/InitCommand.java b/src/main/java/com/claudecode/command/impl/InitCommand.java index 20d0cbc..afdf0b7 100644 --- a/src/main/java/com/claudecode/command/impl/InitCommand.java +++ b/src/main/java/com/claudecode/command/impl/InitCommand.java @@ -1,14 +1,13 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.util.List; /** * /init 命令 —— 初始化项目 CLAUDE.md。 @@ -16,7 +15,7 @@ import java.util.List; * 对应 claude-code/src/commands/init.ts。 * 检测项目类型并生成 CLAUDE.md 模板文件。 */ -public class InitCommand implements SlashCommand { +public class InitCommand extends BaseSlashCommand { @Override public String name() { diff --git a/src/main/java/com/claudecode/command/impl/KeybindingsCommand.java b/src/main/java/com/claudecode/command/impl/KeybindingsCommand.java index 2bb4930..b58596e 100644 --- a/src/main/java/com/claudecode/command/impl/KeybindingsCommand.java +++ b/src/main/java/com/claudecode/command/impl/KeybindingsCommand.java @@ -1,7 +1,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import java.util.List; @@ -9,7 +9,7 @@ import java.util.List; /** * /keybindings 命令 —— 显示和配置快捷键。 */ -public class KeybindingsCommand implements SlashCommand { +public class KeybindingsCommand extends BaseSlashCommand { @Override public String name() { return "keybindings"; } diff --git a/src/main/java/com/claudecode/command/impl/McpCommand.java b/src/main/java/com/claudecode/command/impl/McpCommand.java index 78e5244..a0db39f 100644 --- a/src/main/java/com/claudecode/command/impl/McpCommand.java +++ b/src/main/java/com/claudecode/command/impl/McpCommand.java @@ -2,7 +2,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; import com.claudecode.command.CommandUtils; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import com.claudecode.mcp.McpClient; import com.claudecode.mcp.McpException; @@ -25,7 +25,7 @@ import java.util.*; *

  • {@code /mcp reload} —— 从配置文件重新加载
  • * */ -public class McpCommand implements SlashCommand { +public class McpCommand extends BaseSlashCommand { @Override public String name() { @@ -48,7 +48,7 @@ public class McpCommand implements SlashCommand { return AnsiStyle.red(" ❌ MCP manager not initialized"); } - String trimmed = CommandUtils.parseArgs(args); + String trimmed = args(args); if (trimmed.isEmpty()) { return showStatus(manager); } @@ -73,7 +73,7 @@ public class McpCommand implements SlashCommand { */ private String showStatus(McpManager manager) { StringBuilder sb = new StringBuilder(); - sb.append(CommandUtils.header("🔌", "MCP Server Status")); + sb.append(header("🔌", "MCP Server Status")); Map clients = manager.getClients(); if (clients.isEmpty()) { @@ -192,7 +192,7 @@ public class McpCommand implements SlashCommand { */ private String handleTools(McpManager manager, String args) { StringBuilder sb = new StringBuilder(); - sb.append(CommandUtils.header("🛠️", "MCP Tools")); + sb.append(header("🛠️", "MCP Tools")); String serverFilter = args.isEmpty() ? null : args.split("\\s+")[0]; @@ -231,7 +231,7 @@ public class McpCommand implements SlashCommand { */ private String handleResources(McpManager manager, String args) { StringBuilder sb = new StringBuilder(); - sb.append(CommandUtils.header("📦", "MCP Resources")); + sb.append(header("📦", "MCP Resources")); String serverFilter = args.isEmpty() ? null : args.split("\\s+")[0]; diff --git a/src/main/java/com/claudecode/command/impl/MemoryCommand.java b/src/main/java/com/claudecode/command/impl/MemoryCommand.java index 6408740..7ba6e55 100644 --- a/src/main/java/com/claudecode/command/impl/MemoryCommand.java +++ b/src/main/java/com/claudecode/command/impl/MemoryCommand.java @@ -1,7 +1,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import java.io.IOException; @@ -21,7 +21,7 @@ import java.util.List; *
  • /memory user —— 查看用户级 CLAUDE.md
  • * */ -public class MemoryCommand implements SlashCommand { +public class MemoryCommand extends BaseSlashCommand { @Override public String name() { @@ -40,7 +40,7 @@ public class MemoryCommand implements SlashCommand { @Override public String execute(String args, CommandContext context) { - args = args == null ? "" : args.strip(); + args = args(args); if (args.startsWith("add ")) { return handleAdd(args.substring(4).strip()); diff --git a/src/main/java/com/claudecode/command/impl/ModelCommand.java b/src/main/java/com/claudecode/command/impl/ModelCommand.java index c981ebc..53761d5 100644 --- a/src/main/java/com/claudecode/command/impl/ModelCommand.java +++ b/src/main/java/com/claudecode/command/impl/ModelCommand.java @@ -1,7 +1,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import java.util.List; @@ -12,7 +12,7 @@ import java.util.Map; *

    * 支持查看当前模型信息和切换到其他模型。 */ -public class ModelCommand implements SlashCommand { +public class ModelCommand extends BaseSlashCommand { private static final Map AVAILABLE_MODELS = Map.of( "sonnet", "claude-sonnet-4-20250514", diff --git a/src/main/java/com/claudecode/command/impl/OutputStyleCommand.java b/src/main/java/com/claudecode/command/impl/OutputStyleCommand.java index b982603..f269e0c 100644 --- a/src/main/java/com/claudecode/command/impl/OutputStyleCommand.java +++ b/src/main/java/com/claudecode/command/impl/OutputStyleCommand.java @@ -1,13 +1,13 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; /** * /output-style 命令 —— 输出风格设置。 */ -public class OutputStyleCommand implements SlashCommand { +public class OutputStyleCommand extends BaseSlashCommand { @Override public String name() { return "output-style"; } @@ -17,15 +17,15 @@ public class OutputStyleCommand implements SlashCommand { @Override public String execute(String args, CommandContext context) { - if (context.agentLoop() == null) { - return AnsiStyle.red(" ✗ No active session"); + if (requireAgentLoop(context) == null) { + return noSession(); } - var toolCtx = context.agentLoop().getToolContext(); + var toolCtx = toolCtx(context); String current = (String) toolCtx.get("OUTPUT_STYLE"); if (current == null) current = "markdown"; - String trimmed = (args == null) ? "" : args.trim().toLowerCase(); + String trimmed = args(args).toLowerCase(); if (trimmed.isEmpty()) { return "\n" + AnsiStyle.bold(" 📝 Output Style\n") diff --git a/src/main/java/com/claudecode/command/impl/PerformanceCommand.java b/src/main/java/com/claudecode/command/impl/PerformanceCommand.java index dd36363..5e18299 100644 --- a/src/main/java/com/claudecode/command/impl/PerformanceCommand.java +++ b/src/main/java/com/claudecode/command/impl/PerformanceCommand.java @@ -2,14 +2,13 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; import com.claudecode.command.CommandUtils; -import com.claudecode.command.SlashCommand; -import com.claudecode.console.AnsiStyle; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.telemetry.MetricsCollector; /** * /performance 命令 —— 性能统计。 */ -public class PerformanceCommand implements SlashCommand { +public class PerformanceCommand extends BaseSlashCommand { @Override public String name() { return "performance"; } @@ -25,7 +24,7 @@ public class PerformanceCommand implements SlashCommand { @Override public String execute(String args, CommandContext context) { StringBuilder sb = new StringBuilder(); - sb.append(CommandUtils.header("⚡", "Performance Statistics")); + sb.append(header("⚡", "Performance Statistics")); Runtime runtime = Runtime.getRuntime(); long totalMem = runtime.totalMemory(); @@ -33,14 +32,14 @@ public class PerformanceCommand implements SlashCommand { long usedMem = totalMem - freeMem; long maxMem = runtime.maxMemory(); - sb.append(CommandUtils.subtitle("Memory")).append("\n"); + sb.append(subtitle("Memory")).append("\n"); sb.append(" Used: ").append(CommandUtils.formatBytes(usedMem)).append("\n"); sb.append(" Allocated: ").append(CommandUtils.formatBytes(totalMem)).append("\n"); sb.append(" Max: ").append(CommandUtils.formatBytes(maxMem)).append("\n"); sb.append(" Usage: ").append(CommandUtils.progressBar((double) usedMem / maxMem, 20)).append("\n\n"); int threadCount = Thread.activeCount(); - sb.append(CommandUtils.subtitle("Threads")).append("\n"); + sb.append(subtitle("Threads")).append("\n"); sb.append(" Active: ").append(threadCount).append("\n"); sb.append(" Available: ").append(runtime.availableProcessors()).append(" CPUs\n\n"); @@ -50,14 +49,14 @@ public class PerformanceCommand implements SlashCommand { gcCount += gc.getCollectionCount(); gcTime += gc.getCollectionTime(); } - sb.append(CommandUtils.subtitle("GC")).append("\n"); + sb.append(subtitle("GC")).append("\n"); sb.append(" Collections: ").append(gcCount).append("\n"); sb.append(" Total time: ").append(CommandUtils.formatMillis(gcTime)).append("\n\n"); if (context.agentLoop() != null) { - Object metricsObj = context.agentLoop().getToolContext().get("METRICS_COLLECTOR"); + Object metricsObj = toolCtx(context).get("METRICS_COLLECTOR"); if (metricsObj instanceof MetricsCollector metrics) { - sb.append(CommandUtils.subtitle("Session Metrics")).append("\n"); + sb.append(subtitle("Session Metrics")).append("\n"); sb.append(" Duration: ").append(CommandUtils.formatDuration(metrics.getSessionDurationSeconds())).append("\n"); var toolUsage = metrics.getToolUsage(); if (!toolUsage.isEmpty()) { diff --git a/src/main/java/com/claudecode/command/impl/PermissionsCommand.java b/src/main/java/com/claudecode/command/impl/PermissionsCommand.java index 5245b53..31e2ad1 100644 --- a/src/main/java/com/claudecode/command/impl/PermissionsCommand.java +++ b/src/main/java/com/claudecode/command/impl/PermissionsCommand.java @@ -1,7 +1,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import com.claudecode.permission.PermissionSettings; import com.claudecode.permission.PermissionTypes; @@ -14,7 +14,7 @@ import java.util.List; * 对应 claude-code/src/commands/permissions.ts。 * 显示当前权限模式和规则列表。 */ -public class PermissionsCommand implements SlashCommand { +public class PermissionsCommand extends BaseSlashCommand { private final PermissionSettings settings; diff --git a/src/main/java/com/claudecode/command/impl/PlanCommand.java b/src/main/java/com/claudecode/command/impl/PlanCommand.java index 88374ab..86b0808 100644 --- a/src/main/java/com/claudecode/command/impl/PlanCommand.java +++ b/src/main/java/com/claudecode/command/impl/PlanCommand.java @@ -1,7 +1,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.permission.PermissionSettings; import com.claudecode.permission.PermissionTypes.PermissionMode; @@ -10,7 +10,7 @@ import com.claudecode.permission.PermissionTypes.PermissionMode; *

    * 切换计划模式开关。在计划模式下,AI只能分析不能修改。 */ -public class PlanCommand implements SlashCommand { +public class PlanCommand extends BaseSlashCommand { private final PermissionSettings permissionSettings; diff --git a/src/main/java/com/claudecode/command/impl/PluginCommand.java b/src/main/java/com/claudecode/command/impl/PluginCommand.java index aa29cf5..9a84ce9 100644 --- a/src/main/java/com/claudecode/command/impl/PluginCommand.java +++ b/src/main/java/com/claudecode/command/impl/PluginCommand.java @@ -1,7 +1,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.CommandUtils; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.command.SlashCommand; import com.claudecode.console.AnsiStyle; import com.claudecode.plugin.*; @@ -32,7 +32,7 @@ import java.util.Optional; * 通过 {@link com.claudecode.tool.ToolContext} 中 key 为 * {@code "PLUGIN_MANAGER"} 的共享状态获取 {@link PluginManager} 实例。 */ -public class PluginCommand implements SlashCommand { +public class PluginCommand extends BaseSlashCommand { @Override public String name() { @@ -56,7 +56,7 @@ public class PluginCommand implements SlashCommand { return AnsiStyle.red(" ✗ Plugin system not initialized"); } - String trimmed = CommandUtils.parseArgs(args); + String trimmed = args(args); // 无参数:列出所有插件 if (trimmed.isEmpty()) { @@ -88,7 +88,7 @@ public class PluginCommand implements SlashCommand { private String listPlugins(PluginManager manager) { List plugins = manager.getPlugins(); StringBuilder sb = new StringBuilder(); - sb.append(CommandUtils.header("🔌", "Loaded Plugins")); + sb.append(header("🔌", "Loaded Plugins")); if (plugins.isEmpty()) { sb.append(AnsiStyle.dim(" No plugins loaded.")).append("\n"); @@ -173,7 +173,7 @@ public class PluginCommand implements SlashCommand { Plugin p = info.plugin(); StringBuilder sb = new StringBuilder(); - sb.append(CommandUtils.header("🔌", "Plugin Details")); + sb.append(header("🔌", "Plugin Details")); sb.append(" ").append(AnsiStyle.bold("Name: ")).append(p.name()).append("\n"); sb.append(" ").append(AnsiStyle.bold("ID: ")).append(AnsiStyle.cyan(p.id())).append("\n"); @@ -360,7 +360,7 @@ public class PluginCommand implements SlashCommand { private PluginInstaller getInstaller(CommandContext context) { try { - Object obj = context.agentLoop().getToolContext().get("PLUGIN_INSTALLER"); + Object obj = toolCtx(context).get("PLUGIN_INSTALLER"); if (obj instanceof PluginInstaller pi) return pi; } catch (Exception ignored) {} return null; @@ -368,7 +368,7 @@ public class PluginCommand implements SlashCommand { private MarketplaceManager getMarketplace(CommandContext context) { try { - Object obj = context.agentLoop().getToolContext().get("MARKETPLACE_MANAGER"); + Object obj = toolCtx(context).get("MARKETPLACE_MANAGER"); if (obj instanceof MarketplaceManager mm) return mm; } catch (Exception ignored) {} return null; @@ -376,7 +376,7 @@ public class PluginCommand implements SlashCommand { private PluginAutoUpdate getAutoUpdate(CommandContext context) { try { - Object obj = context.agentLoop().getToolContext().get("PLUGIN_AUTO_UPDATE"); + Object obj = toolCtx(context).get("PLUGIN_AUTO_UPDATE"); if (obj instanceof PluginAutoUpdate pau) return pau; } catch (Exception ignored) {} return null; @@ -391,11 +391,11 @@ public class PluginCommand implements SlashCommand { * @return PluginManager 实例,未找到时返回 null */ private PluginManager getPluginManager(CommandContext context) { - if (context.agentLoop() == null) { + if (requireAgentLoop(context) == null) { return null; } try { - Object manager = context.agentLoop().getToolContext().get("PLUGIN_MANAGER"); + Object manager = toolCtx(context).get("PLUGIN_MANAGER"); if (manager instanceof PluginManager pm) { return pm; } diff --git a/src/main/java/com/claudecode/command/impl/PrivacyCommand.java b/src/main/java/com/claudecode/command/impl/PrivacyCommand.java index 8ffd4fc..936e678 100644 --- a/src/main/java/com/claudecode/command/impl/PrivacyCommand.java +++ b/src/main/java/com/claudecode/command/impl/PrivacyCommand.java @@ -1,13 +1,13 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; /** * /privacy 命令 —— 隐私设置查看和修改。 */ -public class PrivacyCommand implements SlashCommand { +public class PrivacyCommand extends BaseSlashCommand { @Override public String name() { return "privacy"; } @@ -17,16 +17,16 @@ public class PrivacyCommand implements SlashCommand { @Override public String execute(String args, CommandContext context) { - if (context.agentLoop() == null) { - return AnsiStyle.red(" ✗ No active session"); + if (requireAgentLoop(context) == null) { + return noSession(); } - var toolCtx = context.agentLoop().getToolContext(); + var toolCtx = toolCtx(context); boolean telemetryEnabled = Boolean.TRUE.equals(toolCtx.get("TELEMETRY_ENABLED")); boolean sessionLogging = !Boolean.FALSE.equals(toolCtx.get("SESSION_LOGGING")); boolean memoryPersist = !Boolean.FALSE.equals(toolCtx.get("MEMORY_PERSIST")); - String trimmed = (args == null) ? "" : args.trim().toLowerCase(); + String trimmed = args(args).toLowerCase(); if (trimmed.isEmpty()) { StringBuilder sb = new StringBuilder(); diff --git a/src/main/java/com/claudecode/command/impl/ReleaseNotesCommand.java b/src/main/java/com/claudecode/command/impl/ReleaseNotesCommand.java index 7d656e5..c1b9985 100644 --- a/src/main/java/com/claudecode/command/impl/ReleaseNotesCommand.java +++ b/src/main/java/com/claudecode/command/impl/ReleaseNotesCommand.java @@ -2,7 +2,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; import com.claudecode.command.CommandUtils; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import java.util.List; @@ -10,7 +10,7 @@ import java.util.List; /** * /release-notes 命令 —— 显示版本更新日志。 */ -public class ReleaseNotesCommand implements SlashCommand { +public class ReleaseNotesCommand extends BaseSlashCommand { @Override public String name() { return "release-notes"; } @@ -26,7 +26,7 @@ public class ReleaseNotesCommand implements SlashCommand { @Override public String execute(String args, CommandContext context) { StringBuilder sb = new StringBuilder(); - sb.append(CommandUtils.header("📋", "Release Notes")); + sb.append(header("📋", "Release Notes")); sb.append(AnsiStyle.bold(" v0.4.0 — Phase 4: Commands, Tools & Services\n")); sb.append(CommandUtils.separator(45)).append("\n"); diff --git a/src/main/java/com/claudecode/command/impl/RenameCommand.java b/src/main/java/com/claudecode/command/impl/RenameCommand.java index c564b49..8ef1712 100644 --- a/src/main/java/com/claudecode/command/impl/RenameCommand.java +++ b/src/main/java/com/claudecode/command/impl/RenameCommand.java @@ -1,7 +1,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import java.util.List; @@ -12,7 +12,7 @@ import java.util.List; * 对应 claude-code 的 /rename 命令,给当前会话设置一个友好名称。 * 会话名称在 UI 标题栏和会话列表中显示。 */ -public class RenameCommand implements SlashCommand { +public class RenameCommand extends BaseSlashCommand { /** 当前会话名称(进程级别) */ private static volatile String sessionName = null; @@ -34,7 +34,7 @@ public class RenameCommand implements SlashCommand { @Override public String execute(String args, CommandContext context) { - args = args == null ? "" : args.strip(); + args = args(args); if (args.isBlank()) { if (sessionName == null) { diff --git a/src/main/java/com/claudecode/command/impl/ResetLimitsCommand.java b/src/main/java/com/claudecode/command/impl/ResetLimitsCommand.java index 12d9bb4..a11aca9 100644 --- a/src/main/java/com/claudecode/command/impl/ResetLimitsCommand.java +++ b/src/main/java/com/claudecode/command/impl/ResetLimitsCommand.java @@ -1,8 +1,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.CommandUtils; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import com.claudecode.core.RateLimiter; @@ -11,7 +10,7 @@ import java.util.List; /** * /reset-limits 命令 —— 重置速率限制。 */ -public class ResetLimitsCommand implements SlashCommand { +public class ResetLimitsCommand extends BaseSlashCommand { @Override public String name() { return "reset-limits"; } @@ -25,13 +24,13 @@ public class ResetLimitsCommand implements SlashCommand { @Override public String execute(String args, CommandContext context) { StringBuilder sb = new StringBuilder(); - sb.append(CommandUtils.header("🔄", "Rate Limit Reset")); + sb.append(header("🔄", "Rate Limit Reset")); - if (context.agentLoop() == null) { + if (requireAgentLoop(context) == null) { return sb.append(" No active agent loop\n").toString(); } - var toolCtx = context.agentLoop().getToolContext(); + var toolCtx = toolCtx(context); Object limiterObj = toolCtx.get("RATE_LIMITER"); if (limiterObj instanceof RateLimiter limiter) { diff --git a/src/main/java/com/claudecode/command/impl/ResumeCommand.java b/src/main/java/com/claudecode/command/impl/ResumeCommand.java index ebf9dc7..619054f 100644 --- a/src/main/java/com/claudecode/command/impl/ResumeCommand.java +++ b/src/main/java/com/claudecode/command/impl/ResumeCommand.java @@ -1,7 +1,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import com.claudecode.core.ConversationPersistence; import com.claudecode.core.ConversationPersistence.ConversationSummary; @@ -21,7 +21,7 @@ import java.util.List; *

  • /resume [序号] —— 恢复指定序号的对话
  • * */ -public class ResumeCommand implements SlashCommand { +public class ResumeCommand extends BaseSlashCommand { @Override public String name() { @@ -38,7 +38,7 @@ public class ResumeCommand implements SlashCommand { ConversationPersistence persistence = new ConversationPersistence(); List conversations = persistence.listConversations(); - args = args == null ? "" : args.strip(); + args = args(args); if (conversations.isEmpty()) { return AnsiStyle.yellow(" ⚠ No saved conversations\n") diff --git a/src/main/java/com/claudecode/command/impl/ReviewCommand.java b/src/main/java/com/claudecode/command/impl/ReviewCommand.java index edb9260..4b21424 100644 --- a/src/main/java/com/claudecode/command/impl/ReviewCommand.java +++ b/src/main/java/com/claudecode/command/impl/ReviewCommand.java @@ -1,7 +1,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import java.io.BufferedReader; @@ -23,7 +23,7 @@ import java.util.stream.Collectors; *

    * 获取 diff 内容后,发送给 AI 模型进行代码审查。 */ -public class ReviewCommand implements SlashCommand { +public class ReviewCommand extends BaseSlashCommand { @Override public String name() { @@ -42,7 +42,7 @@ public class ReviewCommand implements SlashCommand { @Override public String execute(String args, CommandContext context) { - if (context.agentLoop() == null) { + if (requireAgentLoop(context) == null) { return AnsiStyle.red(" ✗ AgentLoop unavailable, cannot perform code review."); } diff --git a/src/main/java/com/claudecode/command/impl/RewindCommand.java b/src/main/java/com/claudecode/command/impl/RewindCommand.java index 8b19c75..8a6222b 100644 --- a/src/main/java/com/claudecode/command/impl/RewindCommand.java +++ b/src/main/java/com/claudecode/command/impl/RewindCommand.java @@ -1,7 +1,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import org.springframework.ai.chat.messages.Message; @@ -20,7 +20,7 @@ import java.util.List; * 使用 {@code agentLoop.getMessageHistory()} 获取当前消息历史, * 然后通过 {@code agentLoop.replaceHistory()} 用截断后的列表替换。 */ -public class RewindCommand implements SlashCommand { +public class RewindCommand extends BaseSlashCommand { /** 每个消息对包含的消息数(用户消息 + 助手消息) */ private static final int MESSAGES_PER_PAIR = 2; @@ -37,7 +37,7 @@ public class RewindCommand implements SlashCommand { @Override public String execute(String args, CommandContext context) { - if (context.agentLoop() == null) { + if (requireAgentLoop(context) == null) { return AnsiStyle.red(" ✗ AgentLoop unavailable."); } diff --git a/src/main/java/com/claudecode/command/impl/SandboxCommand.java b/src/main/java/com/claudecode/command/impl/SandboxCommand.java index 04ef2f9..b963023 100644 --- a/src/main/java/com/claudecode/command/impl/SandboxCommand.java +++ b/src/main/java/com/claudecode/command/impl/SandboxCommand.java @@ -1,17 +1,15 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.CommandUtils; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; -import java.util.List; /** * /sandbox 命令 —— 沙箱模式切换。 * 控制工具执行的安全隔离级别。 */ -public class SandboxCommand implements SlashCommand { +public class SandboxCommand extends BaseSlashCommand { @Override public String name() { return "sandbox"; } @@ -21,16 +19,16 @@ public class SandboxCommand implements SlashCommand { @Override public String execute(String args, CommandContext context) { - String trimmed = CommandUtils.parseArgs(args); + String trimmed = args(args); StringBuilder sb = new StringBuilder(); - sb.append(CommandUtils.header("🏖", "Sandbox Mode")); + sb.append(header("🏖", "Sandbox Mode")); - if (context.agentLoop() == null) { + if (requireAgentLoop(context) == null) { return sb.append(" No active agent loop\n").toString(); } - var toolCtx = context.agentLoop().getToolContext(); + var toolCtx = toolCtx(context); if (trimmed.equals("on") || trimmed.equals("enable") || trimmed.equals("strict")) { toolCtx.set("SANDBOX_MODE", "strict"); diff --git a/src/main/java/com/claudecode/command/impl/SecurityReviewCommand.java b/src/main/java/com/claudecode/command/impl/SecurityReviewCommand.java index 2c19428..c10891c 100644 --- a/src/main/java/com/claudecode/command/impl/SecurityReviewCommand.java +++ b/src/main/java/com/claudecode/command/impl/SecurityReviewCommand.java @@ -1,7 +1,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import java.io.BufferedReader; @@ -25,7 +25,7 @@ import java.util.stream.Collectors; *

  • 依赖安全问题
  • * */ -public class SecurityReviewCommand implements SlashCommand { +public class SecurityReviewCommand extends BaseSlashCommand { @Override public String name() { @@ -44,7 +44,7 @@ public class SecurityReviewCommand implements SlashCommand { @Override public String execute(String args, CommandContext context) { - if (context.agentLoop() == null) { + if (requireAgentLoop(context) == null) { return AnsiStyle.red(" ✗ AgentLoop unavailable, cannot perform security review."); } diff --git a/src/main/java/com/claudecode/command/impl/SessionCommand.java b/src/main/java/com/claudecode/command/impl/SessionCommand.java index bb4b7e4..3247a88 100644 --- a/src/main/java/com/claudecode/command/impl/SessionCommand.java +++ b/src/main/java/com/claudecode/command/impl/SessionCommand.java @@ -1,7 +1,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import java.io.IOException; @@ -24,7 +24,7 @@ import java.util.List; *
  • /session export —— 导出会话为 JSON
  • * */ -public class SessionCommand implements SlashCommand { +public class SessionCommand extends BaseSlashCommand { private static final DateTimeFormatter FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") @@ -49,7 +49,7 @@ public class SessionCommand implements SlashCommand { @Override public String execute(String args, CommandContext context) { - args = args == null ? "" : args.strip(); + args = args(args); if (args.startsWith("save")) { String sessionName = args.length() > 5 ? args.substring(5).strip() : ""; diff --git a/src/main/java/com/claudecode/command/impl/SkillsCommand.java b/src/main/java/com/claudecode/command/impl/SkillsCommand.java index e7468d8..63cdada 100644 --- a/src/main/java/com/claudecode/command/impl/SkillsCommand.java +++ b/src/main/java/com/claudecode/command/impl/SkillsCommand.java @@ -2,7 +2,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; import com.claudecode.command.CommandUtils; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import com.claudecode.context.SkillLoader; @@ -14,7 +14,7 @@ import java.util.List; *

    * 扫描并显示从用户级、项目级和命令目录加载的技能文件。 */ -public class SkillsCommand implements SlashCommand { +public class SkillsCommand extends BaseSlashCommand { @Override public String name() { @@ -51,7 +51,7 @@ public class SkillsCommand implements SlashCommand { } StringBuilder sb = new StringBuilder(); - sb.append(CommandUtils.header("🎯", "Available Skills")); + sb.append(header("🎯", "Available Skills")); if (skills.isEmpty()) { sb.append(AnsiStyle.dim(" (No available skills)\n\n")); @@ -89,7 +89,7 @@ public class SkillsCommand implements SlashCommand { private String formatSkillDetail(SkillLoader.Skill skill) { StringBuilder sb = new StringBuilder(); - sb.append(CommandUtils.header("🎯", "Skill: " + skill.name())); + sb.append(header("🎯", "Skill: " + skill.name())); sb.append(" ").append(AnsiStyle.bold("Source: ")).append(skill.source()).append("\n"); if (!skill.description().isEmpty()) { diff --git a/src/main/java/com/claudecode/command/impl/StatsCommand.java b/src/main/java/com/claudecode/command/impl/StatsCommand.java index df6c639..5f12894 100644 --- a/src/main/java/com/claudecode/command/impl/StatsCommand.java +++ b/src/main/java/com/claudecode/command/impl/StatsCommand.java @@ -1,7 +1,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import com.claudecode.core.TokenTracker; @@ -23,7 +23,7 @@ import java.util.concurrent.TimeUnit; *

  • JVM 运行时长
  • * */ -public class StatsCommand implements SlashCommand { +public class StatsCommand extends BaseSlashCommand { @Override public String name() { @@ -37,7 +37,7 @@ public class StatsCommand implements SlashCommand { @Override public String execute(String args, CommandContext context) { - if (context.agentLoop() == null) { + if (requireAgentLoop(context) == null) { return AnsiStyle.red(" ✗ AgentLoop unavailable."); } diff --git a/src/main/java/com/claudecode/command/impl/StatusCommand.java b/src/main/java/com/claudecode/command/impl/StatusCommand.java index cf27f06..3eacadc 100644 --- a/src/main/java/com/claudecode/command/impl/StatusCommand.java +++ b/src/main/java/com/claudecode/command/impl/StatusCommand.java @@ -2,7 +2,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; import com.claudecode.command.CommandUtils; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import com.claudecode.core.TokenTracker; @@ -15,7 +15,7 @@ import java.time.Instant; *

    * 展示当前模型、Token 用量、工具数、消息数、内存和运行时间等信息。 */ -public class StatusCommand implements SlashCommand { +public class StatusCommand extends BaseSlashCommand { private final Instant startTime = Instant.now(); @@ -32,7 +32,7 @@ public class StatusCommand implements SlashCommand { @Override public String execute(String args, CommandContext context) { StringBuilder sb = new StringBuilder(); - sb.append(CommandUtils.header("📊", "Session Status")); + sb.append(header("📊", "Session Status")); // 模型信息 TokenTracker tracker = context.agentLoop().getTokenTracker(); diff --git a/src/main/java/com/claudecode/command/impl/TagCommand.java b/src/main/java/com/claudecode/command/impl/TagCommand.java index 3d9b093..8384b84 100644 --- a/src/main/java/com/claudecode/command/impl/TagCommand.java +++ b/src/main/java/com/claudecode/command/impl/TagCommand.java @@ -1,7 +1,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import org.springframework.ai.chat.messages.Message; @@ -25,7 +25,7 @@ import java.util.Map; *

  • {@code /tag goto } —— 回溯到指定标签位置
  • * */ -public class TagCommand implements SlashCommand { +public class TagCommand extends BaseSlashCommand { /** 静态标签存储:标签名称 -> 标签信息 */ private static final Map tags = new LinkedHashMap<>(); @@ -42,7 +42,7 @@ public class TagCommand implements SlashCommand { @Override public String execute(String args, CommandContext context) { - if (context.agentLoop() == null) { + if (requireAgentLoop(context) == null) { return AnsiStyle.red(" ✗ AgentLoop unavailable."); } diff --git a/src/main/java/com/claudecode/command/impl/TasksCommand.java b/src/main/java/com/claudecode/command/impl/TasksCommand.java index 4d86e38..a43ffb0 100644 --- a/src/main/java/com/claudecode/command/impl/TasksCommand.java +++ b/src/main/java/com/claudecode/command/impl/TasksCommand.java @@ -2,7 +2,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; import com.claudecode.command.CommandUtils; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import com.claudecode.core.TaskManager; import com.claudecode.core.TaskManager.TaskInfo; @@ -18,7 +18,7 @@ import java.util.List; * 对应 claude-code 中的任务管理 UI。 * 显示所有任务的状态、创建时间和结果摘要。 */ -public class TasksCommand implements SlashCommand { +public class TasksCommand extends BaseSlashCommand { private final TaskManager taskManager; @@ -39,7 +39,7 @@ public class TasksCommand implements SlashCommand { @Override public String execute(String args, CommandContext context) { List tasks; - String filter = CommandUtils.parseArgs(args); + String filter = args(args); // Optional status filter if (!filter.isEmpty()) { diff --git a/src/main/java/com/claudecode/command/impl/ThemeCommand.java b/src/main/java/com/claudecode/command/impl/ThemeCommand.java index 84356e3..e3d2f45 100644 --- a/src/main/java/com/claudecode/command/impl/ThemeCommand.java +++ b/src/main/java/com/claudecode/command/impl/ThemeCommand.java @@ -1,13 +1,13 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; /** * /theme 命令 —— 切换主题(dark/light/auto)。 */ -public class ThemeCommand implements SlashCommand { +public class ThemeCommand extends BaseSlashCommand { @Override public String name() { return "theme"; } @@ -17,15 +17,15 @@ public class ThemeCommand implements SlashCommand { @Override public String execute(String args, CommandContext context) { - if (context.agentLoop() == null) { - return AnsiStyle.red(" ✗ No active session"); + if (requireAgentLoop(context) == null) { + return noSession(); } - var toolCtx = context.agentLoop().getToolContext(); + var toolCtx = toolCtx(context); String current = (String) toolCtx.get("THEME"); if (current == null) current = "dark"; - String trimmed = (args == null) ? "" : args.trim().toLowerCase(); + String trimmed = args(args).toLowerCase(); if (trimmed.isEmpty()) { return "\n" + AnsiStyle.bold(" 🎨 Theme Settings\n") diff --git a/src/main/java/com/claudecode/command/impl/TipsCommand.java b/src/main/java/com/claudecode/command/impl/TipsCommand.java index 08d98b6..daf985d 100644 --- a/src/main/java/com/claudecode/command/impl/TipsCommand.java +++ b/src/main/java/com/claudecode/command/impl/TipsCommand.java @@ -1,7 +1,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import java.util.List; @@ -10,7 +10,7 @@ import java.util.Random; /** * /tips 命令 —— 显示使用技巧和建议。 */ -public class TipsCommand implements SlashCommand { +public class TipsCommand extends BaseSlashCommand { private static final List TIPS = List.of( "Use /compact when the conversation gets long to reduce token usage", @@ -45,7 +45,7 @@ public class TipsCommand implements SlashCommand { @Override public String execute(String args, CommandContext context) { - String trimmed = (args == null) ? "" : args.trim(); + String trimmed = args(args); if ("all".equals(trimmed)) { StringBuilder sb = new StringBuilder(); diff --git a/src/main/java/com/claudecode/command/impl/TraceCommand.java b/src/main/java/com/claudecode/command/impl/TraceCommand.java index 9f07f67..516f22b 100644 --- a/src/main/java/com/claudecode/command/impl/TraceCommand.java +++ b/src/main/java/com/claudecode/command/impl/TraceCommand.java @@ -1,20 +1,18 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.CommandUtils; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import java.time.Instant; import java.util.LinkedList; -import java.util.List; import java.util.concurrent.ConcurrentLinkedDeque; /** * /trace 命令 —— 请求/响应追踪。 * 显示 API 调用追踪信息(模型调用、tool 调用链等)。 */ -public class TraceCommand implements SlashCommand { +public class TraceCommand extends BaseSlashCommand { @Override public String name() { return "trace"; } @@ -24,16 +22,16 @@ public class TraceCommand implements SlashCommand { @Override public String execute(String args, CommandContext context) { - String trimmed = CommandUtils.parseArgs(args); + String trimmed = args(args); StringBuilder sb = new StringBuilder(); - sb.append(CommandUtils.header("🔍", "Request Tracing")); + sb.append(header("🔍", "Request Tracing")); - if (context.agentLoop() == null) { + if (requireAgentLoop(context) == null) { return sb.append(" No active agent loop\n").toString(); } - var toolCtx = context.agentLoop().getToolContext(); + var toolCtx = toolCtx(context); if (trimmed.equals("on") || trimmed.equals("enable")) { toolCtx.set("TRACE_ENABLED", true); @@ -69,7 +67,7 @@ public class TraceCommand implements SlashCommand { // Show recent conversation turns sb.append("\n").append(AnsiStyle.bold(" Conversation State\n")); - sb.append(" Session ID: ").append(context.agentLoop().getToolContext() + sb.append(" Session ID: ").append(toolCtx(context) .get("SESSION_ID") != null ? toolCtx.get("SESSION_ID") : "default").append("\n"); sb.append("\n").append(AnsiStyle.bold(" Subcommands\n")); diff --git a/src/main/java/com/claudecode/command/impl/UsageCommand.java b/src/main/java/com/claudecode/command/impl/UsageCommand.java index f8836dd..aaac535 100644 --- a/src/main/java/com/claudecode/command/impl/UsageCommand.java +++ b/src/main/java/com/claudecode/command/impl/UsageCommand.java @@ -1,14 +1,14 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; import com.claudecode.core.TokenTracker; /** * /usage 命令 —— 详细 token 和费用统计。 */ -public class UsageCommand implements SlashCommand { +public class UsageCommand extends BaseSlashCommand { @Override public String name() { return "usage"; } @@ -18,8 +18,8 @@ public class UsageCommand implements SlashCommand { @Override public String execute(String args, CommandContext context) { - if (context.agentLoop() == null) { - return AnsiStyle.red(" ✗ No active session"); + if (requireAgentLoop(context) == null) { + return noSession(); } TokenTracker tracker = context.agentLoop().getTokenTracker(); diff --git a/src/main/java/com/claudecode/command/impl/VersionCommand.java b/src/main/java/com/claudecode/command/impl/VersionCommand.java index 53ab9c9..0990e98 100644 --- a/src/main/java/com/claudecode/command/impl/VersionCommand.java +++ b/src/main/java/com/claudecode/command/impl/VersionCommand.java @@ -1,8 +1,7 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.CommandUtils; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; /** @@ -10,7 +9,7 @@ import com.claudecode.console.AnsiStyle; *

    * 展示 Claude Code Java 版本、运行时环境等信息。 */ -public class VersionCommand implements SlashCommand { +public class VersionCommand extends BaseSlashCommand { /** 当前版本号 */ public static final String VERSION = "1.0.0"; @@ -29,7 +28,7 @@ public class VersionCommand implements SlashCommand { @Override public String execute(String args, CommandContext context) { StringBuilder sb = new StringBuilder(); - sb.append(CommandUtils.header("🏷️", "Claude Code Java")); + sb.append(header("🏷️", "Claude Code Java")); sb.append(" ").append(AnsiStyle.bold("Version: ")) .append(AnsiStyle.cyan("v" + VERSION)).append("\n"); diff --git a/src/main/java/com/claudecode/command/impl/VimCommand.java b/src/main/java/com/claudecode/command/impl/VimCommand.java index 1c6e901..ca895ed 100644 --- a/src/main/java/com/claudecode/command/impl/VimCommand.java +++ b/src/main/java/com/claudecode/command/impl/VimCommand.java @@ -1,13 +1,13 @@ package com.claudecode.command.impl; import com.claudecode.command.CommandContext; -import com.claudecode.command.SlashCommand; +import com.claudecode.command.BaseSlashCommand; import com.claudecode.console.AnsiStyle; /** * /vim 命令 —— 切换 Vi 编辑模式(JLine vi-mode)。 */ -public class VimCommand implements SlashCommand { +public class VimCommand extends BaseSlashCommand { @Override public String name() { return "vim"; } @@ -17,14 +17,14 @@ public class VimCommand implements SlashCommand { @Override public String execute(String args, CommandContext context) { - if (context.agentLoop() == null) { - return AnsiStyle.red(" ✗ No active session"); + if (requireAgentLoop(context) == null) { + return noSession(); } - var toolCtx = context.agentLoop().getToolContext(); + var toolCtx = toolCtx(context); boolean current = Boolean.TRUE.equals(toolCtx.get("VIM_MODE")); - String trimmed = (args == null) ? "" : args.trim(); + String trimmed = args(args); boolean newMode = switch (trimmed) { case "on", "enable" -> true; case "off", "disable" -> false; diff --git a/weather_example.py b/weather_example.py new file mode 100644 index 0000000..e7d3447 --- /dev/null +++ b/weather_example.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +""" +使用OpenWeatherMap API查询天气的示例代码 +需要先注册获取API密钥:https://openweathermap.org/api +""" + +import requests +import json +import sys + +def get_weather_by_city(city_name, api_key, units="metric"): + """ + 根据城市名称查询天气 + + Args: + city_name: 城市名称(如:"Beijing") + api_key: OpenWeatherMap API密钥 + units: 温度单位,"metric"为摄氏度,"imperial"为华氏度 + + Returns: + 天气数据的字典 + """ + base_url = "http://api.openweathermap.org/data/2.5/weather" + + params = { + "q": city_name, + "appid": api_key, + "units": units, + "lang": "zh_cn" # 中文返回 + } + + try: + response = requests.get(base_url, params=params, timeout=10) + response.raise_for_status() + return response.json() + except requests.exceptions.RequestException as e: + print(f"请求错误: {e}") + return None + except json.JSONDecodeError as e: + print(f"JSON解析错误: {e}") + return None + +def get_weather_by_coords(lat, lon, api_key, units="metric"): + """ + 根据经纬度坐标查询天气 + + Args: + lat: 纬度 + lon: 经度 + api_key: OpenWeatherMap API密钥 + units: 温度单位 + + Returns: + 天气数据的字典 + """ + base_url = "http://api.openweathermap.org/data/2.5/weather" + + params = { + "lat": lat, + "lon": lon, + "appid": api_key, + "units": units, + "lang": "zh_cn" + } + + try: + response = requests.get(base_url, params=params, timeout=10) + response.raise_for_status() + return response.json() + except requests.exceptions.RequestException as e: + print(f"请求错误: {e}") + return None + except json.JSONDecodeError as e: + print(f"JSON解析错误: {e}") + return None + +def display_weather(weather_data): + """ + 显示天气信息 + + Args: + weather_data: 天气数据字典 + """ + if not weather_data or "cod" in weather_data and weather_data["cod"] != 200: + print("无法获取天气数据") + if weather_data and "message" in weather_data: + print(f"错误信息: {weather_data['message']}") + return + + print("\n=== 天气信息 ===") + print(f"城市: {weather_data.get('name', '未知')}") + + if "sys" in weather_data: + country = weather_data["sys"].get("country", "") + print(f"国家: {country}") + + if "main" in weather_data: + main = weather_data["main"] + print(f"温度: {main.get('temp', '未知')}°C") + print(f"体感温度: {main.get('feels_like', '未知')}°C") + print(f"最低温度: {main.get('temp_min', '未知')}°C") + print(f"最高温度: {main.get('temp_max', '未知')}°C") + print(f"湿度: {main.get('humidity', '未知')}%") + print(f"气压: {main.get('pressure', '未知')} hPa") + + if "weather" in weather_data and len(weather_data["weather"]) > 0: + weather = weather_data["weather"][0] + print(f"天气状况: {weather.get('description', '未知')}") + print(f"天气图标: http://openweathermap.org/img/wn/{weather.get('icon', '')}@2x.png") + + if "wind" in weather_data: + wind = weather_data["wind"] + print(f"风速: {wind.get('speed', '未知')} m/s") + if "deg" in wind: + print(f"风向: {wind.get('deg', '未知')}°") + + if "clouds" in weather_data: + print(f"云量: {weather_data['clouds'].get('all', '未知')}%") + + if "visibility" in weather_data: + visibility = weather_data["visibility"] + if visibility >= 1000: + print(f"能见度: {visibility/1000:.1f} km") + else: + print(f"能见度: {visibility} m") + + if "dt" in weather_data: + from datetime import datetime + dt = datetime.fromtimestamp(weather_data["dt"]) + print(f"数据时间: {dt.strftime('%Y-%m-%d %H:%M:%S')}") + + if "timezone" in weather_data: + offset = weather_data["timezone"] / 3600 + print(f"时区: UTC{offset:+g}") + +def main(): + """ + 主函数:演示如何使用API + """ + print("OpenWeatherMap API 天气查询示例") + print("=" * 40) + + # 您需要在这里填写您的API密钥 + API_KEY = "YOUR_API_KEY_HERE" # 请替换为您的实际API密钥 + + if API_KEY == "YOUR_API_KEY_HERE": + print("\n⚠️ 请先注册OpenWeatherMap并获取API密钥:") + print(" https://openweathermap.org/api") + print("\n注册步骤:") + print("1. 访问 https://openweathermap.org/api") + print("2. 点击 'Subscribe' 或 'Sign Up'") + print("3. 注册免费账户(Current Weather Data API 免费套餐每天可调用60次)") + print("4. 在控制台获取您的API密钥") + print("\n获取API密钥后,请替换代码中的 'YOUR_API_KEY_HERE'") + return + + # 示例:查询北京的天气 + print("\n示例1:查询北京天气") + weather_data = get_weather_by_city("Beijing", API_KEY) + if weather_data: + display_weather(weather_data) + + # 示例:使用经纬度查询(纽约的坐标) + print("\n" + "=" * 40) + print("示例2:使用经纬度查询纽约天气") + weather_data = get_weather_by_coords(40.7128, -74.0060, API_KEY) + if weather_data: + display_weather(weather_data) + +if __name__ == "__main__": + main() \ No newline at end of file