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>
This commit is contained in:
abel533
2026-04-05 13:16:55 +08:00
co-authored by Copilot
parent 12a443c9a9
commit 9f4023bdf0
57 changed files with 558 additions and 217 deletions
+69
View File
@@ -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()
@@ -0,0 +1,110 @@
package com.claudecode.command;
import com.claudecode.core.AgentLoop;
import com.claudecode.tool.ToolContext;
import java.util.List;
/**
* 命令抽象基类 —— 消除 54 个命令实现中的重复模式。
* <p>
* 提供:
* <ul>
* <li>{@link #args(String)} — 安全解析参数</li>
* <li>{@link #requireAgentLoop(CommandContext)} — null 检查并返回错误</li>
* <li>{@link #toolCtx(CommandContext)} — 快速获取 ToolContext</li>
* <li>{@link #success(String)}, {@link #error(String)}, {@link #warning(String)} — 格式化消息</li>
* <li>默认 {@link #aliases()} 返回空列表</li>
* </ul>
*
* 子类只需实现 {@link #name()}, {@link #description()}, {@link #execute(String, CommandContext)}。
* 可选覆盖 {@link #aliases()}。
*/
public abstract class BaseSlashCommand implements SlashCommand {
@Override
public List<String> aliases() {
return List.of();
}
// ==================== 参数解析 ====================
/**
* 安全解析命令参数(null → 空字符串,去首尾空白)。
*/
protected String args(String raw) {
return CommandUtils.parseArgs(raw);
}
// ==================== 上下文访问 ====================
/**
* 检查 AgentLoop 是否可用。如果为 null,返回错误消息;否则返回 null。
* 典型用法:
* <pre>
* AgentLoop loop = requireAgentLoop(context);
* if (loop == null) return noSession();
* </pre>
*/
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);
}
}
@@ -1,7 +1,7 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import com.claudecode.core.TaskManager; import com.claudecode.core.TaskManager;
@@ -20,7 +20,7 @@ import java.util.List;
* <li>/agent stop-all —— 停止所有 agent</li> * <li>/agent stop-all —— 停止所有 agent</li>
* </ul> * </ul>
*/ */
public class AgentCommand implements SlashCommand { public class AgentCommand extends BaseSlashCommand {
@Override @Override
public String name() { public String name() {
@@ -39,7 +39,7 @@ public class AgentCommand implements SlashCommand {
@Override @Override
public String execute(String args, CommandContext context) { public String execute(String args, CommandContext context) {
args = args == null ? "" : args.strip(); args = args(args);
// Get TaskManager from AgentLoop's tool context // Get TaskManager from AgentLoop's tool context
TaskManager taskManager = getTaskManager(context); TaskManager taskManager = getTaskManager(context);
@@ -183,8 +183,8 @@ public class AgentCommand implements SlashCommand {
private TaskManager getTaskManager(CommandContext context) { private TaskManager getTaskManager(CommandContext context) {
// The TaskManager is typically accessible from the tool context // The TaskManager is typically accessible from the tool context
// via AgentLoop's tool context. Try to access it. // via AgentLoop's tool context. Try to access it.
if (context.agentLoop() == null) return null; if (requireAgentLoop(context) == null) return null;
var toolContext = context.agentLoop().getToolContext(); var toolContext = toolCtx(context);
if (toolContext == null) return null; if (toolContext == null) return null;
return toolContext.getOrDefault("TASK_MANAGER", null); return toolContext.getOrDefault("TASK_MANAGER", null);
} }
@@ -1,7 +1,7 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.Message;
@@ -26,7 +26,7 @@ import java.util.Map;
* <li>{@code /branch delete <name>} —— 删除指定分支</li> * <li>{@code /branch delete <name>} —— 删除指定分支</li>
* </ul> * </ul>
*/ */
public class BranchCommand implements SlashCommand { public class BranchCommand extends BaseSlashCommand {
/** 静态分支存储:分支名称 -> 消息快照 */ /** 静态分支存储:分支名称 -> 消息快照 */
private static final Map<String, BranchSnapshot> branches = new LinkedHashMap<>(); private static final Map<String, BranchSnapshot> branches = new LinkedHashMap<>();
@@ -43,7 +43,7 @@ public class BranchCommand implements SlashCommand {
@Override @Override
public String execute(String args, CommandContext context) { public String execute(String args, CommandContext context) {
if (context.agentLoop() == null) { if (requireAgentLoop(context) == null) {
return AnsiStyle.red(" ✗ AgentLoop unavailable."); return AnsiStyle.red(" ✗ AgentLoop unavailable.");
} }
@@ -1,13 +1,13 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
/** /**
* /brief 命令 —— 切换简洁输出模式。 * /brief 命令 —— 切换简洁输出模式。
*/ */
public class BriefCommand implements SlashCommand { public class BriefCommand extends BaseSlashCommand {
@Override @Override
public String name() { return "brief"; } public String name() { return "brief"; }
@@ -17,14 +17,14 @@ public class BriefCommand implements SlashCommand {
@Override @Override
public String execute(String args, CommandContext context) { public String execute(String args, CommandContext context) {
if (context.agentLoop() == null) { if (requireAgentLoop(context) == null) {
return AnsiStyle.red(" ✗ No active session"); return noSession();
} }
var toolCtx = context.agentLoop().getToolContext(); var toolCtx = toolCtx(context);
boolean current = Boolean.TRUE.equals(toolCtx.get("BRIEF_MODE")); boolean current = Boolean.TRUE.equals(toolCtx.get("BRIEF_MODE"));
String trimmed = (args == null) ? "" : args.trim(); String trimmed = args(args);
boolean newMode = switch (trimmed) { boolean newMode = switch (trimmed) {
case "on", "enable", "true" -> true; case "on", "enable", "true" -> true;
case "off", "disable", "false" -> false; case "off", "disable", "false" -> false;
@@ -1,13 +1,13 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
/** /**
* /clear 命令 —— 清除对话历史。 * /clear 命令 —— 清除对话历史。
*/ */
public class ClearCommand implements SlashCommand { public class ClearCommand extends BaseSlashCommand {
@Override @Override
public String name() { public String name() {
@@ -1,7 +1,7 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import java.io.BufferedReader; import java.io.BufferedReader;
@@ -22,7 +22,7 @@ import java.util.concurrent.TimeUnit;
* <li>/commit --pr —— 提交 + push + 创建 PR(使用 gh CLI</li> * <li>/commit --pr —— 提交 + push + 创建 PR(使用 gh CLI</li>
* </ul> * </ul>
*/ */
public class CommitCommand implements SlashCommand { public class CommitCommand extends BaseSlashCommand {
@Override @Override
public String name() { public String name() {
@@ -41,7 +41,7 @@ public class CommitCommand implements SlashCommand {
return AnsiStyle.yellow(" ⚠ Current directory is not a Git repository"); return AnsiStyle.yellow(" ⚠ Current directory is not a Git repository");
} }
args = args == null ? "" : args.strip(); args = args(args);
try { try {
boolean addAll = args.contains("--all") || args.contains("-a"); boolean addAll = args.contains("--all") || args.contains("-a");
@@ -1,7 +1,7 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import com.claudecode.core.TokenTracker; import com.claudecode.core.TokenTracker;
import com.claudecode.core.compact.AutoCompactManager; import com.claudecode.core.compact.AutoCompactManager;
@@ -23,7 +23,7 @@ import java.util.List;
* <li>/compact --aggressive —— 激进压缩(保留更少上下文)</li> * <li>/compact --aggressive —— 激进压缩(保留更少上下文)</li>
* </ul> * </ul>
*/ */
public class CompactCommand implements SlashCommand { public class CompactCommand extends BaseSlashCommand {
@Override @Override
public String name() { public String name() {
@@ -37,11 +37,11 @@ public class CompactCommand implements SlashCommand {
@Override @Override
public String execute(String args, CommandContext context) { public String execute(String args, CommandContext context) {
if (context.agentLoop() == null) { if (requireAgentLoop(context) == null) {
return AnsiStyle.yellow(" ⚠ No active conversation to compact."); return AnsiStyle.yellow(" ⚠ No active conversation to compact.");
} }
String argStr = (args == null) ? "" : args.strip(); String argStr = args(args);
// --stats: only show statistics // --stats: only show statistics
if (argStr.equals("--stats") || argStr.equals("-s")) { if (argStr.equals("--stats") || argStr.equals("-s")) {
@@ -1,7 +1,7 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import com.claudecode.permission.PermissionRuleEngine; import com.claudecode.permission.PermissionRuleEngine;
import com.claudecode.permission.PermissionSettings; import com.claudecode.permission.PermissionSettings;
@@ -15,7 +15,7 @@ import java.util.Map;
* <p> * <p>
* 支持查看当前配置、设置单个配置项,以及权限管理子命令。 * 支持查看当前配置、设置单个配置项,以及权限管理子命令。
*/ */
public class ConfigCommand implements SlashCommand { public class ConfigCommand extends BaseSlashCommand {
/** 支持的配置项及说明 */ /** 支持的配置项及说明 */
private static final Map<String, String> CONFIG_KEYS = Map.of( private static final Map<String, String> CONFIG_KEYS = Map.of(
@@ -1,7 +1,7 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import com.claudecode.context.ClaudeMdLoader; import com.claudecode.context.ClaudeMdLoader;
import com.claudecode.context.GitContext; import com.claudecode.context.GitContext;
@@ -14,7 +14,7 @@ import java.nio.file.Path;
* <p> * <p>
* 展示已加载的 CLAUDE.md、Skills、Git 上下文和 Token 预算使用情况。 * 展示已加载的 CLAUDE.md、Skills、Git 上下文和 Token 预算使用情况。
*/ */
public class ContextCommand implements SlashCommand { public class ContextCommand extends BaseSlashCommand {
@Override @Override
public String name() { public String name() {
@@ -1,8 +1,7 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; 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.console.AnsiStyle;
import com.claudecode.core.TokenEstimationService; import com.claudecode.core.TokenEstimationService;
@@ -11,7 +10,7 @@ import java.util.List;
/** /**
* /ctx-viz 命令 —— 上下文可视化(token 分布、消息结构)。 * /ctx-viz 命令 —— 上下文可视化(token 分布、消息结构)。
*/ */
public class ContextVizCommand implements SlashCommand { public class ContextVizCommand extends BaseSlashCommand {
@Override @Override
public String name() { return "ctx-viz"; } public String name() { return "ctx-viz"; }
@@ -25,13 +24,13 @@ public class ContextVizCommand implements SlashCommand {
@Override @Override
public String execute(String args, CommandContext context) { public String execute(String args, CommandContext context) {
StringBuilder sb = new StringBuilder(); 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(); return sb.append(" No active agent loop\n").toString();
} }
var toolCtx = context.agentLoop().getToolContext(); var toolCtx = toolCtx(context);
// Get or create token estimation service // Get or create token estimation service
TokenEstimationService estimator = new TokenEstimationService(); TokenEstimationService estimator = new TokenEstimationService();
@@ -1,7 +1,7 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.Message;
@@ -16,7 +16,7 @@ import java.util.List;
* 从消息历史中提取最后一条 AssistantMessage 的文本内容, * 从消息历史中提取最后一条 AssistantMessage 的文本内容,
* 使用 AWT 剪贴板 API 复制。 * 使用 AWT 剪贴板 API 复制。
*/ */
public class CopyCommand implements SlashCommand { public class CopyCommand extends BaseSlashCommand {
@Override @Override
public String name() { public String name() {
@@ -2,7 +2,7 @@ package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.CommandUtils; import com.claudecode.command.CommandUtils;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import com.claudecode.core.TokenTracker; import com.claudecode.core.TokenTracker;
@@ -12,7 +12,7 @@ import com.claudecode.core.TokenTracker;
* 对应 claude-code/src/commands/cost.ts。 * 对应 claude-code/src/commands/cost.ts。
* 从 AgentLoop 的 TokenTracker 获取真实 Token 统计。 * 从 AgentLoop 的 TokenTracker 获取真实 Token 统计。
*/ */
public class CostCommand implements SlashCommand { public class CostCommand extends BaseSlashCommand {
@Override @Override
public String name() { public String name() {
@@ -30,7 +30,7 @@ public class CostCommand implements SlashCommand {
int msgCount = context.agentLoop().getMessageHistory().size(); int msgCount = context.agentLoop().getMessageHistory().size();
StringBuilder sb = new StringBuilder(); 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("Model: ")).append(AnsiStyle.cyan(tracker.getModelName())).append("\n");
sb.append(" ").append(AnsiStyle.bold("API Calls: ")).append(tracker.getApiCallCount()).append("\n"); sb.append(" ").append(AnsiStyle.bold("API Calls: ")).append(tracker.getApiCallCount()).append("\n");
@@ -1,8 +1,7 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; 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.console.AnsiStyle;
import com.claudecode.core.InternalLogger; import com.claudecode.core.InternalLogger;
@@ -11,7 +10,7 @@ import java.util.List;
/** /**
* /debug 命令 —— 调试模式开关 + 工具调用追踪。 * /debug 命令 —— 调试模式开关 + 工具调用追踪。
*/ */
public class DebugCommand implements SlashCommand { public class DebugCommand extends BaseSlashCommand {
@Override @Override
public String name() { return "debug"; } public String name() { return "debug"; }
@@ -24,16 +23,16 @@ public class DebugCommand implements SlashCommand {
@Override @Override
public String execute(String args, CommandContext context) { public String execute(String args, CommandContext context) {
String trimmed = CommandUtils.parseArgs(args); String trimmed = args(args);
StringBuilder sb = new StringBuilder(); 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(); 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")) { if (trimmed.equals("on") || trimmed.equals("enable")) {
toolCtx.set("DEBUG_MODE", true); toolCtx.set("DEBUG_MODE", true);
@@ -1,7 +1,7 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import java.io.BufferedReader; import java.io.BufferedReader;
@@ -20,7 +20,7 @@ import java.util.concurrent.TimeUnit;
* <li>--stat:仅显示文件统计(不含详细diff</li> * <li>--stat:仅显示文件统计(不含详细diff</li>
* </ul> * </ul>
*/ */
public class DiffCommand implements SlashCommand { public class DiffCommand extends BaseSlashCommand {
@Override @Override
public String name() { public String name() {
@@ -39,7 +39,7 @@ public class DiffCommand implements SlashCommand {
return AnsiStyle.yellow(" ⚠ Current directory is not a Git repository"); return AnsiStyle.yellow(" ⚠ Current directory is not a Git repository");
} }
args = args == null ? "" : args.strip(); args = args(args);
try { try {
String diffOutput; String diffOutput;
@@ -1,7 +1,7 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import java.io.File; import java.io.File;
@@ -21,7 +21,7 @@ import java.util.List;
* <li>磁盘空间</li> * <li>磁盘空间</li>
* </ul> * </ul>
*/ */
public class DoctorCommand implements SlashCommand { public class DoctorCommand extends BaseSlashCommand {
@Override @Override
public String name() { public String name() {
@@ -2,7 +2,7 @@ package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.CommandUtils; import com.claudecode.command.CommandUtils;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import java.io.File; import java.io.File;
@@ -13,7 +13,7 @@ import java.util.TreeMap;
/** /**
* /env 命令 —— 显示环境变量和配置信息。 * /env 命令 —— 显示环境变量和配置信息。
*/ */
public class EnvCommand implements SlashCommand { public class EnvCommand extends BaseSlashCommand {
@Override @Override
public String name() { return "env"; } public String name() { return "env"; }
@@ -23,12 +23,12 @@ public class EnvCommand implements SlashCommand {
@Override @Override
public String execute(String args, CommandContext context) { public String execute(String args, CommandContext context) {
String trimmed = CommandUtils.parseArgs(args); String trimmed = args(args);
StringBuilder sb = new StringBuilder(); 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(" ") sb.append(" OS: ").append(System.getProperty("os.name")).append(" ")
.append(System.getProperty("os.version")).append("\n"); .append(System.getProperty("os.version")).append("\n");
sb.append(" Java: ").append(System.getProperty("java.version")) 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"); .append(" / ").append(CommandUtils.formatBytes(Runtime.getRuntime().maxMemory())).append("\n");
sb.append(" PID: ").append(ProcessHandle.current().pid()).append("\n\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(" WorkDir: ").append(System.getProperty("user.dir")).append("\n");
sb.append(" Home: ").append(System.getProperty("user.home")).append("\n"); sb.append(" Home: ").append(System.getProperty("user.home")).append("\n");
sb.append(" Config: ").append(System.getProperty("user.home")) sb.append(" Config: ").append(System.getProperty("user.home"))
.append(File.separator).append(".claude-code-java").append("\n\n"); .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<String> relevantVars = List.of( List<String> relevantVars = List.of(
"ANTHROPIC_API_KEY", "OPENAI_API_KEY", "CLAUDE_CODE_", "ANTHROPIC_API_KEY", "OPENAI_API_KEY", "CLAUDE_CODE_",
"JAVA_HOME", "PATH", "SHELL", "TERM", "EDITOR" "JAVA_HOME", "PATH", "SHELL", "TERM", "EDITOR"
@@ -1,14 +1,14 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import java.util.List; import java.util.List;
/** /**
* /exit 命令 —— 退出应用。 * /exit 命令 —— 退出应用。
*/ */
public class ExitCommand implements SlashCommand { public class ExitCommand extends BaseSlashCommand {
@Override @Override
public String name() { public String name() {
@@ -1,7 +1,7 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import org.springframework.ai.chat.messages.*; import org.springframework.ai.chat.messages.*;
@@ -22,7 +22,7 @@ import java.util.List;
* <li>/export [路径] —— 导出到指定路径</li> * <li>/export [路径] —— 导出到指定路径</li>
* </ul> * </ul>
*/ */
public class ExportCommand implements SlashCommand { public class ExportCommand extends BaseSlashCommand {
private static final DateTimeFormatter TIMESTAMP_FMT = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss"); 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; Path outputPath;
if (!args.isEmpty()) { if (!args.isEmpty()) {
outputPath = Path.of(args); outputPath = Path.of(args);
@@ -1,7 +1,7 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import java.io.IOException; import java.io.IOException;
@@ -12,7 +12,7 @@ import java.time.Instant;
/** /**
* /feedback 命令 —— 提交反馈(本地保存)。 * /feedback 命令 —— 提交反馈(本地保存)。
*/ */
public class FeedbackCommand implements SlashCommand { public class FeedbackCommand extends BaseSlashCommand {
@Override @Override
public String name() { return "feedback"; } public String name() { return "feedback"; }
@@ -22,7 +22,7 @@ public class FeedbackCommand implements SlashCommand {
@Override @Override
public String execute(String args, CommandContext context) { public String execute(String args, CommandContext context) {
String trimmed = (args == null) ? "" : args.trim(); String trimmed = args(args);
if (trimmed.isEmpty()) { if (trimmed.isEmpty()) {
return AnsiStyle.yellow(" Usage: /feedback <your feedback text>") return AnsiStyle.yellow(" Usage: /feedback <your feedback text>")
@@ -1,7 +1,7 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import java.io.IOException; import java.io.IOException;
@@ -15,7 +15,7 @@ import java.util.stream.Stream;
* 对应 claude-code/src/commands/files.ts。 * 对应 claude-code/src/commands/files.ts。
* 显示项目目录树(默认2层深度)。 * 显示项目目录树(默认2层深度)。
*/ */
public class FilesCommand implements SlashCommand { public class FilesCommand extends BaseSlashCommand {
@Override @Override
public String name() { public String name() {
@@ -2,7 +2,7 @@ package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.CommandUtils; import com.claudecode.command.CommandUtils;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
@@ -16,7 +16,7 @@ import java.time.format.DateTimeFormatter;
/** /**
* /heapdump 命令 —— JVM 堆转储(Java 独有优势)。 * /heapdump 命令 —— JVM 堆转储(Java 独有优势)。
*/ */
public class HeapdumpCommand implements SlashCommand { public class HeapdumpCommand extends BaseSlashCommand {
@Override @Override
public String name() { return "heapdump"; } public String name() { return "heapdump"; }
@@ -26,26 +26,26 @@ public class HeapdumpCommand implements SlashCommand {
@Override @Override
public String execute(String args, CommandContext context) { public String execute(String args, CommandContext context) {
String trimmed = CommandUtils.parseArgs(args); String trimmed = args(args);
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(CommandUtils.header("📦", "JVM Heap Dump")); sb.append(header("📦", "JVM Heap Dump"));
if (trimmed.equals("info") || trimmed.isEmpty()) { if (trimmed.equals("info") || trimmed.isEmpty()) {
MemoryMXBean memBean = ManagementFactory.getMemoryMXBean(); MemoryMXBean memBean = ManagementFactory.getMemoryMXBean();
MemoryUsage heap = memBean.getHeapMemoryUsage(); MemoryUsage heap = memBean.getHeapMemoryUsage();
MemoryUsage nonHeap = memBean.getNonHeapMemoryUsage(); 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(" Used: ").append(CommandUtils.formatBytes(heap.getUsed())).append("\n");
sb.append(" Committed: ").append(CommandUtils.formatBytes(heap.getCommitted())).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(" 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(" Used: ").append(CommandUtils.formatBytes(nonHeap.getUsed())).append("\n");
sb.append(" Committed: ").append(CommandUtils.formatBytes(nonHeap.getCommitted())).append("\n\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()) { for (MemoryPoolMXBean pool : ManagementFactory.getMemoryPoolMXBeans()) {
MemoryUsage usage = pool.getUsage(); MemoryUsage usage = pool.getUsage();
if (usage != null && usage.getUsed() > 0) { if (usage != null && usage.getUsed() > 0) {
@@ -66,12 +66,12 @@ public class HeapdumpCommand implements SlashCommand {
com.sun.management.HotSpotDiagnosticMXBean.class); com.sun.management.HotSpotDiagnosticMXBean.class);
hotspot.dumpHeap(dumpPath.toString(), true); hotspot.dumpHeap(dumpPath.toString(), true);
long fileSize = dumpPath.toFile().length(); 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(" ").append(AnsiStyle.cyan(dumpPath.toString())).append("\n");
sb.append(" Size: ").append(CommandUtils.formatBytes(fileSize)).append("\n\n"); sb.append(" Size: ").append(CommandUtils.formatBytes(fileSize)).append("\n\n");
sb.append(AnsiStyle.dim(" Analyze with: jhat, MAT, or VisualVM")); sb.append(AnsiStyle.dim(" Analyze with: jhat, MAT, or VisualVM"));
} catch (Exception e) { } 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)")); 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"); sb.append(" Freed: ").append(AnsiStyle.green(CommandUtils.formatBytes(Math.max(0, freed)))).append("\n");
} else { } 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 Show memory pool info\n");
sb.append(" /heapdump dump Generate .hprof file\n"); sb.append(" /heapdump dump Generate .hprof file\n");
sb.append(" /heapdump gc Trigger garbage collection\n"); sb.append(" /heapdump gc Trigger garbage collection\n");
@@ -1,6 +1,7 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.BaseSlashCommand;
import com.claudecode.command.SlashCommand; import com.claudecode.command.SlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
@@ -17,7 +18,7 @@ import java.util.stream.Collectors;
* <li>/help [command] —— 显示特定命令详情</li> * <li>/help [command] —— 显示特定命令详情</li>
* </ul> * </ul>
*/ */
public class HelpCommand implements SlashCommand { public class HelpCommand extends BaseSlashCommand {
@Override @Override
public String name() { public String name() {
@@ -36,7 +37,7 @@ public class HelpCommand implements SlashCommand {
@Override @Override
public String execute(String args, CommandContext context) { public String execute(String args, CommandContext context) {
String query = (args == null) ? "" : args.strip(); String query = args(args);
var allCommands = context.commandRegistry().getCommands(); var allCommands = context.commandRegistry().getCommands();
// If a specific command name is given, show details // If a specific command name is given, show details
@@ -1,7 +1,7 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import com.claudecode.core.ConversationPersistence; import com.claudecode.core.ConversationPersistence;
@@ -10,7 +10,7 @@ import com.claudecode.core.ConversationPersistence;
* <p> * <p>
* 显示最近的对话记录,包括时间、摘要和消息数量。 * 显示最近的对话记录,包括时间、摘要和消息数量。
*/ */
public class HistoryCommand implements SlashCommand { public class HistoryCommand extends BaseSlashCommand {
@Override @Override
public String name() { public String name() {
@@ -1,7 +1,7 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import com.claudecode.core.HookManager; import com.claudecode.core.HookManager;
import com.claudecode.core.HookManager.HookRegistration; import com.claudecode.core.HookManager.HookRegistration;
@@ -17,7 +17,7 @@ import java.util.stream.Collectors;
* 按 Hook 类型分组展示,包含每个 Hook 的名称和优先级。 * 按 Hook 类型分组展示,包含每个 Hook 的名称和优先级。
* 如果没有注册任何 Hook,则显示提示信息。 * 如果没有注册任何 Hook,则显示提示信息。
*/ */
public class HooksCommand implements SlashCommand { public class HooksCommand extends BaseSlashCommand {
@Override @Override
public String name() { public String name() {
@@ -1,14 +1,13 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.List;
/** /**
* /init 命令 —— 初始化项目 CLAUDE.md。 * /init 命令 —— 初始化项目 CLAUDE.md。
@@ -16,7 +15,7 @@ import java.util.List;
* 对应 claude-code/src/commands/init.ts。 * 对应 claude-code/src/commands/init.ts。
* 检测项目类型并生成 CLAUDE.md 模板文件。 * 检测项目类型并生成 CLAUDE.md 模板文件。
*/ */
public class InitCommand implements SlashCommand { public class InitCommand extends BaseSlashCommand {
@Override @Override
public String name() { public String name() {
@@ -1,7 +1,7 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import java.util.List; import java.util.List;
@@ -9,7 +9,7 @@ import java.util.List;
/** /**
* /keybindings 命令 —— 显示和配置快捷键。 * /keybindings 命令 —— 显示和配置快捷键。
*/ */
public class KeybindingsCommand implements SlashCommand { public class KeybindingsCommand extends BaseSlashCommand {
@Override @Override
public String name() { return "keybindings"; } public String name() { return "keybindings"; }
@@ -2,7 +2,7 @@ package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.CommandUtils; import com.claudecode.command.CommandUtils;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import com.claudecode.mcp.McpClient; import com.claudecode.mcp.McpClient;
import com.claudecode.mcp.McpException; import com.claudecode.mcp.McpException;
@@ -25,7 +25,7 @@ import java.util.*;
* <li>{@code /mcp reload} —— 从配置文件重新加载</li> * <li>{@code /mcp reload} —— 从配置文件重新加载</li>
* </ul> * </ul>
*/ */
public class McpCommand implements SlashCommand { public class McpCommand extends BaseSlashCommand {
@Override @Override
public String name() { public String name() {
@@ -48,7 +48,7 @@ public class McpCommand implements SlashCommand {
return AnsiStyle.red(" ❌ MCP manager not initialized"); return AnsiStyle.red(" ❌ MCP manager not initialized");
} }
String trimmed = CommandUtils.parseArgs(args); String trimmed = args(args);
if (trimmed.isEmpty()) { if (trimmed.isEmpty()) {
return showStatus(manager); return showStatus(manager);
} }
@@ -73,7 +73,7 @@ public class McpCommand implements SlashCommand {
*/ */
private String showStatus(McpManager manager) { private String showStatus(McpManager manager) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(CommandUtils.header("🔌", "MCP Server Status")); sb.append(header("🔌", "MCP Server Status"));
Map<String, McpClient> clients = manager.getClients(); Map<String, McpClient> clients = manager.getClients();
if (clients.isEmpty()) { if (clients.isEmpty()) {
@@ -192,7 +192,7 @@ public class McpCommand implements SlashCommand {
*/ */
private String handleTools(McpManager manager, String args) { private String handleTools(McpManager manager, String args) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(CommandUtils.header("🛠️", "MCP Tools")); sb.append(header("🛠️", "MCP Tools"));
String serverFilter = args.isEmpty() ? null : args.split("\\s+")[0]; 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) { private String handleResources(McpManager manager, String args) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(CommandUtils.header("📦", "MCP Resources")); sb.append(header("📦", "MCP Resources"));
String serverFilter = args.isEmpty() ? null : args.split("\\s+")[0]; String serverFilter = args.isEmpty() ? null : args.split("\\s+")[0];
@@ -1,7 +1,7 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import java.io.IOException; import java.io.IOException;
@@ -21,7 +21,7 @@ import java.util.List;
* <li>/memory user —— 查看用户级 CLAUDE.md</li> * <li>/memory user —— 查看用户级 CLAUDE.md</li>
* </ul> * </ul>
*/ */
public class MemoryCommand implements SlashCommand { public class MemoryCommand extends BaseSlashCommand {
@Override @Override
public String name() { public String name() {
@@ -40,7 +40,7 @@ public class MemoryCommand implements SlashCommand {
@Override @Override
public String execute(String args, CommandContext context) { public String execute(String args, CommandContext context) {
args = args == null ? "" : args.strip(); args = args(args);
if (args.startsWith("add ")) { if (args.startsWith("add ")) {
return handleAdd(args.substring(4).strip()); return handleAdd(args.substring(4).strip());
@@ -1,7 +1,7 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import java.util.List; import java.util.List;
@@ -12,7 +12,7 @@ import java.util.Map;
* <p> * <p>
* 支持查看当前模型信息和切换到其他模型。 * 支持查看当前模型信息和切换到其他模型。
*/ */
public class ModelCommand implements SlashCommand { public class ModelCommand extends BaseSlashCommand {
private static final Map<String, String> AVAILABLE_MODELS = Map.of( private static final Map<String, String> AVAILABLE_MODELS = Map.of(
"sonnet", "claude-sonnet-4-20250514", "sonnet", "claude-sonnet-4-20250514",
@@ -1,13 +1,13 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
/** /**
* /output-style 命令 —— 输出风格设置。 * /output-style 命令 —— 输出风格设置。
*/ */
public class OutputStyleCommand implements SlashCommand { public class OutputStyleCommand extends BaseSlashCommand {
@Override @Override
public String name() { return "output-style"; } public String name() { return "output-style"; }
@@ -17,15 +17,15 @@ public class OutputStyleCommand implements SlashCommand {
@Override @Override
public String execute(String args, CommandContext context) { public String execute(String args, CommandContext context) {
if (context.agentLoop() == null) { if (requireAgentLoop(context) == null) {
return AnsiStyle.red(" ✗ No active session"); return noSession();
} }
var toolCtx = context.agentLoop().getToolContext(); var toolCtx = toolCtx(context);
String current = (String) toolCtx.get("OUTPUT_STYLE"); String current = (String) toolCtx.get("OUTPUT_STYLE");
if (current == null) current = "markdown"; if (current == null) current = "markdown";
String trimmed = (args == null) ? "" : args.trim().toLowerCase(); String trimmed = args(args).toLowerCase();
if (trimmed.isEmpty()) { if (trimmed.isEmpty()) {
return "\n" + AnsiStyle.bold(" 📝 Output Style\n") return "\n" + AnsiStyle.bold(" 📝 Output Style\n")
@@ -2,14 +2,13 @@ package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.CommandUtils; import com.claudecode.command.CommandUtils;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle;
import com.claudecode.telemetry.MetricsCollector; import com.claudecode.telemetry.MetricsCollector;
/** /**
* /performance 命令 —— 性能统计。 * /performance 命令 —— 性能统计。
*/ */
public class PerformanceCommand implements SlashCommand { public class PerformanceCommand extends BaseSlashCommand {
@Override @Override
public String name() { return "performance"; } public String name() { return "performance"; }
@@ -25,7 +24,7 @@ public class PerformanceCommand implements SlashCommand {
@Override @Override
public String execute(String args, CommandContext context) { public String execute(String args, CommandContext context) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(CommandUtils.header("", "Performance Statistics")); sb.append(header("", "Performance Statistics"));
Runtime runtime = Runtime.getRuntime(); Runtime runtime = Runtime.getRuntime();
long totalMem = runtime.totalMemory(); long totalMem = runtime.totalMemory();
@@ -33,14 +32,14 @@ public class PerformanceCommand implements SlashCommand {
long usedMem = totalMem - freeMem; long usedMem = totalMem - freeMem;
long maxMem = runtime.maxMemory(); 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(" Used: ").append(CommandUtils.formatBytes(usedMem)).append("\n");
sb.append(" Allocated: ").append(CommandUtils.formatBytes(totalMem)).append("\n"); sb.append(" Allocated: ").append(CommandUtils.formatBytes(totalMem)).append("\n");
sb.append(" Max: ").append(CommandUtils.formatBytes(maxMem)).append("\n"); sb.append(" Max: ").append(CommandUtils.formatBytes(maxMem)).append("\n");
sb.append(" Usage: ").append(CommandUtils.progressBar((double) usedMem / maxMem, 20)).append("\n\n"); sb.append(" Usage: ").append(CommandUtils.progressBar((double) usedMem / maxMem, 20)).append("\n\n");
int threadCount = Thread.activeCount(); 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(" Active: ").append(threadCount).append("\n");
sb.append(" Available: ").append(runtime.availableProcessors()).append(" CPUs\n\n"); sb.append(" Available: ").append(runtime.availableProcessors()).append(" CPUs\n\n");
@@ -50,14 +49,14 @@ public class PerformanceCommand implements SlashCommand {
gcCount += gc.getCollectionCount(); gcCount += gc.getCollectionCount();
gcTime += gc.getCollectionTime(); 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(" Collections: ").append(gcCount).append("\n");
sb.append(" Total time: ").append(CommandUtils.formatMillis(gcTime)).append("\n\n"); sb.append(" Total time: ").append(CommandUtils.formatMillis(gcTime)).append("\n\n");
if (context.agentLoop() != null) { if (context.agentLoop() != null) {
Object metricsObj = context.agentLoop().getToolContext().get("METRICS_COLLECTOR"); Object metricsObj = toolCtx(context).get("METRICS_COLLECTOR");
if (metricsObj instanceof MetricsCollector metrics) { 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"); sb.append(" Duration: ").append(CommandUtils.formatDuration(metrics.getSessionDurationSeconds())).append("\n");
var toolUsage = metrics.getToolUsage(); var toolUsage = metrics.getToolUsage();
if (!toolUsage.isEmpty()) { if (!toolUsage.isEmpty()) {
@@ -1,7 +1,7 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import com.claudecode.permission.PermissionSettings; import com.claudecode.permission.PermissionSettings;
import com.claudecode.permission.PermissionTypes; import com.claudecode.permission.PermissionTypes;
@@ -14,7 +14,7 @@ import java.util.List;
* 对应 claude-code/src/commands/permissions.ts。 * 对应 claude-code/src/commands/permissions.ts。
* 显示当前权限模式和规则列表。 * 显示当前权限模式和规则列表。
*/ */
public class PermissionsCommand implements SlashCommand { public class PermissionsCommand extends BaseSlashCommand {
private final PermissionSettings settings; private final PermissionSettings settings;
@@ -1,7 +1,7 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.permission.PermissionSettings; import com.claudecode.permission.PermissionSettings;
import com.claudecode.permission.PermissionTypes.PermissionMode; import com.claudecode.permission.PermissionTypes.PermissionMode;
@@ -10,7 +10,7 @@ import com.claudecode.permission.PermissionTypes.PermissionMode;
* <p> * <p>
* 切换计划模式开关。在计划模式下,AI只能分析不能修改。 * 切换计划模式开关。在计划模式下,AI只能分析不能修改。
*/ */
public class PlanCommand implements SlashCommand { public class PlanCommand extends BaseSlashCommand {
private final PermissionSettings permissionSettings; private final PermissionSettings permissionSettings;
@@ -1,7 +1,7 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.CommandUtils; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.command.SlashCommand; import com.claudecode.command.SlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import com.claudecode.plugin.*; import com.claudecode.plugin.*;
@@ -32,7 +32,7 @@ import java.util.Optional;
* 通过 {@link com.claudecode.tool.ToolContext} 中 key 为 * 通过 {@link com.claudecode.tool.ToolContext} 中 key 为
* {@code "PLUGIN_MANAGER"} 的共享状态获取 {@link PluginManager} 实例。 * {@code "PLUGIN_MANAGER"} 的共享状态获取 {@link PluginManager} 实例。
*/ */
public class PluginCommand implements SlashCommand { public class PluginCommand extends BaseSlashCommand {
@Override @Override
public String name() { public String name() {
@@ -56,7 +56,7 @@ public class PluginCommand implements SlashCommand {
return AnsiStyle.red(" ✗ Plugin system not initialized"); return AnsiStyle.red(" ✗ Plugin system not initialized");
} }
String trimmed = CommandUtils.parseArgs(args); String trimmed = args(args);
// 无参数:列出所有插件 // 无参数:列出所有插件
if (trimmed.isEmpty()) { if (trimmed.isEmpty()) {
@@ -88,7 +88,7 @@ public class PluginCommand implements SlashCommand {
private String listPlugins(PluginManager manager) { private String listPlugins(PluginManager manager) {
List<PluginInfo> plugins = manager.getPlugins(); List<PluginInfo> plugins = manager.getPlugins();
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(CommandUtils.header("🔌", "Loaded Plugins")); sb.append(header("🔌", "Loaded Plugins"));
if (plugins.isEmpty()) { if (plugins.isEmpty()) {
sb.append(AnsiStyle.dim(" No plugins loaded.")).append("\n"); sb.append(AnsiStyle.dim(" No plugins loaded.")).append("\n");
@@ -173,7 +173,7 @@ public class PluginCommand implements SlashCommand {
Plugin p = info.plugin(); Plugin p = info.plugin();
StringBuilder sb = new StringBuilder(); 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("Name: ")).append(p.name()).append("\n");
sb.append(" ").append(AnsiStyle.bold("ID: ")).append(AnsiStyle.cyan(p.id())).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) { private PluginInstaller getInstaller(CommandContext context) {
try { try {
Object obj = context.agentLoop().getToolContext().get("PLUGIN_INSTALLER"); Object obj = toolCtx(context).get("PLUGIN_INSTALLER");
if (obj instanceof PluginInstaller pi) return pi; if (obj instanceof PluginInstaller pi) return pi;
} catch (Exception ignored) {} } catch (Exception ignored) {}
return null; return null;
@@ -368,7 +368,7 @@ public class PluginCommand implements SlashCommand {
private MarketplaceManager getMarketplace(CommandContext context) { private MarketplaceManager getMarketplace(CommandContext context) {
try { try {
Object obj = context.agentLoop().getToolContext().get("MARKETPLACE_MANAGER"); Object obj = toolCtx(context).get("MARKETPLACE_MANAGER");
if (obj instanceof MarketplaceManager mm) return mm; if (obj instanceof MarketplaceManager mm) return mm;
} catch (Exception ignored) {} } catch (Exception ignored) {}
return null; return null;
@@ -376,7 +376,7 @@ public class PluginCommand implements SlashCommand {
private PluginAutoUpdate getAutoUpdate(CommandContext context) { private PluginAutoUpdate getAutoUpdate(CommandContext context) {
try { 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; if (obj instanceof PluginAutoUpdate pau) return pau;
} catch (Exception ignored) {} } catch (Exception ignored) {}
return null; return null;
@@ -391,11 +391,11 @@ public class PluginCommand implements SlashCommand {
* @return PluginManager 实例,未找到时返回 null * @return PluginManager 实例,未找到时返回 null
*/ */
private PluginManager getPluginManager(CommandContext context) { private PluginManager getPluginManager(CommandContext context) {
if (context.agentLoop() == null) { if (requireAgentLoop(context) == null) {
return null; return null;
} }
try { try {
Object manager = context.agentLoop().getToolContext().get("PLUGIN_MANAGER"); Object manager = toolCtx(context).get("PLUGIN_MANAGER");
if (manager instanceof PluginManager pm) { if (manager instanceof PluginManager pm) {
return pm; return pm;
} }
@@ -1,13 +1,13 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
/** /**
* /privacy 命令 —— 隐私设置查看和修改。 * /privacy 命令 —— 隐私设置查看和修改。
*/ */
public class PrivacyCommand implements SlashCommand { public class PrivacyCommand extends BaseSlashCommand {
@Override @Override
public String name() { return "privacy"; } public String name() { return "privacy"; }
@@ -17,16 +17,16 @@ public class PrivacyCommand implements SlashCommand {
@Override @Override
public String execute(String args, CommandContext context) { public String execute(String args, CommandContext context) {
if (context.agentLoop() == null) { if (requireAgentLoop(context) == null) {
return AnsiStyle.red(" ✗ No active session"); return noSession();
} }
var toolCtx = context.agentLoop().getToolContext(); var toolCtx = toolCtx(context);
boolean telemetryEnabled = Boolean.TRUE.equals(toolCtx.get("TELEMETRY_ENABLED")); boolean telemetryEnabled = Boolean.TRUE.equals(toolCtx.get("TELEMETRY_ENABLED"));
boolean sessionLogging = !Boolean.FALSE.equals(toolCtx.get("SESSION_LOGGING")); boolean sessionLogging = !Boolean.FALSE.equals(toolCtx.get("SESSION_LOGGING"));
boolean memoryPersist = !Boolean.FALSE.equals(toolCtx.get("MEMORY_PERSIST")); boolean memoryPersist = !Boolean.FALSE.equals(toolCtx.get("MEMORY_PERSIST"));
String trimmed = (args == null) ? "" : args.trim().toLowerCase(); String trimmed = args(args).toLowerCase();
if (trimmed.isEmpty()) { if (trimmed.isEmpty()) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
@@ -2,7 +2,7 @@ package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.CommandUtils; import com.claudecode.command.CommandUtils;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import java.util.List; import java.util.List;
@@ -10,7 +10,7 @@ import java.util.List;
/** /**
* /release-notes 命令 —— 显示版本更新日志。 * /release-notes 命令 —— 显示版本更新日志。
*/ */
public class ReleaseNotesCommand implements SlashCommand { public class ReleaseNotesCommand extends BaseSlashCommand {
@Override @Override
public String name() { return "release-notes"; } public String name() { return "release-notes"; }
@@ -26,7 +26,7 @@ public class ReleaseNotesCommand implements SlashCommand {
@Override @Override
public String execute(String args, CommandContext context) { public String execute(String args, CommandContext context) {
StringBuilder sb = new StringBuilder(); 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(AnsiStyle.bold(" v0.4.0 — Phase 4: Commands, Tools & Services\n"));
sb.append(CommandUtils.separator(45)).append("\n"); sb.append(CommandUtils.separator(45)).append("\n");
@@ -1,7 +1,7 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import java.util.List; import java.util.List;
@@ -12,7 +12,7 @@ import java.util.List;
* 对应 claude-code 的 /rename 命令,给当前会话设置一个友好名称。 * 对应 claude-code 的 /rename 命令,给当前会话设置一个友好名称。
* 会话名称在 UI 标题栏和会话列表中显示。 * 会话名称在 UI 标题栏和会话列表中显示。
*/ */
public class RenameCommand implements SlashCommand { public class RenameCommand extends BaseSlashCommand {
/** 当前会话名称(进程级别) */ /** 当前会话名称(进程级别) */
private static volatile String sessionName = null; private static volatile String sessionName = null;
@@ -34,7 +34,7 @@ public class RenameCommand implements SlashCommand {
@Override @Override
public String execute(String args, CommandContext context) { public String execute(String args, CommandContext context) {
args = args == null ? "" : args.strip(); args = args(args);
if (args.isBlank()) { if (args.isBlank()) {
if (sessionName == null) { if (sessionName == null) {
@@ -1,8 +1,7 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; 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.console.AnsiStyle;
import com.claudecode.core.RateLimiter; import com.claudecode.core.RateLimiter;
@@ -11,7 +10,7 @@ import java.util.List;
/** /**
* /reset-limits 命令 —— 重置速率限制。 * /reset-limits 命令 —— 重置速率限制。
*/ */
public class ResetLimitsCommand implements SlashCommand { public class ResetLimitsCommand extends BaseSlashCommand {
@Override @Override
public String name() { return "reset-limits"; } public String name() { return "reset-limits"; }
@@ -25,13 +24,13 @@ public class ResetLimitsCommand implements SlashCommand {
@Override @Override
public String execute(String args, CommandContext context) { public String execute(String args, CommandContext context) {
StringBuilder sb = new StringBuilder(); 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(); return sb.append(" No active agent loop\n").toString();
} }
var toolCtx = context.agentLoop().getToolContext(); var toolCtx = toolCtx(context);
Object limiterObj = toolCtx.get("RATE_LIMITER"); Object limiterObj = toolCtx.get("RATE_LIMITER");
if (limiterObj instanceof RateLimiter limiter) { if (limiterObj instanceof RateLimiter limiter) {
@@ -1,7 +1,7 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import com.claudecode.core.ConversationPersistence; import com.claudecode.core.ConversationPersistence;
import com.claudecode.core.ConversationPersistence.ConversationSummary; import com.claudecode.core.ConversationPersistence.ConversationSummary;
@@ -21,7 +21,7 @@ import java.util.List;
* <li>/resume [序号] —— 恢复指定序号的对话</li> * <li>/resume [序号] —— 恢复指定序号的对话</li>
* </ul> * </ul>
*/ */
public class ResumeCommand implements SlashCommand { public class ResumeCommand extends BaseSlashCommand {
@Override @Override
public String name() { public String name() {
@@ -38,7 +38,7 @@ public class ResumeCommand implements SlashCommand {
ConversationPersistence persistence = new ConversationPersistence(); ConversationPersistence persistence = new ConversationPersistence();
List<ConversationSummary> conversations = persistence.listConversations(); List<ConversationSummary> conversations = persistence.listConversations();
args = args == null ? "" : args.strip(); args = args(args);
if (conversations.isEmpty()) { if (conversations.isEmpty()) {
return AnsiStyle.yellow(" ⚠ No saved conversations\n") return AnsiStyle.yellow(" ⚠ No saved conversations\n")
@@ -1,7 +1,7 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import java.io.BufferedReader; import java.io.BufferedReader;
@@ -23,7 +23,7 @@ import java.util.stream.Collectors;
* <p> * <p>
* 获取 diff 内容后,发送给 AI 模型进行代码审查。 * 获取 diff 内容后,发送给 AI 模型进行代码审查。
*/ */
public class ReviewCommand implements SlashCommand { public class ReviewCommand extends BaseSlashCommand {
@Override @Override
public String name() { public String name() {
@@ -42,7 +42,7 @@ public class ReviewCommand implements SlashCommand {
@Override @Override
public String execute(String args, CommandContext context) { public String execute(String args, CommandContext context) {
if (context.agentLoop() == null) { if (requireAgentLoop(context) == null) {
return AnsiStyle.red(" ✗ AgentLoop unavailable, cannot perform code review."); return AnsiStyle.red(" ✗ AgentLoop unavailable, cannot perform code review.");
} }
@@ -1,7 +1,7 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.Message;
@@ -20,7 +20,7 @@ import java.util.List;
* 使用 {@code agentLoop.getMessageHistory()} 获取当前消息历史, * 使用 {@code agentLoop.getMessageHistory()} 获取当前消息历史,
* 然后通过 {@code agentLoop.replaceHistory()} 用截断后的列表替换。 * 然后通过 {@code agentLoop.replaceHistory()} 用截断后的列表替换。
*/ */
public class RewindCommand implements SlashCommand { public class RewindCommand extends BaseSlashCommand {
/** 每个消息对包含的消息数(用户消息 + 助手消息) */ /** 每个消息对包含的消息数(用户消息 + 助手消息) */
private static final int MESSAGES_PER_PAIR = 2; private static final int MESSAGES_PER_PAIR = 2;
@@ -37,7 +37,7 @@ public class RewindCommand implements SlashCommand {
@Override @Override
public String execute(String args, CommandContext context) { public String execute(String args, CommandContext context) {
if (context.agentLoop() == null) { if (requireAgentLoop(context) == null) {
return AnsiStyle.red(" ✗ AgentLoop unavailable."); return AnsiStyle.red(" ✗ AgentLoop unavailable.");
} }
@@ -1,17 +1,15 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; 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.console.AnsiStyle;
import java.util.List;
/** /**
* /sandbox 命令 —— 沙箱模式切换。 * /sandbox 命令 —— 沙箱模式切换。
* 控制工具执行的安全隔离级别。 * 控制工具执行的安全隔离级别。
*/ */
public class SandboxCommand implements SlashCommand { public class SandboxCommand extends BaseSlashCommand {
@Override @Override
public String name() { return "sandbox"; } public String name() { return "sandbox"; }
@@ -21,16 +19,16 @@ public class SandboxCommand implements SlashCommand {
@Override @Override
public String execute(String args, CommandContext context) { public String execute(String args, CommandContext context) {
String trimmed = CommandUtils.parseArgs(args); String trimmed = args(args);
StringBuilder sb = new StringBuilder(); 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(); 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")) { if (trimmed.equals("on") || trimmed.equals("enable") || trimmed.equals("strict")) {
toolCtx.set("SANDBOX_MODE", "strict"); toolCtx.set("SANDBOX_MODE", "strict");
@@ -1,7 +1,7 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import java.io.BufferedReader; import java.io.BufferedReader;
@@ -25,7 +25,7 @@ import java.util.stream.Collectors;
* <li>依赖安全问题</li> * <li>依赖安全问题</li>
* </ul> * </ul>
*/ */
public class SecurityReviewCommand implements SlashCommand { public class SecurityReviewCommand extends BaseSlashCommand {
@Override @Override
public String name() { public String name() {
@@ -44,7 +44,7 @@ public class SecurityReviewCommand implements SlashCommand {
@Override @Override
public String execute(String args, CommandContext context) { public String execute(String args, CommandContext context) {
if (context.agentLoop() == null) { if (requireAgentLoop(context) == null) {
return AnsiStyle.red(" ✗ AgentLoop unavailable, cannot perform security review."); return AnsiStyle.red(" ✗ AgentLoop unavailable, cannot perform security review.");
} }
@@ -1,7 +1,7 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import java.io.IOException; import java.io.IOException;
@@ -24,7 +24,7 @@ import java.util.List;
* <li>/session export —— 导出会话为 JSON</li> * <li>/session export —— 导出会话为 JSON</li>
* </ul> * </ul>
*/ */
public class SessionCommand implements SlashCommand { public class SessionCommand extends BaseSlashCommand {
private static final DateTimeFormatter FMT = private static final DateTimeFormatter FMT =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
@@ -49,7 +49,7 @@ public class SessionCommand implements SlashCommand {
@Override @Override
public String execute(String args, CommandContext context) { public String execute(String args, CommandContext context) {
args = args == null ? "" : args.strip(); args = args(args);
if (args.startsWith("save")) { if (args.startsWith("save")) {
String sessionName = args.length() > 5 ? args.substring(5).strip() : ""; String sessionName = args.length() > 5 ? args.substring(5).strip() : "";
@@ -2,7 +2,7 @@ package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.CommandUtils; import com.claudecode.command.CommandUtils;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import com.claudecode.context.SkillLoader; import com.claudecode.context.SkillLoader;
@@ -14,7 +14,7 @@ import java.util.List;
* <p> * <p>
* 扫描并显示从用户级、项目级和命令目录加载的技能文件。 * 扫描并显示从用户级、项目级和命令目录加载的技能文件。
*/ */
public class SkillsCommand implements SlashCommand { public class SkillsCommand extends BaseSlashCommand {
@Override @Override
public String name() { public String name() {
@@ -51,7 +51,7 @@ public class SkillsCommand implements SlashCommand {
} }
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(CommandUtils.header("🎯", "Available Skills")); sb.append(header("🎯", "Available Skills"));
if (skills.isEmpty()) { if (skills.isEmpty()) {
sb.append(AnsiStyle.dim(" (No available skills)\n\n")); sb.append(AnsiStyle.dim(" (No available skills)\n\n"));
@@ -89,7 +89,7 @@ public class SkillsCommand implements SlashCommand {
private String formatSkillDetail(SkillLoader.Skill skill) { private String formatSkillDetail(SkillLoader.Skill skill) {
StringBuilder sb = new StringBuilder(); 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"); sb.append(" ").append(AnsiStyle.bold("Source: ")).append(skill.source()).append("\n");
if (!skill.description().isEmpty()) { if (!skill.description().isEmpty()) {
@@ -1,7 +1,7 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import com.claudecode.core.TokenTracker; import com.claudecode.core.TokenTracker;
@@ -23,7 +23,7 @@ import java.util.concurrent.TimeUnit;
* <li>JVM 运行时长</li> * <li>JVM 运行时长</li>
* </ul> * </ul>
*/ */
public class StatsCommand implements SlashCommand { public class StatsCommand extends BaseSlashCommand {
@Override @Override
public String name() { public String name() {
@@ -37,7 +37,7 @@ public class StatsCommand implements SlashCommand {
@Override @Override
public String execute(String args, CommandContext context) { public String execute(String args, CommandContext context) {
if (context.agentLoop() == null) { if (requireAgentLoop(context) == null) {
return AnsiStyle.red(" ✗ AgentLoop unavailable."); return AnsiStyle.red(" ✗ AgentLoop unavailable.");
} }
@@ -2,7 +2,7 @@ package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.CommandUtils; import com.claudecode.command.CommandUtils;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import com.claudecode.core.TokenTracker; import com.claudecode.core.TokenTracker;
@@ -15,7 +15,7 @@ import java.time.Instant;
* <p> * <p>
* 展示当前模型、Token 用量、工具数、消息数、内存和运行时间等信息。 * 展示当前模型、Token 用量、工具数、消息数、内存和运行时间等信息。
*/ */
public class StatusCommand implements SlashCommand { public class StatusCommand extends BaseSlashCommand {
private final Instant startTime = Instant.now(); private final Instant startTime = Instant.now();
@@ -32,7 +32,7 @@ public class StatusCommand implements SlashCommand {
@Override @Override
public String execute(String args, CommandContext context) { public String execute(String args, CommandContext context) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(CommandUtils.header("📊", "Session Status")); sb.append(header("📊", "Session Status"));
// 模型信息 // 模型信息
TokenTracker tracker = context.agentLoop().getTokenTracker(); TokenTracker tracker = context.agentLoop().getTokenTracker();
@@ -1,7 +1,7 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.Message;
@@ -25,7 +25,7 @@ import java.util.Map;
* <li>{@code /tag goto <name>} —— 回溯到指定标签位置</li> * <li>{@code /tag goto <name>} —— 回溯到指定标签位置</li>
* </ul> * </ul>
*/ */
public class TagCommand implements SlashCommand { public class TagCommand extends BaseSlashCommand {
/** 静态标签存储:标签名称 -> 标签信息 */ /** 静态标签存储:标签名称 -> 标签信息 */
private static final Map<String, TagInfo> tags = new LinkedHashMap<>(); private static final Map<String, TagInfo> tags = new LinkedHashMap<>();
@@ -42,7 +42,7 @@ public class TagCommand implements SlashCommand {
@Override @Override
public String execute(String args, CommandContext context) { public String execute(String args, CommandContext context) {
if (context.agentLoop() == null) { if (requireAgentLoop(context) == null) {
return AnsiStyle.red(" ✗ AgentLoop unavailable."); return AnsiStyle.red(" ✗ AgentLoop unavailable.");
} }
@@ -2,7 +2,7 @@ package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.CommandUtils; import com.claudecode.command.CommandUtils;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import com.claudecode.core.TaskManager; import com.claudecode.core.TaskManager;
import com.claudecode.core.TaskManager.TaskInfo; import com.claudecode.core.TaskManager.TaskInfo;
@@ -18,7 +18,7 @@ import java.util.List;
* 对应 claude-code 中的任务管理 UI。 * 对应 claude-code 中的任务管理 UI。
* 显示所有任务的状态、创建时间和结果摘要。 * 显示所有任务的状态、创建时间和结果摘要。
*/ */
public class TasksCommand implements SlashCommand { public class TasksCommand extends BaseSlashCommand {
private final TaskManager taskManager; private final TaskManager taskManager;
@@ -39,7 +39,7 @@ public class TasksCommand implements SlashCommand {
@Override @Override
public String execute(String args, CommandContext context) { public String execute(String args, CommandContext context) {
List<TaskInfo> tasks; List<TaskInfo> tasks;
String filter = CommandUtils.parseArgs(args); String filter = args(args);
// Optional status filter // Optional status filter
if (!filter.isEmpty()) { if (!filter.isEmpty()) {
@@ -1,13 +1,13 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
/** /**
* /theme 命令 —— 切换主题(dark/light/auto)。 * /theme 命令 —— 切换主题(dark/light/auto)。
*/ */
public class ThemeCommand implements SlashCommand { public class ThemeCommand extends BaseSlashCommand {
@Override @Override
public String name() { return "theme"; } public String name() { return "theme"; }
@@ -17,15 +17,15 @@ public class ThemeCommand implements SlashCommand {
@Override @Override
public String execute(String args, CommandContext context) { public String execute(String args, CommandContext context) {
if (context.agentLoop() == null) { if (requireAgentLoop(context) == null) {
return AnsiStyle.red(" ✗ No active session"); return noSession();
} }
var toolCtx = context.agentLoop().getToolContext(); var toolCtx = toolCtx(context);
String current = (String) toolCtx.get("THEME"); String current = (String) toolCtx.get("THEME");
if (current == null) current = "dark"; if (current == null) current = "dark";
String trimmed = (args == null) ? "" : args.trim().toLowerCase(); String trimmed = args(args).toLowerCase();
if (trimmed.isEmpty()) { if (trimmed.isEmpty()) {
return "\n" + AnsiStyle.bold(" 🎨 Theme Settings\n") return "\n" + AnsiStyle.bold(" 🎨 Theme Settings\n")
@@ -1,7 +1,7 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import java.util.List; import java.util.List;
@@ -10,7 +10,7 @@ import java.util.Random;
/** /**
* /tips 命令 —— 显示使用技巧和建议。 * /tips 命令 —— 显示使用技巧和建议。
*/ */
public class TipsCommand implements SlashCommand { public class TipsCommand extends BaseSlashCommand {
private static final List<String> TIPS = List.of( private static final List<String> TIPS = List.of(
"Use /compact when the conversation gets long to reduce token usage", "Use /compact when the conversation gets long to reduce token usage",
@@ -45,7 +45,7 @@ public class TipsCommand implements SlashCommand {
@Override @Override
public String execute(String args, CommandContext context) { public String execute(String args, CommandContext context) {
String trimmed = (args == null) ? "" : args.trim(); String trimmed = args(args);
if ("all".equals(trimmed)) { if ("all".equals(trimmed)) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
@@ -1,20 +1,18 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; 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.console.AnsiStyle;
import java.time.Instant; import java.time.Instant;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.ConcurrentLinkedDeque;
/** /**
* /trace 命令 —— 请求/响应追踪。 * /trace 命令 —— 请求/响应追踪。
* 显示 API 调用追踪信息(模型调用、tool 调用链等)。 * 显示 API 调用追踪信息(模型调用、tool 调用链等)。
*/ */
public class TraceCommand implements SlashCommand { public class TraceCommand extends BaseSlashCommand {
@Override @Override
public String name() { return "trace"; } public String name() { return "trace"; }
@@ -24,16 +22,16 @@ public class TraceCommand implements SlashCommand {
@Override @Override
public String execute(String args, CommandContext context) { public String execute(String args, CommandContext context) {
String trimmed = CommandUtils.parseArgs(args); String trimmed = args(args);
StringBuilder sb = new StringBuilder(); 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(); 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")) { if (trimmed.equals("on") || trimmed.equals("enable")) {
toolCtx.set("TRACE_ENABLED", true); toolCtx.set("TRACE_ENABLED", true);
@@ -69,7 +67,7 @@ public class TraceCommand implements SlashCommand {
// Show recent conversation turns // Show recent conversation turns
sb.append("\n").append(AnsiStyle.bold(" Conversation State\n")); 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"); .get("SESSION_ID") != null ? toolCtx.get("SESSION_ID") : "default").append("\n");
sb.append("\n").append(AnsiStyle.bold(" Subcommands\n")); sb.append("\n").append(AnsiStyle.bold(" Subcommands\n"));
@@ -1,14 +1,14 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
import com.claudecode.core.TokenTracker; import com.claudecode.core.TokenTracker;
/** /**
* /usage 命令 —— 详细 token 和费用统计。 * /usage 命令 —— 详细 token 和费用统计。
*/ */
public class UsageCommand implements SlashCommand { public class UsageCommand extends BaseSlashCommand {
@Override @Override
public String name() { return "usage"; } public String name() { return "usage"; }
@@ -18,8 +18,8 @@ public class UsageCommand implements SlashCommand {
@Override @Override
public String execute(String args, CommandContext context) { public String execute(String args, CommandContext context) {
if (context.agentLoop() == null) { if (requireAgentLoop(context) == null) {
return AnsiStyle.red(" ✗ No active session"); return noSession();
} }
TokenTracker tracker = context.agentLoop().getTokenTracker(); TokenTracker tracker = context.agentLoop().getTokenTracker();
@@ -1,8 +1,7 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; 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.console.AnsiStyle;
/** /**
@@ -10,7 +9,7 @@ import com.claudecode.console.AnsiStyle;
* <p> * <p>
* 展示 Claude Code Java 版本、运行时环境等信息。 * 展示 Claude Code Java 版本、运行时环境等信息。
*/ */
public class VersionCommand implements SlashCommand { public class VersionCommand extends BaseSlashCommand {
/** 当前版本号 */ /** 当前版本号 */
public static final String VERSION = "1.0.0"; public static final String VERSION = "1.0.0";
@@ -29,7 +28,7 @@ public class VersionCommand implements SlashCommand {
@Override @Override
public String execute(String args, CommandContext context) { public String execute(String args, CommandContext context) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(CommandUtils.header("🏷️", "Claude Code Java")); sb.append(header("🏷️", "Claude Code Java"));
sb.append(" ").append(AnsiStyle.bold("Version: ")) sb.append(" ").append(AnsiStyle.bold("Version: "))
.append(AnsiStyle.cyan("v" + VERSION)).append("\n"); .append(AnsiStyle.cyan("v" + VERSION)).append("\n");
@@ -1,13 +1,13 @@
package com.claudecode.command.impl; package com.claudecode.command.impl;
import com.claudecode.command.CommandContext; import com.claudecode.command.CommandContext;
import com.claudecode.command.SlashCommand; import com.claudecode.command.BaseSlashCommand;
import com.claudecode.console.AnsiStyle; import com.claudecode.console.AnsiStyle;
/** /**
* /vim 命令 —— 切换 Vi 编辑模式(JLine vi-mode)。 * /vim 命令 —— 切换 Vi 编辑模式(JLine vi-mode)。
*/ */
public class VimCommand implements SlashCommand { public class VimCommand extends BaseSlashCommand {
@Override @Override
public String name() { return "vim"; } public String name() { return "vim"; }
@@ -17,14 +17,14 @@ public class VimCommand implements SlashCommand {
@Override @Override
public String execute(String args, CommandContext context) { public String execute(String args, CommandContext context) {
if (context.agentLoop() == null) { if (requireAgentLoop(context) == null) {
return AnsiStyle.red(" ✗ No active session"); return noSession();
} }
var toolCtx = context.agentLoop().getToolContext(); var toolCtx = toolCtx(context);
boolean current = Boolean.TRUE.equals(toolCtx.get("VIM_MODE")); boolean current = Boolean.TRUE.equals(toolCtx.get("VIM_MODE"));
String trimmed = (args == null) ? "" : args.trim(); String trimmed = args(args);
boolean newMode = switch (trimmed) { boolean newMode = switch (trimmed) {
case "on", "enable" -> true; case "on", "enable" -> true;
case "off", "disable" -> false; case "off", "disable" -> false;
+171
View File
@@ -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()