generated from nhcarrigan/template
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 262a705996 | |||
| 6b6f9e2a00 |
+1
-1
@@ -72,7 +72,7 @@
|
||||
"@sveltejs/kit": "^2.9.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"@tauri-apps/cli": "^2",
|
||||
"@tauri-apps/cli": "2.10.0",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/svelte": "^5.3.1",
|
||||
"@vitest/coverage-v8": "^4.0.18",
|
||||
|
||||
Generated
+49
-49
@@ -139,8 +139,8 @@ importers:
|
||||
specifier: ^4.1.18
|
||||
version: 4.1.18(vite@6.4.1(jiti@2.6.1)(lightningcss@1.30.2))
|
||||
'@tauri-apps/cli':
|
||||
specifier: ^2
|
||||
version: 2.9.6
|
||||
specifier: 2.10.0
|
||||
version: 2.10.0
|
||||
'@testing-library/jest-dom':
|
||||
specifier: ^6.9.1
|
||||
version: 6.9.1
|
||||
@@ -909,74 +909,74 @@ packages:
|
||||
'@tauri-apps/api@2.9.1':
|
||||
resolution: {integrity: sha512-IGlhP6EivjXHepbBic618GOmiWe4URJiIeZFlB7x3czM0yDHHYviH1Xvoiv4FefdkQtn6v7TuwWCRfOGdnVUGw==}
|
||||
|
||||
'@tauri-apps/cli-darwin-arm64@2.9.6':
|
||||
resolution: {integrity: sha512-gf5no6N9FCk1qMrti4lfwP77JHP5haASZgVbBgpZG7BUepB3fhiLCXGUK8LvuOjP36HivXewjg72LTnPDScnQQ==}
|
||||
'@tauri-apps/cli-darwin-arm64@2.10.0':
|
||||
resolution: {integrity: sha512-avqHD4HRjrMamE/7R/kzJPcAJnZs0IIS+1nkDP5b+TNBn3py7N2aIo9LIpy+VQq0AkN8G5dDpZtOOBkmWt/zjA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@tauri-apps/cli-darwin-x64@2.9.6':
|
||||
resolution: {integrity: sha512-oWh74WmqbERwwrwcueJyY6HYhgCksUc6NT7WKeXyrlY/FPmNgdyQAgcLuTSkhRFuQ6zh4Np1HZpOqCTpeZBDcw==}
|
||||
'@tauri-apps/cli-darwin-x64@2.10.0':
|
||||
resolution: {integrity: sha512-keDmlvJRStzVFjZTd0xYkBONLtgBC9eMTpmXnBXzsHuawV2q9PvDo2x6D5mhuoMVrJ9QWjgaPKBBCFks4dK71Q==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@tauri-apps/cli-linux-arm-gnueabihf@2.9.6':
|
||||
resolution: {integrity: sha512-/zde3bFroFsNXOHN204DC2qUxAcAanUjVXXSdEGmhwMUZeAQalNj5cz2Qli2elsRjKN/hVbZOJj0gQ5zaYUjSg==}
|
||||
'@tauri-apps/cli-linux-arm-gnueabihf@2.10.0':
|
||||
resolution: {integrity: sha512-e5u0VfLZsMAC9iHaOEANumgl6lfnJx0Dtjkd8IJpysZ8jp0tJ6wrIkto2OzQgzcYyRCKgX72aKE0PFgZputA8g==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@tauri-apps/cli-linux-arm64-gnu@2.9.6':
|
||||
resolution: {integrity: sha512-pvbljdhp9VOo4RnID5ywSxgBs7qiylTPlK56cTk7InR3kYSTJKYMqv/4Q/4rGo/mG8cVppesKIeBMH42fw6wjg==}
|
||||
'@tauri-apps/cli-linux-arm64-gnu@2.10.0':
|
||||
resolution: {integrity: sha512-YrYYk2dfmBs5m+OIMCrb+JH/oo+4FtlpcrTCgiFYc7vcs6m3QDd1TTyWu0u01ewsCtK2kOdluhr/zKku+KP7HA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@tauri-apps/cli-linux-arm64-musl@2.9.6':
|
||||
resolution: {integrity: sha512-02TKUndpodXBCR0oP//6dZWGYcc22Upf2eP27NvC6z0DIqvkBBFziQUcvi2n6SrwTRL0yGgQjkm9K5NIn8s6jw==}
|
||||
'@tauri-apps/cli-linux-arm64-musl@2.10.0':
|
||||
resolution: {integrity: sha512-GUoPdVJmrJRIXFfW3Rkt+eGK9ygOdyISACZfC/bCSfOnGt8kNdQIQr5WRH9QUaTVFIwxMlQyV3m+yXYP+xhSVA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@tauri-apps/cli-linux-riscv64-gnu@2.9.6':
|
||||
resolution: {integrity: sha512-fmp1hnulbqzl1GkXl4aTX9fV+ubHw2LqlLH1PE3BxZ11EQk+l/TmiEongjnxF0ie4kV8DQfDNJ1KGiIdWe1GvQ==}
|
||||
'@tauri-apps/cli-linux-riscv64-gnu@2.10.0':
|
||||
resolution: {integrity: sha512-JO7s3TlSxshwsoKNCDkyvsx5gw2QAs/Y2GbR5UE2d5kkU138ATKoPOtxn8G1fFT1aDW4LH0rYAAfBpGkDyJJnw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@tauri-apps/cli-linux-x64-gnu@2.9.6':
|
||||
resolution: {integrity: sha512-vY0le8ad2KaV1PJr+jCd8fUF9VOjwwQP/uBuTJvhvKTloEwxYA/kAjKK9OpIslGA9m/zcnSo74czI6bBrm2sYA==}
|
||||
'@tauri-apps/cli-linux-x64-gnu@2.10.0':
|
||||
resolution: {integrity: sha512-Uvh4SUUp4A6DVRSMWjelww0GnZI3PlVy7VS+DRF5napKuIehVjGl9XD0uKoCoxwAQBLctvipyEK+pDXpJeoHng==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@tauri-apps/cli-linux-x64-musl@2.9.6':
|
||||
resolution: {integrity: sha512-TOEuB8YCFZTWVDzsO2yW0+zGcoMiPPwcUgdnW1ODnmgfwccpnihDRoks+ABT1e3fHb1ol8QQWsHSCovb3o2ENQ==}
|
||||
'@tauri-apps/cli-linux-x64-musl@2.10.0':
|
||||
resolution: {integrity: sha512-AP0KRK6bJuTpQ8kMNWvhIpKUkQJfcPFeba7QshOQZjJ8wOS6emwTN4K5g/d3AbCMo0RRdnZWwu67MlmtJyxC1Q==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@tauri-apps/cli-win32-arm64-msvc@2.9.6':
|
||||
resolution: {integrity: sha512-ujmDGMRc4qRLAnj8nNG26Rlz9klJ0I0jmZs2BPpmNNf0gM/rcVHhqbEkAaHPTBVIrtUdf7bGvQAD2pyIiUrBHQ==}
|
||||
'@tauri-apps/cli-win32-arm64-msvc@2.10.0':
|
||||
resolution: {integrity: sha512-97DXVU3dJystrq7W41IX+82JEorLNY+3+ECYxvXWqkq7DBN6FsA08x/EFGE8N/b0LTOui9X2dvpGGoeZKKV08g==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@tauri-apps/cli-win32-ia32-msvc@2.9.6':
|
||||
resolution: {integrity: sha512-S4pT0yAJgFX8QRCyKA1iKjZ9Q/oPjCZf66A/VlG5Yw54Nnr88J1uBpmenINbXxzyhduWrIXBaUbEY1K80ZbpMg==}
|
||||
'@tauri-apps/cli-win32-ia32-msvc@2.10.0':
|
||||
resolution: {integrity: sha512-EHyQ1iwrWy1CwMalEm9z2a6L5isQ121pe7FcA2xe4VWMJp+GHSDDGvbTv/OPdkt2Lyr7DAZBpZHM6nvlHXEc4A==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@tauri-apps/cli-win32-x64-msvc@2.9.6':
|
||||
resolution: {integrity: sha512-ldWuWSSkWbKOPjQMJoYVj9wLHcOniv7diyI5UAJ4XsBdtaFB0pKHQsqw/ItUma0VXGC7vB4E9fZjivmxur60aw==}
|
||||
'@tauri-apps/cli-win32-x64-msvc@2.10.0':
|
||||
resolution: {integrity: sha512-NTpyQxkpzGmU6ceWBTY2xRIEaS0ZLbVx1HE1zTA3TY/pV3+cPoPPOs+7YScr4IMzXMtOw7tLw5LEXo5oIG3qaQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@tauri-apps/cli@2.9.6':
|
||||
resolution: {integrity: sha512-3xDdXL5omQ3sPfBfdC8fCtDKcnyV7OqyzQgfyT5P3+zY6lcPqIYKQBvUasNvppi21RSdfhy44ttvJmftb0PCDw==}
|
||||
'@tauri-apps/cli@2.10.0':
|
||||
resolution: {integrity: sha512-ZwT0T+7bw4+DPCSWzmviwq5XbXlM0cNoleDKOYPFYqcZqeKY31KlpoMW/MOON/tOFBPgi31a2v3w9gliqwL2+Q==}
|
||||
engines: {node: '>= 10'}
|
||||
hasBin: true
|
||||
|
||||
@@ -2863,52 +2863,52 @@ snapshots:
|
||||
|
||||
'@tauri-apps/api@2.9.1': {}
|
||||
|
||||
'@tauri-apps/cli-darwin-arm64@2.9.6':
|
||||
'@tauri-apps/cli-darwin-arm64@2.10.0':
|
||||
optional: true
|
||||
|
||||
'@tauri-apps/cli-darwin-x64@2.9.6':
|
||||
'@tauri-apps/cli-darwin-x64@2.10.0':
|
||||
optional: true
|
||||
|
||||
'@tauri-apps/cli-linux-arm-gnueabihf@2.9.6':
|
||||
'@tauri-apps/cli-linux-arm-gnueabihf@2.10.0':
|
||||
optional: true
|
||||
|
||||
'@tauri-apps/cli-linux-arm64-gnu@2.9.6':
|
||||
'@tauri-apps/cli-linux-arm64-gnu@2.10.0':
|
||||
optional: true
|
||||
|
||||
'@tauri-apps/cli-linux-arm64-musl@2.9.6':
|
||||
'@tauri-apps/cli-linux-arm64-musl@2.10.0':
|
||||
optional: true
|
||||
|
||||
'@tauri-apps/cli-linux-riscv64-gnu@2.9.6':
|
||||
'@tauri-apps/cli-linux-riscv64-gnu@2.10.0':
|
||||
optional: true
|
||||
|
||||
'@tauri-apps/cli-linux-x64-gnu@2.9.6':
|
||||
'@tauri-apps/cli-linux-x64-gnu@2.10.0':
|
||||
optional: true
|
||||
|
||||
'@tauri-apps/cli-linux-x64-musl@2.9.6':
|
||||
'@tauri-apps/cli-linux-x64-musl@2.10.0':
|
||||
optional: true
|
||||
|
||||
'@tauri-apps/cli-win32-arm64-msvc@2.9.6':
|
||||
'@tauri-apps/cli-win32-arm64-msvc@2.10.0':
|
||||
optional: true
|
||||
|
||||
'@tauri-apps/cli-win32-ia32-msvc@2.9.6':
|
||||
'@tauri-apps/cli-win32-ia32-msvc@2.10.0':
|
||||
optional: true
|
||||
|
||||
'@tauri-apps/cli-win32-x64-msvc@2.9.6':
|
||||
'@tauri-apps/cli-win32-x64-msvc@2.10.0':
|
||||
optional: true
|
||||
|
||||
'@tauri-apps/cli@2.9.6':
|
||||
'@tauri-apps/cli@2.10.0':
|
||||
optionalDependencies:
|
||||
'@tauri-apps/cli-darwin-arm64': 2.9.6
|
||||
'@tauri-apps/cli-darwin-x64': 2.9.6
|
||||
'@tauri-apps/cli-linux-arm-gnueabihf': 2.9.6
|
||||
'@tauri-apps/cli-linux-arm64-gnu': 2.9.6
|
||||
'@tauri-apps/cli-linux-arm64-musl': 2.9.6
|
||||
'@tauri-apps/cli-linux-riscv64-gnu': 2.9.6
|
||||
'@tauri-apps/cli-linux-x64-gnu': 2.9.6
|
||||
'@tauri-apps/cli-linux-x64-musl': 2.9.6
|
||||
'@tauri-apps/cli-win32-arm64-msvc': 2.9.6
|
||||
'@tauri-apps/cli-win32-ia32-msvc': 2.9.6
|
||||
'@tauri-apps/cli-win32-x64-msvc': 2.9.6
|
||||
'@tauri-apps/cli-darwin-arm64': 2.10.0
|
||||
'@tauri-apps/cli-darwin-x64': 2.10.0
|
||||
'@tauri-apps/cli-linux-arm-gnueabihf': 2.10.0
|
||||
'@tauri-apps/cli-linux-arm64-gnu': 2.10.0
|
||||
'@tauri-apps/cli-linux-arm64-musl': 2.10.0
|
||||
'@tauri-apps/cli-linux-riscv64-gnu': 2.10.0
|
||||
'@tauri-apps/cli-linux-x64-gnu': 2.10.0
|
||||
'@tauri-apps/cli-linux-x64-musl': 2.10.0
|
||||
'@tauri-apps/cli-win32-arm64-msvc': 2.10.0
|
||||
'@tauri-apps/cli-win32-ia32-msvc': 2.10.0
|
||||
'@tauri-apps/cli-win32-x64-msvc': 2.10.0
|
||||
|
||||
'@tauri-apps/plugin-clipboard-manager@2.3.2':
|
||||
dependencies:
|
||||
|
||||
@@ -8,7 +8,6 @@ use crate::bridge_manager::SharedBridgeManager;
|
||||
use crate::config::{ClaudeStartOptions, HikariConfig};
|
||||
use crate::stats::UsageStats;
|
||||
use crate::temp_manager::SharedTempFileManager;
|
||||
use crate::utils::normalize_path_separators;
|
||||
|
||||
const CONFIG_STORE_KEY: &str = "config";
|
||||
|
||||
@@ -127,7 +126,7 @@ pub async fn validate_directory(
|
||||
|
||||
// Expand ~ to home directory
|
||||
let expanded_path = if path.starts_with("~") {
|
||||
if let Some(home) = std::env::var_os("HOME").or_else(|| std::env::var_os("USERPROFILE")) {
|
||||
if let Some(home) = std::env::var_os("HOME") {
|
||||
let home_path = Path::new(&home);
|
||||
if path == Path::new("~") {
|
||||
home_path.to_path_buf()
|
||||
@@ -163,10 +162,10 @@ pub async fn validate_directory(
|
||||
));
|
||||
}
|
||||
|
||||
// Return the canonicalized (absolute) path with forward slashes
|
||||
// Return the canonicalized (absolute) path
|
||||
expanded_path
|
||||
.canonicalize()
|
||||
.map(|p| normalize_path_separators(&p.to_string_lossy()))
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.map_err(|e| format!("Failed to resolve path: {}", e))
|
||||
}
|
||||
|
||||
@@ -206,10 +205,9 @@ pub async fn list_skills() -> Result<Vec<String>, String> {
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
// Get the home directory - use HOME on Unix, USERPROFILE on Windows
|
||||
let home = std::env::var_os("HOME")
|
||||
.or_else(|| std::env::var_os("USERPROFILE"))
|
||||
.ok_or_else(|| "Could not determine home directory".to_string())?;
|
||||
// Get the home directory
|
||||
let home =
|
||||
std::env::var_os("HOME").ok_or_else(|| "Could not determine home directory".to_string())?;
|
||||
|
||||
let skills_dir = Path::new(&home).join(".claude").join("skills");
|
||||
|
||||
@@ -439,11 +437,9 @@ pub async fn list_directory(path: String) -> Result<Vec<FileEntry>, String> {
|
||||
continue;
|
||||
}
|
||||
|
||||
let path_str = normalize_path_separators(&path.to_string_lossy());
|
||||
|
||||
file_entries.push(FileEntry {
|
||||
name,
|
||||
path: path_str,
|
||||
path: path.to_string_lossy().to_string(),
|
||||
is_directory: path.is_dir(),
|
||||
});
|
||||
}
|
||||
@@ -818,25 +814,4 @@ mod tests {
|
||||
assert!(json.contains("/tmp/test.txt"));
|
||||
assert!(json.contains("test.txt"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_directory_normalizes_backslashes() {
|
||||
// This test ensures that paths with backslashes are normalized
|
||||
// Create a temp directory for testing
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
|
||||
// On Windows, the canonicalized path will have backslashes
|
||||
// Our normalize_path_separators should convert them to forward slashes
|
||||
let result = run_async(validate_directory(
|
||||
temp_dir.path().to_string_lossy().to_string(),
|
||||
None,
|
||||
));
|
||||
|
||||
assert!(result.is_ok());
|
||||
let normalized_path = result.unwrap();
|
||||
|
||||
// The result should never contain backslashes
|
||||
assert!(!normalized_path.contains('\\'),
|
||||
"Path should not contain backslashes: {}", normalized_path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ mod stats;
|
||||
mod temp_manager;
|
||||
mod tray;
|
||||
mod types;
|
||||
mod utils;
|
||||
mod vbs_notification;
|
||||
mod windows_toast;
|
||||
mod wsl_bridge;
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
/// Utility functions for cross-platform compatibility
|
||||
|
||||
/// Normalize path separators to forward slashes for consistent handling across platforms
|
||||
/// This always normalizes backslashes to forward slashes, regardless of platform,
|
||||
/// because Windows paths may be passed to WSL which expects Unix-style paths
|
||||
pub fn normalize_path_separators(path: &str) -> String {
|
||||
path.replace('\\', "/")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "windows")]
|
||||
fn test_normalize_path_windows() {
|
||||
assert_eq!(normalize_path_separators("C:\\Users\\test"), "C:/Users/test");
|
||||
assert_eq!(normalize_path_separators("path\\to\\file"), "path/to/file");
|
||||
assert_eq!(normalize_path_separators("already/forward"), "already/forward");
|
||||
assert_eq!(normalize_path_separators("mixed\\path/file"), "mixed/path/file");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn test_normalize_path_unix() {
|
||||
assert_eq!(normalize_path_separators("/home/user"), "/home/user");
|
||||
assert_eq!(normalize_path_separators("path/to/file"), "path/to/file");
|
||||
// Even on Unix, we normalize backslashes since paths may come from Windows
|
||||
assert_eq!(normalize_path_separators("weird\\path"), "weird/path");
|
||||
assert_eq!(normalize_path_separators("/home/user\\file"), "/home/user/file");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalize_wsl_paths() {
|
||||
// Test the exact issue that was happening
|
||||
assert_eq!(
|
||||
normalize_path_separators("/home/naomi/code/naomi/portfolio\\.."),
|
||||
"/home/naomi/code/naomi/portfolio/.."
|
||||
);
|
||||
|
||||
// Test mixed separators in WSL paths
|
||||
assert_eq!(
|
||||
normalize_path_separators("/mnt/c\\Users\\naomi\\Documents"),
|
||||
"/mnt/c/Users/naomi/Documents"
|
||||
);
|
||||
|
||||
// Test Windows paths that might be passed to WSL
|
||||
assert_eq!(
|
||||
normalize_path_separators("C:\\Users\\naomi\\.claude\\skills"),
|
||||
"C:/Users/naomi/.claude/skills"
|
||||
);
|
||||
|
||||
// Test that forward slashes remain unchanged
|
||||
assert_eq!(
|
||||
normalize_path_separators("/home/naomi/.claude/skills"),
|
||||
"/home/naomi/.claude/skills"
|
||||
);
|
||||
|
||||
// Test empty string
|
||||
assert_eq!(normalize_path_separators(""), "");
|
||||
|
||||
// Test single backslash
|
||||
assert_eq!(normalize_path_separators("\\"), "/");
|
||||
|
||||
// Test multiple consecutive backslashes
|
||||
assert_eq!(
|
||||
normalize_path_separators("path\\\\to\\\\file"),
|
||||
"path//to//file"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@ use crate::types::{
|
||||
PermissionPromptEvent, QuestionOption, SessionEvent, StateChangeEvent, UserQuestionEvent,
|
||||
WorkingDirectoryEvent,
|
||||
};
|
||||
use crate::utils::normalize_path_separators;
|
||||
use parking_lot::RwLock;
|
||||
|
||||
const SEARCH_TOOLS: [&str; 5] = ["Read", "Glob", "Grep", "WebSearch", "WebFetch"];
|
||||
@@ -137,8 +136,7 @@ impl WslBridge {
|
||||
});
|
||||
|
||||
let working_dir = &options.working_dir;
|
||||
// Normalize path separators to forward slashes
|
||||
self.working_directory = normalize_path_separators(working_dir);
|
||||
self.working_directory = working_dir.clone();
|
||||
|
||||
emit_connection_status(
|
||||
&app,
|
||||
@@ -185,7 +183,7 @@ impl WslBridge {
|
||||
})?;
|
||||
|
||||
eprintln!("[DEBUG] Found claude at: {}", claude_path);
|
||||
eprintln!("[DEBUG] Working dir: {}", &self.working_directory);
|
||||
eprintln!("[DEBUG] Working dir: {}", working_dir);
|
||||
|
||||
let mut cmd = Command::new(&claude_path);
|
||||
cmd.args([
|
||||
@@ -227,7 +225,7 @@ impl WslBridge {
|
||||
}
|
||||
}
|
||||
|
||||
cmd.current_dir(&self.working_directory);
|
||||
cmd.current_dir(working_dir);
|
||||
|
||||
// Set API key as environment variable if specified
|
||||
if let Some(ref api_key) = options.api_key {
|
||||
@@ -243,7 +241,7 @@ impl WslBridge {
|
||||
let mut cmd = Command::new("wsl");
|
||||
|
||||
// Build the claude command with all arguments
|
||||
let mut claude_cmd = format!("cd '{}' && ", &self.working_directory);
|
||||
let mut claude_cmd = format!("cd '{}' && ", working_dir);
|
||||
|
||||
// Set API key as environment variable if specified
|
||||
if let Some(ref api_key) = options.api_key {
|
||||
|
||||
@@ -8,7 +8,6 @@ import type {
|
||||
} from "$lib/types/messages";
|
||||
import type { CharacterState } from "$lib/types/states";
|
||||
import { cleanupConversationTracking } from "$lib/tauri";
|
||||
import { normalizePath } from "$lib/utils/paths";
|
||||
import { characterState } from "$lib/stores/character";
|
||||
import { sessionsStore } from "$lib/stores/sessions";
|
||||
|
||||
@@ -389,7 +388,7 @@ function createConversationsStore() {
|
||||
conversations.update((convs) => {
|
||||
const conv = convs.get(activeId);
|
||||
if (conv) {
|
||||
conv.workingDirectory = normalizePath(dir);
|
||||
conv.workingDirectory = dir;
|
||||
conv.lastActivityAt = new Date();
|
||||
}
|
||||
return convs;
|
||||
@@ -400,7 +399,7 @@ function createConversationsStore() {
|
||||
conversations.update((convs) => {
|
||||
const conv = convs.get(conversationId);
|
||||
if (conv) {
|
||||
conv.workingDirectory = normalizePath(dir);
|
||||
conv.workingDirectory = dir;
|
||||
conv.lastActivityAt = new Date();
|
||||
}
|
||||
return convs;
|
||||
|
||||
+11
-15
@@ -1,7 +1,6 @@
|
||||
import { writable, derived, get } from "svelte/store";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import type { EditorState, EditorTab, FileEntry } from "$lib/types/editor";
|
||||
import { normalizePath, joinPath, getParentPath, getFilename } from "$lib/utils/paths";
|
||||
|
||||
const defaultState: EditorState = {
|
||||
tabs: [],
|
||||
@@ -159,7 +158,7 @@ function createEditorStore() {
|
||||
|
||||
try {
|
||||
const content = await invoke<string>("read_file_content", { path: filePath });
|
||||
const fileName = getFilename(filePath) || "untitled";
|
||||
const fileName = filePath.split(/[/\\]/).pop() || "untitled";
|
||||
const language = getLanguageFromPath(filePath);
|
||||
const newTab: EditorTab = {
|
||||
id: generateTabId(),
|
||||
@@ -248,7 +247,7 @@ function createEditorStore() {
|
||||
}
|
||||
|
||||
async function createFile(parentPath: string, fileName: string): Promise<boolean> {
|
||||
const filePath = joinPath(parentPath, fileName);
|
||||
const filePath = `${parentPath}/${fileName}`;
|
||||
try {
|
||||
await invoke("create_file", { path: filePath });
|
||||
// Refresh the parent directory
|
||||
@@ -262,7 +261,7 @@ function createEditorStore() {
|
||||
}
|
||||
|
||||
async function createDirectory(parentPath: string, dirName: string): Promise<boolean> {
|
||||
const dirPath = joinPath(parentPath, dirName);
|
||||
const dirPath = `${parentPath}/${dirName}`;
|
||||
try {
|
||||
await invoke("create_directory", { path: dirPath });
|
||||
// Refresh the parent directory
|
||||
@@ -285,7 +284,7 @@ function createEditorStore() {
|
||||
closeTab(openTab.id);
|
||||
}
|
||||
// Refresh the parent directory
|
||||
const parentPath = getParentPath(filePath);
|
||||
const parentPath = filePath.substring(0, filePath.lastIndexOf("/"));
|
||||
await refreshDirectory(parentPath);
|
||||
return true;
|
||||
} catch (error) {
|
||||
@@ -300,11 +299,10 @@ function createEditorStore() {
|
||||
await invoke("delete_directory", { path: dirPath });
|
||||
// Close any tabs that are in this directory
|
||||
const currentState = get(state);
|
||||
const normalizedDirPath = normalizePath(dirPath);
|
||||
const tabsToClose = currentState.tabs.filter((t) => normalizePath(t.filePath).startsWith(normalizedDirPath + "/"));
|
||||
const tabsToClose = currentState.tabs.filter((t) => t.filePath.startsWith(dirPath + "/"));
|
||||
tabsToClose.forEach((tab) => closeTab(tab.id));
|
||||
// Refresh the parent directory
|
||||
const parentPath = getParentPath(dirPath);
|
||||
const parentPath = dirPath.substring(0, dirPath.lastIndexOf("/"));
|
||||
await refreshDirectory(parentPath);
|
||||
return true;
|
||||
} catch (error) {
|
||||
@@ -343,8 +341,8 @@ function createEditorStore() {
|
||||
}
|
||||
|
||||
async function renamePath(oldPath: string, newName: string): Promise<boolean> {
|
||||
const parentPath = getParentPath(oldPath);
|
||||
const newPath = joinPath(parentPath, newName);
|
||||
const parentPath = oldPath.substring(0, oldPath.lastIndexOf("/"));
|
||||
const newPath = `${parentPath}/${newName}`;
|
||||
|
||||
try {
|
||||
await invoke("rename_path", { oldPath, newPath });
|
||||
@@ -361,14 +359,12 @@ function createEditorStore() {
|
||||
fileName: newName,
|
||||
};
|
||||
}
|
||||
const normalizedOldPath = normalizePath(oldPath);
|
||||
const normalizedFilePath = normalizePath(t.filePath);
|
||||
if (normalizedFilePath.startsWith(normalizedOldPath + "/")) {
|
||||
if (t.filePath.startsWith(oldPath + "/")) {
|
||||
// File is inside a renamed directory
|
||||
const relativePath = normalizedFilePath.substring(normalizedOldPath.length);
|
||||
const relativePath = t.filePath.substring(oldPath.length);
|
||||
return {
|
||||
...t,
|
||||
filePath: normalizePath(newPath + relativePath),
|
||||
filePath: newPath + relativePath,
|
||||
};
|
||||
}
|
||||
return t;
|
||||
|
||||
+2
-6
@@ -18,7 +18,6 @@ import {
|
||||
handleConnectionStatusChange,
|
||||
handleNewUserMessage,
|
||||
} from "$lib/notifications/rules";
|
||||
import { normalizePath } from "$lib/utils/paths";
|
||||
|
||||
interface StateChangePayload {
|
||||
state: CharacterState;
|
||||
@@ -291,15 +290,12 @@ export async function initializeTauriListeners() {
|
||||
const cwdUnlisten = await listen<WorkingDirectoryPayload>("claude:cwd", (event) => {
|
||||
const { directory, conversation_id } = event.payload;
|
||||
|
||||
// Normalize path separators to forward slashes
|
||||
const normalizedDirectory = normalizePath(directory);
|
||||
|
||||
// Store working directory for the correct conversation
|
||||
if (conversation_id) {
|
||||
claudeStore.setWorkingDirectoryForConversation(conversation_id, normalizedDirectory);
|
||||
claudeStore.setWorkingDirectoryForConversation(conversation_id, directory);
|
||||
} else {
|
||||
// Fallback to active conversation if no conversation_id
|
||||
claudeStore.setWorkingDirectory(normalizedDirectory);
|
||||
claudeStore.setWorkingDirectory(directory);
|
||||
}
|
||||
});
|
||||
unlisteners.push(cwdUnlisten);
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { normalizePath, joinPath, getParentPath, getFilename } from "./paths";
|
||||
|
||||
describe("paths utilities", () => {
|
||||
describe("normalizePath", () => {
|
||||
it("should normalize Windows paths", () => {
|
||||
expect(normalizePath("C:\\Users\\test")).toBe("C:/Users/test");
|
||||
expect(normalizePath("path\\to\\file")).toBe("path/to/file");
|
||||
expect(normalizePath("C:\\Users\\test\\file.txt")).toBe("C:/Users/test/file.txt");
|
||||
});
|
||||
|
||||
it("should leave Unix paths unchanged", () => {
|
||||
expect(normalizePath("/home/user")).toBe("/home/user");
|
||||
expect(normalizePath("path/to/file")).toBe("path/to/file");
|
||||
expect(normalizePath("/usr/local/bin")).toBe("/usr/local/bin");
|
||||
});
|
||||
|
||||
it("should handle mixed separators", () => {
|
||||
expect(normalizePath("mixed\\path/file")).toBe("mixed/path/file");
|
||||
expect(normalizePath("C:\\Users/test\\mixed/path")).toBe("C:/Users/test/mixed/path");
|
||||
});
|
||||
|
||||
it("should handle empty strings", () => {
|
||||
expect(normalizePath("")).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
describe("joinPath", () => {
|
||||
it("should join path segments with forward slashes", () => {
|
||||
expect(joinPath("parent", "child")).toBe("parent/child");
|
||||
expect(joinPath("/home", "user", "documents")).toBe("/home/user/documents");
|
||||
expect(joinPath("C:", "Users", "test")).toBe("C:/Users/test");
|
||||
});
|
||||
|
||||
it("should filter out empty segments", () => {
|
||||
expect(joinPath("parent", "", "child")).toBe("parent/child");
|
||||
expect(joinPath("", "home", "")).toBe("home");
|
||||
expect(joinPath("", "", "")).toBe("");
|
||||
});
|
||||
|
||||
it("should normalize mixed separators in segments", () => {
|
||||
expect(joinPath("parent\\dir", "child")).toBe("parent/dir/child");
|
||||
expect(joinPath("C:\\Users", "test\\file")).toBe("C:/Users/test/file");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getParentPath", () => {
|
||||
it("should get parent directory of Unix paths", () => {
|
||||
expect(getParentPath("/home/user/file.txt")).toBe("/home/user");
|
||||
expect(getParentPath("/home/user/")).toBe("/home");
|
||||
expect(getParentPath("/home")).toBe("/");
|
||||
expect(getParentPath("/")).toBe("/");
|
||||
});
|
||||
|
||||
it("should get parent directory of Windows paths", () => {
|
||||
expect(getParentPath("C:\\Users\\test\\file.txt")).toBe("C:/Users/test");
|
||||
expect(getParentPath("C:\\Users\\test")).toBe("C:/Users");
|
||||
expect(getParentPath("C:\\")).toBe("C:");
|
||||
});
|
||||
|
||||
it("should handle relative paths", () => {
|
||||
expect(getParentPath("parent/child/file.txt")).toBe("parent/child");
|
||||
expect(getParentPath("file.txt")).toBe(".");
|
||||
expect(getParentPath("")).toBe(".");
|
||||
});
|
||||
|
||||
it("should handle paths with trailing slashes", () => {
|
||||
expect(getParentPath("/home/user/")).toBe("/home");
|
||||
expect(getParentPath("parent/child/")).toBe("parent");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getFilename", () => {
|
||||
it("should extract filename from Unix paths", () => {
|
||||
expect(getFilename("/home/user/file.txt")).toBe("file.txt");
|
||||
expect(getFilename("/usr/local/bin/node")).toBe("node");
|
||||
expect(getFilename("/home/user/")).toBe("");
|
||||
});
|
||||
|
||||
it("should extract filename from Windows paths", () => {
|
||||
expect(getFilename("C:\\Users\\test\\file.txt")).toBe("file.txt");
|
||||
expect(getFilename("C:\\Program Files\\app.exe")).toBe("app.exe");
|
||||
expect(getFilename("D:\\folder\\")).toBe("");
|
||||
});
|
||||
|
||||
it("should handle relative paths", () => {
|
||||
expect(getFilename("parent/child/file.txt")).toBe("file.txt");
|
||||
expect(getFilename("file.txt")).toBe("file.txt");
|
||||
expect(getFilename("")).toBe("");
|
||||
});
|
||||
|
||||
it("should handle paths without filename", () => {
|
||||
expect(getFilename("/home/user/")).toBe("");
|
||||
expect(getFilename("folder/")).toBe("");
|
||||
expect(getFilename("/")).toBe("");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,67 +0,0 @@
|
||||
/**
|
||||
* Normalize path separators to forward slashes for consistent handling across platforms
|
||||
*/
|
||||
export function normalizePath(path: string): string {
|
||||
// Replace all backslashes with forward slashes
|
||||
return path.replace(/\\/g, "/");
|
||||
}
|
||||
|
||||
/**
|
||||
* Join path segments with forward slashes
|
||||
*/
|
||||
export function joinPath(...segments: string[]): string {
|
||||
// Filter out empty segments
|
||||
const filtered = segments.filter((s) => s);
|
||||
// Join with forward slash and normalize
|
||||
return normalizePath(filtered.join("/"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent directory of a path
|
||||
*/
|
||||
export function getParentPath(path: string): string {
|
||||
let normalized = normalizePath(path);
|
||||
|
||||
// Remove trailing slash if present (unless it's the root)
|
||||
if (normalized.endsWith("/") && normalized.length > 1) {
|
||||
normalized = normalized.slice(0, -1);
|
||||
}
|
||||
|
||||
// Handle Windows drive roots (e.g., "C:" or "C:/")
|
||||
if (/^[A-Za-z]:$/.test(normalized)) {
|
||||
return normalized; // Drive root has no parent
|
||||
}
|
||||
|
||||
const lastSlash = normalized.lastIndexOf("/");
|
||||
if (lastSlash === -1) {
|
||||
// No slash found - could be just a filename or Windows drive letter
|
||||
if (/^[A-Za-z]:/.test(normalized)) {
|
||||
// It's a Windows path like "C:file.txt" - parent is "C:"
|
||||
return normalized.substring(0, 2);
|
||||
}
|
||||
return ".";
|
||||
}
|
||||
if (lastSlash === 0) {
|
||||
return "/";
|
||||
}
|
||||
|
||||
// Check if parent would be a Windows drive root
|
||||
const parent = normalized.substring(0, lastSlash);
|
||||
if (/^[A-Za-z]:$/.test(parent)) {
|
||||
return parent;
|
||||
}
|
||||
|
||||
return parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filename from a path
|
||||
*/
|
||||
export function getFilename(path: string): string {
|
||||
const normalized = normalizePath(path);
|
||||
const lastSlash = normalized.lastIndexOf("/");
|
||||
if (lastSlash === -1) {
|
||||
return normalized;
|
||||
}
|
||||
return normalized.substring(lastSlash + 1);
|
||||
}
|
||||
Reference in New Issue
Block a user