feat: export agents to .claudia.json file
This commit is contained in:
@@ -1771,6 +1771,57 @@ pub async fn stream_session_output(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Export a single agent to JSON format
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn export_agent(db: State<'_, AgentDb>, id: i64) -> Result<String, String> {
|
||||||
|
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<String>>(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
|
/// Get the stored Claude binary path from settings
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn get_claude_binary_path(db: State<'_, AgentDb>) -> Result<Option<String>, String> {
|
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 {
|
|||||||
|
|
||||||
cmd
|
cmd
|
||||||
}
|
}
|
||||||
|
|
@@ -26,7 +26,7 @@ use commands::agents::{
|
|||||||
migrate_agent_runs_to_session_ids, list_running_sessions, kill_agent_session,
|
migrate_agent_runs_to_session_ids, list_running_sessions, kill_agent_session,
|
||||||
get_session_status, cleanup_finished_processes, get_session_output,
|
get_session_status, cleanup_finished_processes, get_session_output,
|
||||||
get_live_session_output, stream_session_output, get_claude_binary_path,
|
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::{
|
use commands::sandbox::{
|
||||||
list_sandbox_profiles, create_sandbox_profile, update_sandbox_profile, delete_sandbox_profile,
|
list_sandbox_profiles, create_sandbox_profile, update_sandbox_profile, delete_sandbox_profile,
|
||||||
@@ -137,6 +137,8 @@ fn main() {
|
|||||||
update_agent,
|
update_agent,
|
||||||
delete_agent,
|
delete_agent,
|
||||||
get_agent,
|
get_agent,
|
||||||
|
export_agent,
|
||||||
|
export_agent_to_file,
|
||||||
execute_agent,
|
execute_agent,
|
||||||
list_agent_runs,
|
list_agent_runs,
|
||||||
get_agent_run,
|
get_agent_run,
|
||||||
|
@@ -15,11 +15,14 @@ import {
|
|||||||
Shield,
|
Shield,
|
||||||
Terminal,
|
Terminal,
|
||||||
ArrowLeft,
|
ArrowLeft,
|
||||||
History
|
History,
|
||||||
|
Download
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardFooter } from "@/components/ui/card";
|
import { Card, CardContent, CardFooter } from "@/components/ui/card";
|
||||||
import { api, type Agent, type AgentRunWithMetrics } from "@/lib/api";
|
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 { cn } from "@/lib/utils";
|
||||||
import { Toast, ToastContainer } from "@/components/ui/toast";
|
import { Toast, ToastContainer } from "@/components/ui/toast";
|
||||||
import { CreateAgent } from "./CreateAgent";
|
import { CreateAgent } from "./CreateAgent";
|
||||||
@@ -155,6 +158,35 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
|||||||
await loadRuns();
|
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
|
// Pagination calculations
|
||||||
const totalPages = Math.ceil(agents.length / AGENTS_PER_PAGE);
|
const totalPages = Math.ceil(agents.length / AGENTS_PER_PAGE);
|
||||||
const startIndex = (currentPage - 1) * AGENTS_PER_PAGE;
|
const startIndex = (currentPage - 1) * AGENTS_PER_PAGE;
|
||||||
@@ -342,12 +374,13 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
|||||||
Created: {new Date(agent.created_at).toLocaleDateString()}
|
Created: {new Date(agent.created_at).toLocaleDateString()}
|
||||||
</p>
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardFooter className="p-4 pt-0 flex justify-center gap-2">
|
<CardFooter className="p-4 pt-0 flex justify-center gap-1 flex-wrap">
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => handleExecuteAgent(agent)}
|
onClick={() => handleExecuteAgent(agent)}
|
||||||
className="flex items-center gap-1"
|
className="flex items-center gap-1"
|
||||||
|
title="Execute agent"
|
||||||
>
|
>
|
||||||
<Play className="h-3 w-3" />
|
<Play className="h-3 w-3" />
|
||||||
Execute
|
Execute
|
||||||
@@ -357,15 +390,27 @@ export const CCAgents: React.FC<CCAgentsProps> = ({ onBack, className }) => {
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => handleEditAgent(agent)}
|
onClick={() => handleEditAgent(agent)}
|
||||||
className="flex items-center gap-1"
|
className="flex items-center gap-1"
|
||||||
|
title="Edit agent"
|
||||||
>
|
>
|
||||||
<Edit className="h-3 w-3" />
|
<Edit className="h-3 w-3" />
|
||||||
Edit
|
Edit
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => handleExportAgent(agent)}
|
||||||
|
className="flex items-center gap-1"
|
||||||
|
title="Export agent to .claudia.json"
|
||||||
|
>
|
||||||
|
<Download className="h-3 w-3" />
|
||||||
|
Export
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => handleDeleteAgent(agent.id!)}
|
onClick={() => handleDeleteAgent(agent.id!)}
|
||||||
className="flex items-center gap-1 text-destructive hover:text-destructive"
|
className="flex items-center gap-1 text-destructive hover:text-destructive"
|
||||||
|
title="Delete agent"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-3 w-3" />
|
<Trash2 className="h-3 w-3" />
|
||||||
Delete
|
Delete
|
||||||
|
@@ -733,6 +733,20 @@ export const api = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exports a single agent to JSON format
|
||||||
|
* @param id - The agent ID to export
|
||||||
|
* @returns Promise resolving to the JSON string
|
||||||
|
*/
|
||||||
|
async exportAgent(id: number): Promise<string> {
|
||||||
|
try {
|
||||||
|
return await invoke<string>('export_agent', { id });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to export agent:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes an agent
|
* Executes an agent
|
||||||
* @param agentId - The agent ID to execute
|
* @param agentId - The agent ID to execute
|
||||||
|
Reference in New Issue
Block a user