JLine 3 集成: - ReplSession重写: JLine Terminal + LineReader 替代Scanner - 支持行编辑(光标移动、删除、Home/End等) - 持久化历史记录(~/.claude-code-java/history) - 上下箭头浏览输入历史 - Ctrl+C取消当前输入、Ctrl+D退出 - JLine失败时自动降级到Scanner模式 Tab补全: - ClaudeCodeCompleter: 斜杠命令补全(/后按Tab) - 支持命令别名补全(如 /q → exit) - 分组显示(Commands / Aliases) 新增命令: - /model: 显示当前AI模型信息 - /compact: 压缩对话上下文 - /cost: Token用量显示(占位) 改进: - HelpCommand清理为统一格式 - Banner增加终端信息显示和快捷键提示 - 彩色提示符使用AttributedStringBuilder Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>pull/1/head
parent
70c9ebed2b
commit
b29d61581a
@ -0,0 +1,34 @@ |
|||||||
|
package com.claudecode.command.impl; |
||||||
|
|
||||||
|
import com.claudecode.command.CommandContext; |
||||||
|
import com.claudecode.command.SlashCommand; |
||||||
|
import com.claudecode.console.AnsiStyle; |
||||||
|
|
||||||
|
/** |
||||||
|
* /compact 命令 —— 压缩当前对话上下文。 |
||||||
|
* <p> |
||||||
|
* 对应 claude-code/src/commands/compact.ts。 |
||||||
|
* 当前为简化实现,直接清空历史并提示用户。 |
||||||
|
*/ |
||||||
|
public class CompactCommand implements SlashCommand { |
||||||
|
|
||||||
|
@Override |
||||||
|
public String name() { |
||||||
|
return "compact"; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String description() { |
||||||
|
return "Compact conversation context"; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String execute(String args, CommandContext context) { |
||||||
|
if (context.agentLoop() != null) { |
||||||
|
int before = context.agentLoop().getMessageHistory().size(); |
||||||
|
context.agentLoop().reset(); |
||||||
|
return AnsiStyle.green(" ✓ Context compacted: " + before + " messages → 1 (system prompt only)"); |
||||||
|
} |
||||||
|
return AnsiStyle.yellow(" ⚠ No active conversation to compact."); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,44 @@ |
|||||||
|
package com.claudecode.command.impl; |
||||||
|
|
||||||
|
import com.claudecode.command.CommandContext; |
||||||
|
import com.claudecode.command.SlashCommand; |
||||||
|
import com.claudecode.console.AnsiStyle; |
||||||
|
|
||||||
|
/** |
||||||
|
* /cost 命令 —— 显示 Token 使用量和费用估算。 |
||||||
|
* <p> |
||||||
|
* 对应 claude-code/src/commands/cost.ts。 |
||||||
|
* 当前为占位实现,后续接入实际 Token 统计。 |
||||||
|
*/ |
||||||
|
public class CostCommand implements SlashCommand { |
||||||
|
|
||||||
|
@Override |
||||||
|
public String name() { |
||||||
|
return "cost"; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String description() { |
||||||
|
return "Show token usage and cost"; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String execute(String args, CommandContext context) { |
||||||
|
int msgCount = 0; |
||||||
|
if (context.agentLoop() != null) { |
||||||
|
msgCount = context.agentLoop().getMessageHistory().size(); |
||||||
|
} |
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder(); |
||||||
|
sb.append("\n"); |
||||||
|
sb.append(AnsiStyle.bold(" Token Usage:\n\n")); |
||||||
|
sb.append(" Messages: ").append(AnsiStyle.cyan(String.valueOf(msgCount))).append("\n"); |
||||||
|
sb.append(" Input tokens: ").append(AnsiStyle.dim("(tracking not yet implemented)")).append("\n"); |
||||||
|
sb.append(" Output tokens:").append(AnsiStyle.dim("(tracking not yet implemented)")).append("\n"); |
||||||
|
sb.append(" Est. cost: ").append(AnsiStyle.dim("(tracking not yet implemented)")).append("\n"); |
||||||
|
sb.append("\n"); |
||||||
|
sb.append(AnsiStyle.dim(" Token tracking will be added in a future update.")); |
||||||
|
|
||||||
|
return sb.toString(); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,46 @@ |
|||||||
|
package com.claudecode.command.impl; |
||||||
|
|
||||||
|
import com.claudecode.command.CommandContext; |
||||||
|
import com.claudecode.command.SlashCommand; |
||||||
|
import com.claudecode.console.AnsiStyle; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* /model 命令 —— 显示或切换当前 AI 模型。 |
||||||
|
*/ |
||||||
|
public class ModelCommand implements SlashCommand { |
||||||
|
|
||||||
|
@Override |
||||||
|
public String name() { |
||||||
|
return "model"; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String description() { |
||||||
|
return "Show or switch AI model"; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public List<String> aliases() { |
||||||
|
return List.of("m"); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String execute(String args, CommandContext context) { |
||||||
|
// 当前只显示信息,后续可扩展为切换模型
|
||||||
|
StringBuilder sb = new StringBuilder(); |
||||||
|
sb.append("\n"); |
||||||
|
sb.append(AnsiStyle.bold(" Model Configuration:\n\n")); |
||||||
|
sb.append(" Provider: ").append(AnsiStyle.cyan("Anthropic")).append("\n"); |
||||||
|
sb.append(" Model: ").append(AnsiStyle.cyan( |
||||||
|
System.getenv().getOrDefault("AI_MODEL", "claude-sonnet-4-20250514"))).append("\n"); |
||||||
|
|
||||||
|
if (args != null && !args.isBlank()) { |
||||||
|
sb.append("\n"); |
||||||
|
sb.append(AnsiStyle.yellow(" ⚠ Model switching not yet implemented. Set AI_MODEL env variable.")); |
||||||
|
} |
||||||
|
|
||||||
|
return sb.toString(); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,73 @@ |
|||||||
|
package com.claudecode.repl; |
||||||
|
|
||||||
|
import com.claudecode.command.CommandRegistry; |
||||||
|
import com.claudecode.command.SlashCommand; |
||||||
|
import com.claudecode.tool.ToolRegistry; |
||||||
|
import org.jline.reader.Candidate; |
||||||
|
import org.jline.reader.Completer; |
||||||
|
import org.jline.reader.LineReader; |
||||||
|
import org.jline.reader.ParsedLine; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* Tab 补全器 —— 对应 claude-code 中的命令补全逻辑。 |
||||||
|
* <p> |
||||||
|
* 支持: |
||||||
|
* <ul> |
||||||
|
* <li>斜杠命令补全(输入 / 后按 Tab)</li> |
||||||
|
* <li>工具名称补全(用于调试或直接引用)</li> |
||||||
|
* </ul> |
||||||
|
*/ |
||||||
|
public class ClaudeCodeCompleter implements Completer { |
||||||
|
|
||||||
|
private final CommandRegistry commandRegistry; |
||||||
|
private final ToolRegistry toolRegistry; |
||||||
|
|
||||||
|
public ClaudeCodeCompleter(CommandRegistry commandRegistry, ToolRegistry toolRegistry) { |
||||||
|
this.commandRegistry = commandRegistry; |
||||||
|
this.toolRegistry = toolRegistry; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void complete(LineReader reader, ParsedLine line, List<Candidate> candidates) { |
||||||
|
String buffer = line.line().substring(0, line.cursor()); |
||||||
|
|
||||||
|
if (buffer.startsWith("/")) { |
||||||
|
// 斜杠命令补全
|
||||||
|
completeCommands(buffer, candidates); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** 补全斜杠命令 */ |
||||||
|
private void completeCommands(String buffer, List<Candidate> candidates) { |
||||||
|
String prefix = buffer.substring(1).toLowerCase(); |
||||||
|
|
||||||
|
for (SlashCommand cmd : commandRegistry.getCommands()) { |
||||||
|
String name = cmd.name(); |
||||||
|
if (name.startsWith(prefix)) { |
||||||
|
candidates.add(new Candidate( |
||||||
|
"/" + name, // 补全值
|
||||||
|
name, // 显示文本
|
||||||
|
"Commands", // 分组
|
||||||
|
cmd.description(), // 描述(右侧提示)
|
||||||
|
null, // 后缀
|
||||||
|
null, // 关键字
|
||||||
|
true // 完整补全
|
||||||
|
)); |
||||||
|
} |
||||||
|
// 也匹配别名
|
||||||
|
for (String alias : cmd.aliases()) { |
||||||
|
if (alias.startsWith(prefix)) { |
||||||
|
candidates.add(new Candidate( |
||||||
|
"/" + alias, |
||||||
|
alias + " → " + name, |
||||||
|
"Aliases", |
||||||
|
cmd.description(), |
||||||
|
null, null, true |
||||||
|
)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue