generated from nhcarrigan/template
feat: CLI v2.1.68–v2.1.74 compatibility updates (#221)
## Summary This PR brings Hikari Desktop up to full compatibility with Claude Code CLI versions v2.1.68 through v2.1.74, implementing all changelog items audited in issues #200–#218. ## Changes ### Bug Fixes - Remove deprecated Claude Opus 4.0 and 4.1 models from the model selector - Auto-migrate users pinned to deprecated models to Opus 4.6 ### New Features - Add cron tool support (`CronCreate`, `CronDelete`, `CronList`) with character state mapping and `CLAUDE_CODE_DISABLE_CRON` settings toggle - Handle `EnterWorktree` and `ExitWorktree` tools in character state mapping and tool display - Add CLI update check with npm registry indicator in the version bar - Add `agent_type` field and support the Agent tool rename from CLI v2.1.69 - Consume `worktree` field from status line hook events - Display per-agent model override in the agent monitor tree - Expose Claude Code CLI built-in slash commands (`/simplify`, `/loop`, `/batch`, `/memory`, `/context`) in the command menu with CLI badges - Add `includeGitInstructions` toggle in settings - Add `ENABLE_CLAUDEAI_MCP_SERVERS` opt-out setting - Linkify MCP binary file paths (PDFs, audio, Office docs) in markdown output - Add auto-memory panel, `/memory` slash command shortcut, and unified toast notification system - Toast notifications for `WorktreeCreate` and `WorktreeRemove` hook events - Sort session resume list by most recent activity, with most recent user message as preview - Convert WSL Linux paths to Windows UNC paths when opening binary files via `open_binary_file` command - Expose `autoMemoryDirectory` setting in ConfigSidebar (Agent Settings section) - Add `/context` as a CLI built-in in the slash command menu - Expose `modelOverrides` setting as a JSON textarea in ConfigSidebar (for AWS Bedrock, Google Vertex, etc.) > **Note:** The CLI update check commit does not have a corresponding issue — it was a bonus addition during the audit sprint. ## Closes Closes #200 Closes #201 Closes #202 Closes #205 Closes #206 Closes #207 Closes #208 Closes #209 Closes #210 Closes #211 Closes #212 Closes #213 Closes #214 Closes #215 Closes #216 Closes #217 Closes #218 Reviewed-on: #221 Co-authored-by: Hikari <hikari@nhcarrigan.com> Co-committed-by: Hikari <hikari@nhcarrigan.com>
This commit was merged in pull request #221.
This commit is contained in:
@@ -662,6 +662,37 @@ pub async fn fetch_changelog() -> Result<Vec<ChangelogEntry>, String> {
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn parse_npm_cli_version(json: &str) -> Result<String, String> {
|
||||
let data: serde_json::Value =
|
||||
serde_json::from_str(json).map_err(|e| format!("Failed to parse response: {}", e))?;
|
||||
data.get("version")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_string())
|
||||
.ok_or_else(|| "No version field in response".to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn check_cli_latest_version() -> Result<String, String> {
|
||||
let client = reqwest::Client::new();
|
||||
let response = client
|
||||
.get("https://registry.npmjs.org/@anthropic-ai/claude-code/latest")
|
||||
.header("Accept", "application/json")
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to fetch CLI version: {}", e))?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(format!("Registry returned status: {}", response.status()));
|
||||
}
|
||||
|
||||
let body = response
|
||||
.text()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to read response: {}", e))?;
|
||||
|
||||
parse_npm_cli_version(&body)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize)]
|
||||
pub struct SavedFileInfo {
|
||||
pub path: String,
|
||||
@@ -2547,6 +2578,32 @@ pub async fn scan_project(working_dir: String) -> Result<ProjectScan, String> {
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn open_binary_file(app: AppHandle, path: String) -> Result<(), String> {
|
||||
use tauri_plugin_opener::OpenerExt;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
// Convert the WSL Linux path (e.g. /tmp/file.pdf) to a Windows UNC path
|
||||
// (e.g. \\wsl.localhost\Ubuntu\tmp\file.pdf) so the Windows shell can open it.
|
||||
let output = std::process::Command::new("wsl")
|
||||
.args(["wslpath", "-w", &path])
|
||||
.output()
|
||||
.map_err(|e| e.to_string())?;
|
||||
let windows_path = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
app.opener()
|
||||
.open_path(windows_path, None::<&str>)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
app.opener()
|
||||
.open_path(path, None::<&str>)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -2838,6 +2895,35 @@ mod tests {
|
||||
assert!(json.contains("null") || json.contains("release_notes"));
|
||||
}
|
||||
|
||||
// ==================== parse_npm_cli_version tests ====================
|
||||
|
||||
#[test]
|
||||
fn test_parse_npm_cli_version_valid() {
|
||||
let json = r#"{"name":"@anthropic-ai/claude-code","version":"2.1.72","description":"Claude Code"}"#;
|
||||
let result = parse_npm_cli_version(json).unwrap();
|
||||
assert_eq!(result, "2.1.72");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_npm_cli_version_missing_field() {
|
||||
let json = r#"{"name":"@anthropic-ai/claude-code","description":"no version here"}"#;
|
||||
let result = parse_npm_cli_version(json);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_npm_cli_version_invalid_json() {
|
||||
let result = parse_npm_cli_version("not json at all");
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_npm_cli_version_non_string_version() {
|
||||
let json = r#"{"version":123}"#;
|
||||
let result = parse_npm_cli_version(json);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
// ==================== SavedFileInfo struct tests ====================
|
||||
|
||||
#[test]
|
||||
@@ -3232,4 +3318,39 @@ gitea: gitea-mcp -t stdio (STDIO) - ✓ Connected"#;
|
||||
Some("Indented Heading".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== open_binary_file E2E path conversion tests ====================
|
||||
|
||||
/// Build the wslpath command structure without executing it, for cross-platform CI testing.
|
||||
#[cfg(test)]
|
||||
fn build_wslpath_command(path: &str) -> (String, Vec<String>) {
|
||||
(
|
||||
"wsl".to_string(),
|
||||
vec!["wslpath".to_string(), "-w".to_string(), path.to_string()],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_e2e_wslpath_command_structure_pdf() {
|
||||
let (command, args) = build_wslpath_command("/tmp/mcp_output_abc123.pdf");
|
||||
assert_eq!(command, "wsl");
|
||||
assert_eq!(args.len(), 3);
|
||||
assert_eq!(args[0], "wslpath");
|
||||
assert_eq!(args[1], "-w");
|
||||
assert_eq!(args[2], "/tmp/mcp_output_abc123.pdf");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_e2e_wslpath_command_structure_audio() {
|
||||
let (command, args) = build_wslpath_command("/tmp/mcp_output_xyz789.mp3");
|
||||
assert_eq!(command, "wsl");
|
||||
assert_eq!(args[2], "/tmp/mcp_output_xyz789.mp3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_e2e_wslpath_command_structure_preserves_path() {
|
||||
let path = "/home/naomi/documents/report with spaces.pdf";
|
||||
let (_, args) = build_wslpath_command(path);
|
||||
assert_eq!(args[2], path);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user