generated from nhcarrigan/template
feat: add /cd command to change working directory
This commit is contained in:
@@ -105,6 +105,51 @@ pub async fn get_usage_stats(
|
|||||||
manager.get_usage_stats(&conversation_id)
|
manager.get_usage_stats(&conversation_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn validate_directory(path: String, current_dir: Option<String>) -> Result<String, String> {
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
let path = Path::new(&path);
|
||||||
|
|
||||||
|
// Expand ~ to home directory
|
||||||
|
let expanded_path = if path.starts_with("~") {
|
||||||
|
if let Some(home) = std::env::var_os("HOME") {
|
||||||
|
let home_path = Path::new(&home);
|
||||||
|
if path == Path::new("~") {
|
||||||
|
home_path.to_path_buf()
|
||||||
|
} else {
|
||||||
|
home_path.join(path.strip_prefix("~").unwrap())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err("Could not determine home directory".to_string());
|
||||||
|
}
|
||||||
|
} else if path.is_relative() {
|
||||||
|
// Handle relative paths (., .., or any relative path) by resolving against current_dir
|
||||||
|
if let Some(ref cwd) = current_dir {
|
||||||
|
Path::new(cwd).join(path)
|
||||||
|
} else {
|
||||||
|
path.to_path_buf()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
path.to_path_buf()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if the path exists and is a directory
|
||||||
|
if !expanded_path.exists() {
|
||||||
|
return Err(format!("Directory does not exist: {}", expanded_path.display()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !expanded_path.is_dir() {
|
||||||
|
return Err(format!("Path is not a directory: {}", expanded_path.display()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the canonicalized (absolute) path
|
||||||
|
expanded_path
|
||||||
|
.canonicalize()
|
||||||
|
.map(|p| p.to_string_lossy().to_string())
|
||||||
|
.map_err(|e| format!("Failed to resolve path: {}", e))
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn load_saved_achievements(app: AppHandle) -> Result<Vec<AchievementUnlockedEvent>, String> {
|
pub async fn load_saved_achievements(app: AppHandle) -> Result<Vec<AchievementUnlockedEvent>, String> {
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ pub struct ClaudeStartOptions {
|
|||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub skip_greeting: bool,
|
pub skip_greeting: bool,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
pub resume_session_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ pub fn run() {
|
|||||||
send_notify_send,
|
send_notify_send,
|
||||||
send_wsl_notification,
|
send_wsl_notification,
|
||||||
send_vbs_notification,
|
send_vbs_notification,
|
||||||
|
validate_directory,
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
|||||||
@@ -195,6 +195,13 @@ impl WslBridge {
|
|||||||
cmd.args(["--mcp-config", mcp_path]);
|
cmd.args(["--mcp-config", mcp_path]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add resume flag if session ID provided
|
||||||
|
if let Some(ref session_id) = options.resume_session_id {
|
||||||
|
if !session_id.is_empty() {
|
||||||
|
cmd.args(["--resume", session_id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cmd.current_dir(working_dir);
|
cmd.current_dir(working_dir);
|
||||||
|
|
||||||
// Set API key as environment variable if specified
|
// Set API key as environment variable if specified
|
||||||
@@ -251,6 +258,13 @@ impl WslBridge {
|
|||||||
claude_cmd.push_str(&format!(" --mcp-config '{}'", mcp_path));
|
claude_cmd.push_str(&format!(" --mcp-config '{}'", mcp_path));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add resume flag if session ID provided
|
||||||
|
if let Some(ref session_id) = options.resume_session_id {
|
||||||
|
if !session_id.is_empty() {
|
||||||
|
claude_cmd.push_str(&format!(" --resume '{}'", session_id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Use bash -lc to load login profile (ensures PATH includes claude)
|
// Use bash -lc to load login profile (ensures PATH includes claude)
|
||||||
cmd.args(["-e", "bash", "-lc", &claude_cmd]);
|
cmd.args(["-e", "bash", "-lc", &claude_cmd]);
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,71 @@ export interface SlashCommand {
|
|||||||
execute: (args: string) => Promise<void> | void;
|
execute: (args: string) => Promise<void> | void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function changeDirectory(path: string): Promise<void> {
|
||||||
|
const conversationId = get(claudeStore.activeConversationId);
|
||||||
|
if (!conversationId) {
|
||||||
|
claudeStore.addLine("error", "No active conversation");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!path.trim()) {
|
||||||
|
const currentDir = get(claudeStore.currentWorkingDirectory);
|
||||||
|
claudeStore.addLine("system", `Current directory: ${currentDir}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
characterState.setState("thinking");
|
||||||
|
claudeStore.addLine("system", `Changing directory to: ${path}`);
|
||||||
|
|
||||||
|
const currentDir = get(claudeStore.currentWorkingDirectory);
|
||||||
|
const validatedPath = await invoke<string>("validate_directory", { path, currentDir });
|
||||||
|
|
||||||
|
// Capture conversation history before disconnecting
|
||||||
|
const conversationHistory = claudeStore.getConversationHistory();
|
||||||
|
|
||||||
|
await invoke("stop_claude", { conversationId });
|
||||||
|
|
||||||
|
// Wait for clean shutdown
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||||
|
|
||||||
|
claudeStore.setWorkingDirectory(validatedPath);
|
||||||
|
|
||||||
|
setSkipNextGreeting(true);
|
||||||
|
|
||||||
|
await invoke("start_claude", {
|
||||||
|
conversationId,
|
||||||
|
options: {
|
||||||
|
working_dir: validatedPath,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for connection to establish
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
|
||||||
|
// Restore context if there was conversation history
|
||||||
|
if (conversationHistory) {
|
||||||
|
const contextMessage = `[CONTEXT RESTORATION]
|
||||||
|
I just changed the working directory from ${currentDir} to ${validatedPath}. Here's our conversation so far:
|
||||||
|
|
||||||
|
${conversationHistory}
|
||||||
|
|
||||||
|
Please continue where we left off. You are now operating in the new directory.`;
|
||||||
|
|
||||||
|
await invoke("send_prompt", {
|
||||||
|
conversationId,
|
||||||
|
message: contextMessage,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
claudeStore.addLine("system", `Changed directory to: ${validatedPath}`);
|
||||||
|
characterState.setState("idle");
|
||||||
|
} catch (error) {
|
||||||
|
claudeStore.addLine("error", `Failed to change directory: ${error}`);
|
||||||
|
characterState.setTemporaryState("error", 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function startNewConversation(): Promise<void> {
|
async function startNewConversation(): Promise<void> {
|
||||||
const conversationId = get(claudeStore.activeConversationId);
|
const conversationId = get(claudeStore.activeConversationId);
|
||||||
if (!conversationId) {
|
if (!conversationId) {
|
||||||
@@ -48,6 +113,12 @@ async function startNewConversation(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const slashCommands: SlashCommand[] = [
|
export const slashCommands: SlashCommand[] = [
|
||||||
|
{
|
||||||
|
name: "cd",
|
||||||
|
description: "Change the working directory",
|
||||||
|
usage: "/cd <path>",
|
||||||
|
execute: changeDirectory,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "clear",
|
name: "clear",
|
||||||
description: "Clear the terminal display (keeps conversation context)",
|
description: "Clear the terminal display (keeps conversation context)",
|
||||||
|
|||||||
Reference in New Issue
Block a user