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