From bec8cea1e709188e74da55134fb4f61b1c027d73 Mon Sep 17 00:00:00 2001 From: Vivek R <123vivekr@gmail.com> Date: Mon, 23 Jun 2025 15:35:14 +0530 Subject: [PATCH] feat: add JSON import functionality for CC agents --- src-tauri/src/commands/agents.rs | 110 +++++++++++++++++++++++++++++++ src-tauri/src/main.rs | 5 +- src/components/CCAgents.tsx | 62 +++++++++++++---- src/lib/api.ts | 28 ++++++++ 4 files changed, 193 insertions(+), 12 deletions(-) diff --git a/src-tauri/src/commands/agents.rs b/src-tauri/src/commands/agents.rs index e7d4193..c434c5a 100644 --- a/src-tauri/src/commands/agents.rs +++ b/src-tauri/src/commands/agents.rs @@ -169,6 +169,28 @@ pub struct AgentRunWithMetrics { pub output: Option, // Real-time JSONL content } +/// Agent export format +#[derive(Debug, Serialize, Deserialize)] +pub struct AgentExport { + pub version: u32, + pub exported_at: String, + pub agent: AgentData, +} + +/// Agent data within export +#[derive(Debug, Serialize, Deserialize)] +pub struct AgentData { + pub name: String, + pub icon: String, + pub system_prompt: String, + pub default_task: Option, + pub model: String, + pub sandbox_enabled: bool, + pub enable_file_read: bool, + pub enable_file_write: bool, + pub enable_network: bool, +} + /// Database connection state pub struct AgentDb(pub Mutex); @@ -1904,3 +1926,91 @@ fn create_command_with_env(program: &str) -> Command { cmd } + +/// Import an agent from JSON data +#[tauri::command] +pub async fn import_agent(db: State<'_, AgentDb>, json_data: String) -> Result { + // Parse the JSON data + let export_data: AgentExport = serde_json::from_str(&json_data) + .map_err(|e| format!("Invalid JSON format: {}", e))?; + + // Validate version + if export_data.version != 1 { + return Err(format!("Unsupported export version: {}. This version of the app only supports version 1.", export_data.version)); + } + + let agent_data = export_data.agent; + let conn = db.0.lock().map_err(|e| e.to_string())?; + + // Check if an agent with the same name already exists + let existing_count: i64 = conn + .query_row( + "SELECT COUNT(*) FROM agents WHERE name = ?1", + params![agent_data.name], + |row| row.get(0), + ) + .map_err(|e| e.to_string())?; + + // If agent with same name exists, append a suffix + let final_name = if existing_count > 0 { + format!("{} (Imported)", agent_data.name) + } else { + agent_data.name + }; + + // Create the agent + conn.execute( + "INSERT INTO agents (name, icon, system_prompt, default_task, model, sandbox_enabled, enable_file_read, enable_file_write, enable_network) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)", + params![ + final_name, + agent_data.icon, + agent_data.system_prompt, + agent_data.default_task, + agent_data.model, + agent_data.sandbox_enabled, + agent_data.enable_file_read, + agent_data.enable_file_write, + agent_data.enable_network + ], + ) + .map_err(|e| format!("Failed to create agent: {}", e))?; + + let id = conn.last_insert_rowid(); + + // Fetch the created agent + let agent = conn + .query_row( + "SELECT id, name, icon, system_prompt, default_task, model, sandbox_enabled, enable_file_read, enable_file_write, enable_network, created_at, updated_at FROM agents WHERE id = ?1", + params![id], + |row| { + Ok(Agent { + id: Some(row.get(0)?), + name: row.get(1)?, + icon: row.get(2)?, + system_prompt: row.get(3)?, + default_task: row.get(4)?, + model: row.get(5)?, + sandbox_enabled: row.get(6)?, + enable_file_read: row.get(7)?, + enable_file_write: row.get(8)?, + enable_network: row.get(9)?, + created_at: row.get(10)?, + updated_at: row.get(11)?, + }) + }, + ) + .map_err(|e| format!("Failed to fetch created agent: {}", e))?; + + Ok(agent) +} + +/// Import agent from file +#[tauri::command] +pub async fn import_agent_from_file(db: State<'_, AgentDb>, file_path: String) -> Result { + // Read the file + let json_data = std::fs::read_to_string(&file_path) + .map_err(|e| format!("Failed to read file: {}", e))?; + + // Import the agent + import_agent(db, json_data).await +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 77ec716..94669ec 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -26,7 +26,8 @@ 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, export_agent, export_agent_to_file, AgentDb + set_claude_binary_path, export_agent, export_agent_to_file, import_agent, + import_agent_from_file, AgentDb }; use commands::sandbox::{ list_sandbox_profiles, create_sandbox_profile, update_sandbox_profile, delete_sandbox_profile, @@ -139,6 +140,8 @@ fn main() { get_agent, export_agent, export_agent_to_file, + import_agent, + import_agent_from_file, execute_agent, list_agent_runs, get_agent_run, diff --git a/src/components/CCAgents.tsx b/src/components/CCAgents.tsx index 1c815c0..4f926ac 100644 --- a/src/components/CCAgents.tsx +++ b/src/components/CCAgents.tsx @@ -16,12 +16,13 @@ import { Terminal, ArrowLeft, History, - Download + Download, + Upload } 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 { save, open } from "@tauri-apps/plugin-dialog"; import { invoke } from "@tauri-apps/api/core"; import { cn } from "@/lib/utils"; import { Toast, ToastContainer } from "@/components/ui/toast"; @@ -187,6 +188,34 @@ export const CCAgents: React.FC = ({ onBack, className }) => { } }; + const handleImportAgent = async () => { + try { + // Show native open dialog + const filePath = await open({ + multiple: false, + filters: [{ + name: 'Claudia Agent', + extensions: ['claudia.json', 'json'] + }] + }); + + if (!filePath) { + // User cancelled the dialog + return; + } + + // Import the agent from the selected file + await api.importAgentFromFile(filePath as string); + + setToast({ message: "Agent imported successfully", type: "success" }); + await loadAgents(); + } catch (err) { + console.error("Failed to import agent:", err); + const errorMessage = err instanceof Error ? err.message : "Failed to import agent"; + setToast({ message: errorMessage, type: "error" }); + } + }; + // Pagination calculations const totalPages = Math.ceil(agents.length / AGENTS_PER_PAGE); const startIndex = (currentPage - 1) * AGENTS_PER_PAGE; @@ -264,14 +293,25 @@ export const CCAgents: React.FC = ({ onBack, className }) => {

- +
+ + +
@@ -402,7 +442,7 @@ export const CCAgents: React.FC = ({ onBack, className }) => { className="flex items-center gap-1" title="Export agent to .claudia.json" > - + Export