diff --git a/src-tauri/src/commands/agents.rs b/src-tauri/src/commands/agents.rs index 4ae855d..e7d4193 100644 --- a/src-tauri/src/commands/agents.rs +++ b/src-tauri/src/commands/agents.rs @@ -1771,6 +1771,57 @@ pub async fn stream_session_output( Ok(()) } +/// Export a single agent to JSON format +#[tauri::command] +pub async fn export_agent(db: State<'_, AgentDb>, id: i64) -> Result { + let conn = db.0.lock().map_err(|e| e.to_string())?; + + // Fetch the agent + let agent = conn + .query_row( + "SELECT name, icon, system_prompt, default_task, model, sandbox_enabled, enable_file_read, enable_file_write, enable_network FROM agents WHERE id = ?1", + params![id], + |row| { + Ok(serde_json::json!({ + "name": row.get::<_, String>(0)?, + "icon": row.get::<_, String>(1)?, + "system_prompt": row.get::<_, String>(2)?, + "default_task": row.get::<_, Option>(3)?, + "model": row.get::<_, String>(4)?, + "sandbox_enabled": row.get::<_, bool>(5)?, + "enable_file_read": row.get::<_, bool>(6)?, + "enable_file_write": row.get::<_, bool>(7)?, + "enable_network": row.get::<_, bool>(8)? + })) + }, + ) + .map_err(|e| format!("Failed to fetch agent: {}", e))?; + + // Create the export wrapper + let export_data = serde_json::json!({ + "version": 1, + "exported_at": chrono::Utc::now().to_rfc3339(), + "agent": agent + }); + + // Convert to pretty JSON string + serde_json::to_string_pretty(&export_data) + .map_err(|e| format!("Failed to serialize agent: {}", e)) +} + +/// Export agent to file with native dialog +#[tauri::command] +pub async fn export_agent_to_file(db: State<'_, AgentDb>, id: i64, file_path: String) -> Result<(), String> { + // Get the JSON data + let json_data = export_agent(db, id).await?; + + // Write to file + std::fs::write(&file_path, json_data) + .map_err(|e| format!("Failed to write file: {}", e))?; + + Ok(()) +} + /// Get the stored Claude binary path from settings #[tauri::command] pub async fn get_claude_binary_path(db: State<'_, AgentDb>) -> Result, String> { @@ -1853,4 +1904,3 @@ fn create_command_with_env(program: &str) -> Command { cmd } - \ No newline at end of file diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 6c61732..77ec716 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -26,7 +26,7 @@ use commands::agents::{ migrate_agent_runs_to_session_ids, list_running_sessions, kill_agent_session, get_session_status, cleanup_finished_processes, get_session_output, get_live_session_output, stream_session_output, get_claude_binary_path, - set_claude_binary_path, AgentDb + set_claude_binary_path, export_agent, export_agent_to_file, AgentDb }; use commands::sandbox::{ list_sandbox_profiles, create_sandbox_profile, update_sandbox_profile, delete_sandbox_profile, @@ -137,6 +137,8 @@ fn main() { update_agent, delete_agent, get_agent, + export_agent, + export_agent_to_file, execute_agent, list_agent_runs, get_agent_run, diff --git a/src/components/CCAgents.tsx b/src/components/CCAgents.tsx index 8204bd6..1c815c0 100644 --- a/src/components/CCAgents.tsx +++ b/src/components/CCAgents.tsx @@ -15,11 +15,14 @@ import { Shield, Terminal, ArrowLeft, - History + History, + Download } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardFooter } from "@/components/ui/card"; import { api, type Agent, type AgentRunWithMetrics } from "@/lib/api"; +import { save } from "@tauri-apps/plugin-dialog"; +import { invoke } from "@tauri-apps/api/core"; import { cn } from "@/lib/utils"; import { Toast, ToastContainer } from "@/components/ui/toast"; import { CreateAgent } from "./CreateAgent"; @@ -155,6 +158,35 @@ export const CCAgents: React.FC = ({ onBack, className }) => { await loadRuns(); }; + const handleExportAgent = async (agent: Agent) => { + try { + // Show native save dialog + const filePath = await save({ + defaultPath: `${agent.name.toLowerCase().replace(/\s+/g, '-')}.claudia.json`, + filters: [{ + name: 'Claudia Agent', + extensions: ['claudia.json'] + }] + }); + + if (!filePath) { + // User cancelled the dialog + return; + } + + // Export the agent to the selected file + await invoke('export_agent_to_file', { + id: agent.id!, + filePath + }); + + setToast({ message: `Agent "${agent.name}" exported successfully`, type: "success" }); + } catch (err) { + console.error("Failed to export agent:", err); + setToast({ message: "Failed to export agent", type: "error" }); + } + }; + // Pagination calculations const totalPages = Math.ceil(agents.length / AGENTS_PER_PAGE); const startIndex = (currentPage - 1) * AGENTS_PER_PAGE; @@ -342,12 +374,13 @@ export const CCAgents: React.FC = ({ onBack, className }) => { Created: {new Date(agent.created_at).toLocaleDateString()}

- + +