Skip to content

Commit ad03503

Browse files
committed
feat: export agents to .claudia.json file
1 parent ccac68a commit ad03503

4 files changed

Lines changed: 117 additions & 6 deletions

File tree

src-tauri/src/commands/agents.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1771,6 +1771,57 @@ pub async fn stream_session_output(
17711771
Ok(())
17721772
}
17731773

1774+
/// Export a single agent to JSON format
1775+
#[tauri::command]
1776+
pub async fn export_agent(db: State<'_, AgentDb>, id: i64) -> Result<String, String> {
1777+
let conn = db.0.lock().map_err(|e| e.to_string())?;
1778+
1779+
// Fetch the agent
1780+
let agent = conn
1781+
.query_row(
1782+
"SELECT name, icon, system_prompt, default_task, model, sandbox_enabled, enable_file_read, enable_file_write, enable_network FROM agents WHERE id = ?1",
1783+
params![id],
1784+
|row| {
1785+
Ok(serde_json::json!({
1786+
"name": row.get::<_, String>(0)?,
1787+
"icon": row.get::<_, String>(1)?,
1788+
"system_prompt": row.get::<_, String>(2)?,
1789+
"default_task": row.get::<_, Option<String>>(3)?,
1790+
"model": row.get::<_, String>(4)?,
1791+
"sandbox_enabled": row.get::<_, bool>(5)?,
1792+
"enable_file_read": row.get::<_, bool>(6)?,
1793+
"enable_file_write": row.get::<_, bool>(7)?,
1794+
"enable_network": row.get::<_, bool>(8)?
1795+
}))
1796+
},
1797+
)
1798+
.map_err(|e| format!("Failed to fetch agent: {}", e))?;
1799+
1800+
// Create the export wrapper
1801+
let export_data = serde_json::json!({
1802+
"version": 1,
1803+
"exported_at": chrono::Utc::now().to_rfc3339(),
1804+
"agent": agent
1805+
});
1806+
1807+
// Convert to pretty JSON string
1808+
serde_json::to_string_pretty(&export_data)
1809+
.map_err(|e| format!("Failed to serialize agent: {}", e))
1810+
}
1811+
1812+
/// Export agent to file with native dialog
1813+
#[tauri::command]
1814+
pub async fn export_agent_to_file(db: State<'_, AgentDb>, id: i64, file_path: String) -> Result<(), String> {
1815+
// Get the JSON data
1816+
let json_data = export_agent(db, id).await?;
1817+
1818+
// Write to file
1819+
std::fs::write(&file_path, json_data)
1820+
.map_err(|e| format!("Failed to write file: {}", e))?;
1821+
1822+
Ok(())
1823+
}
1824+
17741825
/// Get the stored Claude binary path from settings
17751826
#[tauri::command]
17761827
pub async fn get_claude_binary_path(db: State<'_, AgentDb>) -> Result<Option<String>, String> {
@@ -1853,4 +1904,3 @@ fn create_command_with_env(program: &str) -> Command {
18531904

18541905
cmd
18551906
}
1856-

src-tauri/src/main.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use commands::agents::{
2626
migrate_agent_runs_to_session_ids, list_running_sessions, kill_agent_session,
2727
get_session_status, cleanup_finished_processes, get_session_output,
2828
get_live_session_output, stream_session_output, get_claude_binary_path,
29-
set_claude_binary_path, AgentDb
29+
set_claude_binary_path, export_agent, export_agent_to_file, AgentDb
3030
};
3131
use commands::sandbox::{
3232
list_sandbox_profiles, create_sandbox_profile, update_sandbox_profile, delete_sandbox_profile,
@@ -137,6 +137,8 @@ fn main() {
137137
update_agent,
138138
delete_agent,
139139
get_agent,
140+
export_agent,
141+
export_agent_to_file,
140142
execute_agent,
141143
list_agent_runs,
142144
get_agent_run,

src/components/CCAgents.tsx

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,14 @@ import {
1515
Shield,
1616
Terminal,
1717
ArrowLeft,
18-
History
18+
History,
19+
Download
1920
} from "lucide-react";
2021
import { Button } from "@/components/ui/button";
2122
import { Card, CardContent, CardFooter } from "@/components/ui/card";
2223
import { api, type Agent, type AgentRunWithMetrics } from "@/lib/api";
24+
import { save } from "@tauri-apps/plugin-dialog";
25+
import { invoke } from "@tauri-apps/api/core";
2326
import { cn } from "@/lib/utils";
2427
import { Toast, ToastContainer } from "@/components/ui/toast";
2528
import { CreateAgent } from "./CreateAgent";
@@ -155,6 +158,35 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
155158
await loadRuns();
156159
};
157160

161+
const handleExportAgent = async (agent: Agent) => {
162+
try {
163+
// Show native save dialog
164+
const filePath = await save({
165+
defaultPath: `${agent.name.toLowerCase().replace(/\s+/g, '-')}.claudia.json`,
166+
filters: [{
167+
name: 'Claudia Agent',
168+
extensions: ['claudia.json']
169+
}]
170+
});
171+
172+
if (!filePath) {
173+
// User cancelled the dialog
174+
return;
175+
}
176+
177+
// Export the agent to the selected file
178+
await invoke('export_agent_to_file', {
179+
id: agent.id!,
180+
filePath
181+
});
182+
183+
setToast({ message: `Agent "${agent.name}" exported successfully`, type: "success" });
184+
} catch (err) {
185+
console.error("Failed to export agent:", err);
186+
setToast({ message: "Failed to export agent", type: "error" });
187+
}
188+
};
189+
158190
// Pagination calculations
159191
const totalPages = Math.ceil(agents.length / AGENTS_PER_PAGE);
160192
const startIndex = (currentPage - 1) * AGENTS_PER_PAGE;
@@ -342,12 +374,13 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
342374
Created: {new Date(agent.created_at).toLocaleDateString()}
343375
</p>
344376
</CardContent>
345-
<CardFooter className="p-4 pt-0 flex justify-center gap-2">
377+
<CardFooter className="p-4 pt-0 flex justify-center gap-1 flex-wrap">
346378
<Button
347379
size="sm"
348380
variant="ghost"
349381
onClick={() => handleExecuteAgent(agent)}
350382
className="flex items-center gap-1"
383+
title="Execute agent"
351384
>
352385
<Play className="h-3 w-3" />
353386
Execute
@@ -357,15 +390,27 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
357390
variant="ghost"
358391
onClick={() => handleEditAgent(agent)}
359392
className="flex items-center gap-1"
393+
title="Edit agent"
360394
>
361395
<Edit className="h-3 w-3" />
362396
Edit
363397
</Button>
398+
<Button
399+
size="sm"
400+
variant="ghost"
401+
onClick={() => handleExportAgent(agent)}
402+
className="flex items-center gap-1"
403+
title="Export agent to .claudia.json"
404+
>
405+
<Download className="h-3 w-3" />
406+
Export
407+
</Button>
364408
<Button
365409
size="sm"
366410
variant="ghost"
367411
onClick={() => handleDeleteAgent(agent.id!)}
368412
className="flex items-center gap-1 text-destructive hover:text-destructive"
413+
title="Delete agent"
369414
>
370415
<Trash2 className="h-3 w-3" />
371416
Delete
@@ -452,4 +497,4 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
452497
</ToastContainer>
453498
</div>
454499
);
455-
};
500+
};

src/lib/api.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,20 @@ export const api = {
733733
}
734734
},
735735

736+
/**
737+
* Exports a single agent to JSON format
738+
* @param id - The agent ID to export
739+
* @returns Promise resolving to the JSON string
740+
*/
741+
async exportAgent(id: number): Promise<string> {
742+
try {
743+
return await invoke<string>('export_agent', { id });
744+
} catch (error) {
745+
console.error("Failed to export agent:", error);
746+
throw error;
747+
}
748+
},
749+
736750
/**
737751
* Executes an agent
738752
* @param agentId - The agent ID to execute
@@ -1800,4 +1814,4 @@ export const api = {
18001814
throw error;
18011815
}
18021816
},
1803-
};
1817+
};

0 commit comments

Comments
 (0)