mirror of
https://github.com/mfakbar127/Claude-Samurai
synced 2026-06-21 13:57:42 +00:00
sub agent
This commit is contained in:
+23
-19
@@ -1,21 +1,25 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(pnpm tsc:*)",
|
||||
"mcp__exa__get_code_context_exa",
|
||||
"mcp__context7__resolve-library-id",
|
||||
"mcp__context7__get-library-docs",
|
||||
"mcp__exa__web_search_exa",
|
||||
"Bash(pnpm dlx shadcn@latest add native-select)",
|
||||
"mcp__shadcn__get_project_registries",
|
||||
"mcp__shadcn__search_items_in_registries",
|
||||
"mcp__shadcn__get_add_command_for_items",
|
||||
"Bash(pnpm dlx shadcn@latest add:*)",
|
||||
"Bash(pnpm exec biome check:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
},
|
||||
"enableAllProjectMcpServers": true,
|
||||
"enabledMcpjsonServers": ["shadcn"]
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(pnpm tsc:*)",
|
||||
"mcp__exa__get_code_context_exa",
|
||||
"mcp__context7__resolve-library-id",
|
||||
"mcp__context7__get-library-docs",
|
||||
"mcp__exa__web_search_exa",
|
||||
"Bash(pnpm dlx shadcn@latest add native-select)",
|
||||
"mcp__shadcn__get_project_registries",
|
||||
"mcp__shadcn__search_items_in_registries",
|
||||
"mcp__shadcn__get_add_command_for_items",
|
||||
"Bash(pnpm dlx shadcn@latest add:*)",
|
||||
"Bash(pnpm exec biome check:*)",
|
||||
"Bash(pnpm install:*)",
|
||||
"Bash(pnpm search:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
},
|
||||
"enableAllProjectMcpServers": true,
|
||||
"enabledMcpjsonServers": [
|
||||
"shadcn"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
name: translate skill
|
||||
description: Use this skill when you are doing localization and translation work.
|
||||
---
|
||||
|
||||
## Rules
|
||||
|
||||
- Only localize the existing locales. Do not create new locales.
|
||||
- Do not translate the following terms:
|
||||
- Agent
|
||||
- Agents
|
||||
- Sub Agent
|
||||
- Sub Agents
|
||||
@@ -22,3 +22,5 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
target/
|
||||
@@ -0,0 +1 @@
|
||||
- Support manage sub agents
|
||||
+3
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "cc-mate",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -13,6 +13,7 @@
|
||||
"@codemirror/fold": "^0.19.4",
|
||||
"@codemirror/lang-json": "^6.0.2",
|
||||
"@codemirror/lang-markdown": "^6.4.0",
|
||||
"@codemirror/lang-yaml": "^6.1.2",
|
||||
"@codemirror/language": "^6.11.3",
|
||||
"@codemirror/language-data": "^6.5.1",
|
||||
"@hookform/resolvers": "^5.2.2",
|
||||
@@ -63,6 +64,7 @@
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwind-variants": "^3.1.1",
|
||||
"ts-pattern": "^5.8.0",
|
||||
"vaul": "^1.1.2",
|
||||
"zod": "^4.1.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
Generated
+21
@@ -17,6 +17,9 @@ importers:
|
||||
'@codemirror/lang-markdown':
|
||||
specifier: ^6.4.0
|
||||
version: 6.4.0
|
||||
'@codemirror/lang-yaml':
|
||||
specifier: ^6.1.2
|
||||
version: 6.1.2
|
||||
'@codemirror/language':
|
||||
specifier: ^6.11.3
|
||||
version: 6.11.3
|
||||
@@ -167,6 +170,9 @@ importers:
|
||||
ts-pattern:
|
||||
specifier: ^5.8.0
|
||||
version: 5.8.0
|
||||
vaul:
|
||||
specifier: ^1.1.2
|
||||
version: 1.1.2(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
zod:
|
||||
specifier: ^4.1.11
|
||||
version: 4.1.11
|
||||
@@ -5228,6 +5234,12 @@ packages:
|
||||
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
vaul@1.1.2:
|
||||
resolution: {integrity: sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==}
|
||||
peerDependencies:
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc
|
||||
|
||||
vfile-location@5.0.3:
|
||||
resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==}
|
||||
|
||||
@@ -11534,6 +11546,15 @@ snapshots:
|
||||
|
||||
vary@1.1.2: {}
|
||||
|
||||
vaul@1.1.2(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
|
||||
dependencies:
|
||||
'@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
|
||||
react: 19.2.0
|
||||
react-dom: 19.2.0(react@19.2.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
- '@types/react-dom'
|
||||
|
||||
vfile-location@5.0.3:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
|
||||
@@ -1985,3 +1985,84 @@ pub async fn delete_claude_command(command_name: String) -> Result<(), String> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Agent management functions
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
|
||||
pub struct AgentFile {
|
||||
pub name: String,
|
||||
pub content: String,
|
||||
pub exists: bool,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn read_claude_agents() -> Result<Vec<AgentFile>, String> {
|
||||
let home_dir = dirs::home_dir().ok_or("Could not find home directory")?;
|
||||
let agents_dir = home_dir.join(".claude/agents");
|
||||
|
||||
if !agents_dir.exists() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
let mut agent_files = Vec::new();
|
||||
|
||||
// Read all .md files in the agents directory
|
||||
let entries = std::fs::read_dir(&agents_dir)
|
||||
.map_err(|e| format!("Failed to read agents directory: {}", e))?;
|
||||
|
||||
for entry in entries {
|
||||
let entry = entry.map_err(|e| format!("Failed to read directory entry: {}", e))?;
|
||||
let path = entry.path();
|
||||
|
||||
if path.is_file() && path.extension().map(|ext| ext == "md").unwrap_or(false) {
|
||||
let file_name = path.file_stem()
|
||||
.and_then(|name| name.to_str())
|
||||
.unwrap_or("unknown")
|
||||
.to_string();
|
||||
|
||||
let content = std::fs::read_to_string(&path)
|
||||
.map_err(|e| format!("Failed to read agent file {}: {}", path.display(), e))?;
|
||||
|
||||
agent_files.push(AgentFile {
|
||||
name: file_name,
|
||||
content,
|
||||
exists: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Sort agents alphabetically by name
|
||||
agent_files.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
|
||||
Ok(agent_files)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn write_claude_agent(agent_name: String, content: String) -> Result<(), String> {
|
||||
let home_dir = dirs::home_dir().ok_or("Could not find home directory")?;
|
||||
let agents_dir = home_dir.join(".claude/agents");
|
||||
let agent_file_path = agents_dir.join(format!("{}.md", agent_name));
|
||||
|
||||
// Ensure .claude/agents directory exists
|
||||
std::fs::create_dir_all(&agents_dir)
|
||||
.map_err(|e| format!("Failed to create .claude/agents directory: {}", e))?;
|
||||
|
||||
std::fs::write(&agent_file_path, content)
|
||||
.map_err(|e| format!("Failed to write agent file: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn delete_claude_agent(agent_name: String) -> Result<(), String> {
|
||||
let home_dir = dirs::home_dir().ok_or("Could not find home directory")?;
|
||||
let agents_dir = home_dir.join(".claude/agents");
|
||||
let agent_file_path = agents_dir.join(format!("{}.md", agent_name));
|
||||
|
||||
if agent_file_path.exists() {
|
||||
std::fs::remove_file(&agent_file_path)
|
||||
.map_err(|e| format!("Failed to delete agent file: {}", e))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -202,7 +202,10 @@ pub fn run() {
|
||||
remove_claude_code_hook,
|
||||
read_claude_commands,
|
||||
write_claude_command,
|
||||
delete_claude_command
|
||||
delete_claude_command,
|
||||
read_claude_agents,
|
||||
write_claude_agent,
|
||||
delete_claude_agent
|
||||
])
|
||||
.on_window_event(|window, event| {
|
||||
#[cfg(target_os = "macos")]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "CC Mate",
|
||||
"version": "0.2.9",
|
||||
"version": "0.3.0",
|
||||
"identifier": "org.randynamic.ccconfig",
|
||||
"build": {
|
||||
"beforeDevCommand": "pnpm dev",
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import {
|
||||
ActivityIcon,
|
||||
BellIcon,
|
||||
BotIcon,
|
||||
BrainIcon,
|
||||
CpuIcon,
|
||||
FileJsonIcon,
|
||||
FolderIcon,
|
||||
SettingsIcon,
|
||||
TerminalIcon,
|
||||
UserIcon,
|
||||
} from "lucide-react";
|
||||
import type React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -36,6 +38,11 @@ export function Layout() {
|
||||
icon: CpuIcon,
|
||||
label: t("navigation.mcp"),
|
||||
},
|
||||
{
|
||||
to: "/agents",
|
||||
icon: BotIcon,
|
||||
label: "Agents",
|
||||
},
|
||||
{
|
||||
to: "/memory",
|
||||
icon: BrainIcon,
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import * as React from "react"
|
||||
import { Drawer as DrawerPrimitive } from "vaul"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Drawer = ({
|
||||
shouldScaleBackground = true,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
|
||||
<DrawerPrimitive.Root
|
||||
shouldScaleBackground={shouldScaleBackground}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
Drawer.displayName = "Drawer"
|
||||
|
||||
const DrawerTrigger = DrawerPrimitive.Trigger
|
||||
|
||||
const DrawerPortal = DrawerPrimitive.Portal
|
||||
|
||||
const DrawerClose = DrawerPrimitive.Close
|
||||
|
||||
const DrawerOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof DrawerPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DrawerPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn("fixed inset-0 z-50 bg-black/80", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
|
||||
|
||||
const DrawerContent = React.forwardRef<
|
||||
React.ElementRef<typeof DrawerPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DrawerPortal>
|
||||
<DrawerOverlay />
|
||||
<DrawerPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
|
||||
{children}
|
||||
</DrawerPrimitive.Content>
|
||||
</DrawerPortal>
|
||||
))
|
||||
DrawerContent.displayName = "DrawerContent"
|
||||
|
||||
const DrawerHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
DrawerHeader.displayName = "DrawerHeader"
|
||||
|
||||
const DrawerFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
DrawerFooter.displayName = "DrawerFooter"
|
||||
|
||||
const DrawerTitle = React.forwardRef<
|
||||
React.ElementRef<typeof DrawerPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DrawerPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"text-lg font-semibold leading-none tracking-tight",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DrawerTitle.displayName = DrawerPrimitive.Title.displayName
|
||||
|
||||
const DrawerDescription = React.forwardRef<
|
||||
React.ElementRef<typeof DrawerPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DrawerPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DrawerDescription.displayName = DrawerPrimitive.Description.displayName
|
||||
|
||||
export {
|
||||
Drawer,
|
||||
DrawerPortal,
|
||||
DrawerOverlay,
|
||||
DrawerTrigger,
|
||||
DrawerClose,
|
||||
DrawerContent,
|
||||
DrawerHeader,
|
||||
DrawerFooter,
|
||||
DrawerTitle,
|
||||
DrawerDescription,
|
||||
}
|
||||
@@ -147,6 +147,28 @@
|
||||
"toast.commandSaveFailed": "Failed to save command: {{error}}",
|
||||
"toast.commandDeleted": "Command deleted successfully",
|
||||
"toast.commandDeleteFailed": "Failed to delete command: {{error}}",
|
||||
"agents.title": "Agents",
|
||||
"agents.description": "Manage your Claude Code sub agents",
|
||||
"agents.noAgents": "No sub agents found. Click \"Add Agent\" to create one.",
|
||||
"agents.error": "Error loading agents: {{error}}",
|
||||
"agents.addAgent": "Add Agent",
|
||||
"agents.addAgentTitle": "Add Agent",
|
||||
"agents.addAgentDescription": "Create a new sub agent for Claude Code",
|
||||
"agents.agentName": "Agent Name",
|
||||
"agents.agentNamePlaceholder": "e.g., code-reviewer",
|
||||
"agents.agentContent": "Agent Content",
|
||||
"agents.contentPlaceholder": "Enter your agent instructions...",
|
||||
"agents.save": "Save",
|
||||
"agents.saving": "Saving...",
|
||||
"agents.create": "Create",
|
||||
"agents.creating": "Creating...",
|
||||
"agents.deleteConfirm": "Are you sure you want to delete the agent \"{{agentName}}\"? This action cannot be undone.",
|
||||
"agents.deleteTitle": "Delete Agent",
|
||||
"agents.emptyNameError": "Agent name cannot be empty",
|
||||
"agents.emptyContentError": "Agent content cannot be empty",
|
||||
"agents.validationError": "Validation Error",
|
||||
"agents.agentExistsError": "Agent \"{{agentName}}\" already exists",
|
||||
"agents.agentExistsTitle": "Agent Exists",
|
||||
"projects.title": "Projects",
|
||||
"projects.detail.editor": "Project Configuration Editor",
|
||||
"projects.detail.projectEditor": "Project Editor",
|
||||
|
||||
@@ -147,6 +147,28 @@
|
||||
"toast.commandSaveFailed": "Échec de la sauvegarde de la commande : {{error}}",
|
||||
"toast.commandDeleted": "Commande supprimée avec succès",
|
||||
"toast.commandDeleteFailed": "Échec de la suppression de la commande : {{error}}",
|
||||
"agents.title": "Agent",
|
||||
"agents.description": "Gérer vos sub agent Claude Code",
|
||||
"agents.noAgents": "Aucun sub agent trouvé. Cliquez sur \"Add Agent\" pour en créer un.",
|
||||
"agents.error": "Erreur lors du chargement des agents : {{error}}",
|
||||
"agents.addAgent": "Add Agent",
|
||||
"agents.addAgentTitle": "Add Agent",
|
||||
"agents.addAgentDescription": "Créer un nouveau sub agent pour Claude Code",
|
||||
"agents.agentName": "Agent nom",
|
||||
"agents.agentNamePlaceholder": "ex: code-reviewer",
|
||||
"agents.agentContent": "Agent contenu",
|
||||
"agents.contentPlaceholder": "Entrez vos instructions d'agent...",
|
||||
"agents.save": "Sauvegarder",
|
||||
"agents.saving": "Sauvegarde...",
|
||||
"agents.create": "Créer",
|
||||
"agents.creating": "Création...",
|
||||
"agents.deleteConfirm": "Êtes-vous sûr de vouloir supprimer l'agent \"{{agentName}}\" ? Cette action ne peut pas être annulée.",
|
||||
"agents.deleteTitle": "Supprimer Agent",
|
||||
"agents.emptyNameError": "Agent nom ne peut pas être vide",
|
||||
"agents.emptyContentError": "Agent contenu ne peut pas être vide",
|
||||
"agents.validationError": "Erreur de validation",
|
||||
"agents.agentExistsError": "Agent \"{{agentName}}\" existe déjà",
|
||||
"agents.agentExistsTitle": "Agent existe déjà",
|
||||
"projects.title": "Projets",
|
||||
"projects.detail.editor": "Éditeur de configuration de projet",
|
||||
"projects.detail.projectEditor": "Éditeur de projet",
|
||||
|
||||
@@ -147,6 +147,28 @@
|
||||
"toast.commandSaveFailed": "コマンドの保存に失敗しました:{{error}}",
|
||||
"toast.commandDeleted": "コマンドが正常に削除されました",
|
||||
"toast.commandDeleteFailed": "コマンドの削除に失敗しました:{{error}}",
|
||||
"agents.title": "Agent",
|
||||
"agents.description": "Claude Code sub agent を管理",
|
||||
"agents.noAgents": "sub agent が見つかりません。「Add Agent」をクリックして作成してください。",
|
||||
"agents.error": "agent の読み込みエラー:{{error}}",
|
||||
"agents.addAgent": "Add Agent",
|
||||
"agents.addAgentTitle": "Add Agent",
|
||||
"agents.addAgentDescription": "Claude Code の新しい sub agent を作成",
|
||||
"agents.agentName": "Agent 名",
|
||||
"agents.agentNamePlaceholder": "例:code-reviewer",
|
||||
"agents.agentContent": "Agent 内容",
|
||||
"agents.contentPlaceholder": "agent の指示を入力...",
|
||||
"agents.save": "保存",
|
||||
"agents.saving": "保存中...",
|
||||
"agents.create": "作成",
|
||||
"agents.creating": "作成中...",
|
||||
"agents.deleteConfirm": "agent \"{{agentName}}\" を削除してもよろしいですか?この操作は元に戻せません。",
|
||||
"agents.deleteTitle": "Agent を削除",
|
||||
"agents.emptyNameError": "Agent 名は空にできません",
|
||||
"agents.emptyContentError": "Agent 内容は空にできません",
|
||||
"agents.validationError": "検証エラー",
|
||||
"agents.agentExistsError": "Agent \"{{agentName}}\" は既に存在します",
|
||||
"agents.agentExistsTitle": "Agent が既に存在します",
|
||||
"projects.title": "プロジェクト",
|
||||
"projects.detail.editor": "プロジェクト設定エディター",
|
||||
"projects.detail.projectEditor": "プロジェクトエディター",
|
||||
|
||||
@@ -147,6 +147,28 @@
|
||||
"toast.commandSaveFailed": "保存命令失败:{{error}}",
|
||||
"toast.commandDeleted": "命令删除成功",
|
||||
"toast.commandDeleteFailed": "删除命令失败:{{error}}",
|
||||
"agents.title": "Agent",
|
||||
"agents.description": "管理您的 Claude Code sub agent",
|
||||
"agents.noAgents": "未找到 sub agent。点击「Add Agent」来创建一个。",
|
||||
"agents.error": "加载 agent 时出错:{{error}}",
|
||||
"agents.addAgent": "Add Agent",
|
||||
"agents.addAgentTitle": "Add Agent",
|
||||
"agents.addAgentDescription": "为 Claude Code 创建新的 sub agent",
|
||||
"agents.agentName": "Agent 名称",
|
||||
"agents.agentNamePlaceholder": "例如:code-reviewer",
|
||||
"agents.agentContent": "Agent 内容",
|
||||
"agents.contentPlaceholder": "输入您的 agent 说明...",
|
||||
"agents.save": "保存",
|
||||
"agents.saving": "保存中...",
|
||||
"agents.create": "创建",
|
||||
"agents.creating": "创建中...",
|
||||
"agents.deleteConfirm": "确定要删除 agent「{{agentName}}」吗?此操作无法撤销。",
|
||||
"agents.deleteTitle": "删除 Agent",
|
||||
"agents.emptyNameError": "Agent 名称不能为空",
|
||||
"agents.emptyContentError": "Agent 内容不能为空",
|
||||
"agents.validationError": "验证错误",
|
||||
"agents.agentExistsError": "Agent「{{agentName}}」已存在",
|
||||
"agents.agentExistsTitle": "Agent 已存在",
|
||||
"projects.title": "项目",
|
||||
"projects.detail.editor": "项目配置编辑器",
|
||||
"projects.detail.projectEditor": "项目编辑器",
|
||||
|
||||
@@ -568,6 +568,54 @@ export const useDeleteClaudeCommand = () => {
|
||||
});
|
||||
};
|
||||
|
||||
// Agent management hooks
|
||||
export const useClaudeAgents = () =>
|
||||
useQuery({
|
||||
queryKey: ["claude-agents"],
|
||||
queryFn: () => invoke<CommandFile[]>("read_claude_agents"),
|
||||
});
|
||||
|
||||
export const useWriteClaudeAgent = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
agentName,
|
||||
content,
|
||||
}: {
|
||||
agentName: string;
|
||||
content: string;
|
||||
}) => invoke<void>("write_claude_agent", { agentName, content }),
|
||||
onSuccess: () => {
|
||||
toast.success("Agent saved successfully");
|
||||
queryClient.invalidateQueries({ queryKey: ["claude-agents"] });
|
||||
},
|
||||
onError: (error) => {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
toast.error(`Failed to save agent: ${errorMessage}`);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteClaudeAgent = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (agentName: string) =>
|
||||
invoke<void>("delete_claude_agent", { agentName }),
|
||||
onSuccess: () => {
|
||||
toast.success("Agent deleted successfully");
|
||||
queryClient.invalidateQueries({ queryKey: ["claude-agents"] });
|
||||
},
|
||||
onError: (error) => {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
toast.error(`Failed to delete agent: ${errorMessage}`);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Helper function to rebuild tray menu
|
||||
const rebuildTrayMenu = async () => {
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,366 @@
|
||||
import { markdown, markdownLanguage } from "@codemirror/lang-markdown";
|
||||
import { yamlFrontmatter } from "@codemirror/lang-yaml";
|
||||
import { ask, message } from "@tauri-apps/plugin-dialog";
|
||||
import CodeMirror, { EditorView } from "@uiw/react-codemirror";
|
||||
import { BotIcon, PlusIcon, SaveIcon, TrashIcon } from "lucide-react";
|
||||
import { Suspense, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@/components/ui/accordion";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import {
|
||||
useClaudeAgents,
|
||||
useDeleteClaudeAgent,
|
||||
useWriteClaudeAgent,
|
||||
} from "@/lib/query";
|
||||
import { useCodeMirrorTheme } from "@/lib/use-codemirror-theme";
|
||||
|
||||
function AgentsPageContent() {
|
||||
const { t } = useTranslation();
|
||||
const { data: agents, isLoading, error } = useClaudeAgents();
|
||||
const writeAgent = useWriteClaudeAgent();
|
||||
const deleteAgent = useDeleteClaudeAgent();
|
||||
const [agentEdits, setAgentEdits] = useState<Record<string, string>>({});
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||
const codeMirrorTheme = useCodeMirrorTheme();
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<div className="text-center">{t("loading")}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<div className="text-center text-red-500">
|
||||
{t("agents.error", { error: error.message })}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const handleContentChange = (agentName: string, content: string) => {
|
||||
setAgentEdits((prev) => ({
|
||||
...prev,
|
||||
[agentName]: content,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSaveAgent = async (agentName: string) => {
|
||||
const content = agentEdits[agentName];
|
||||
if (content === undefined) return;
|
||||
|
||||
writeAgent.mutate({
|
||||
agentName,
|
||||
content,
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteAgent = async (agentName: string) => {
|
||||
const confirmed = await ask(t("agents.deleteConfirm", { agentName }), {
|
||||
title: t("agents.deleteTitle"),
|
||||
kind: "warning",
|
||||
});
|
||||
|
||||
if (confirmed) {
|
||||
deleteAgent.mutate(agentName);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
<div
|
||||
className="flex items-center p-3 border-b px-3 justify-between sticky top-0 bg-background z-10"
|
||||
data-tauri-drag-region
|
||||
>
|
||||
<div data-tauri-drag-region>
|
||||
<h3 className="font-bold" data-tauri-drag-region>
|
||||
{t("agents.title")}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground" data-tauri-drag-region>
|
||||
{t("agents.description")}
|
||||
</p>
|
||||
</div>
|
||||
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="ghost" className="text-muted-foreground" size="sm">
|
||||
<PlusIcon size={14} />
|
||||
{t("agents.addAgent")}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-w-[600px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="">
|
||||
{t("agents.addAgentTitle")}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-muted-foreground text-sm">
|
||||
{t("agents.addAgentDescription")}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<CreateAgentPanel onClose={() => setIsDialogOpen(false)} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
<div className="">
|
||||
{!agents || agents.length === 0 ? (
|
||||
<div className="text-center text-muted-foreground py-8">
|
||||
{t("agents.noAgents")}
|
||||
</div>
|
||||
) : (
|
||||
<ScrollArea className="h-full">
|
||||
<div className="">
|
||||
<Accordion type="multiple" className="">
|
||||
{agents.map((agent) => (
|
||||
<AccordionItem
|
||||
key={agent.name}
|
||||
value={agent.name}
|
||||
className="bg-card"
|
||||
>
|
||||
<AccordionTrigger className="hover:no-underline px-4 py-2 bg-card hover:bg-accent duration-150">
|
||||
<div className="flex items-center gap-2">
|
||||
<BotIcon size={12} />
|
||||
<span className="font-medium">{agent.name}</span>
|
||||
<span className="text-sm text-muted-foreground font-normal">
|
||||
{`~/.claude/agents/${agent.name}.md`}
|
||||
</span>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="pb-3">
|
||||
<div className="px-3 pt-3 space-y-3">
|
||||
<div className="rounded-lg overflow-hidden border">
|
||||
<CodeMirror
|
||||
value={
|
||||
agentEdits[agent.name] !== undefined
|
||||
? agentEdits[agent.name]
|
||||
: agent.content
|
||||
}
|
||||
height="180px"
|
||||
theme={codeMirrorTheme}
|
||||
onChange={(value) =>
|
||||
handleContentChange(agent.name, value)
|
||||
}
|
||||
placeholder={t("agents.contentPlaceholder")}
|
||||
extensions={[
|
||||
yamlFrontmatter({
|
||||
content: markdown({
|
||||
base: markdownLanguage,
|
||||
}),
|
||||
}),
|
||||
EditorView.lineWrapping,
|
||||
]}
|
||||
basicSetup={{
|
||||
lineNumbers: false,
|
||||
highlightActiveLineGutter: true,
|
||||
foldGutter: false,
|
||||
dropCursor: false,
|
||||
allowMultipleSelections: false,
|
||||
indentOnInput: true,
|
||||
bracketMatching: true,
|
||||
closeBrackets: true,
|
||||
autocompletion: true,
|
||||
highlightActiveLine: true,
|
||||
highlightSelectionMatches: true,
|
||||
searchKeymap: false,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-between bg-card">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => handleSaveAgent(agent.name)}
|
||||
disabled={
|
||||
writeAgent.isPending ||
|
||||
agentEdits[agent.name] === undefined
|
||||
}
|
||||
size="sm"
|
||||
>
|
||||
<SaveIcon size={14} className="" />
|
||||
{writeAgent.isPending
|
||||
? t("agents.saving")
|
||||
: t("agents.save")}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleDeleteAgent(agent.name)}
|
||||
disabled={deleteAgent.isPending}
|
||||
>
|
||||
<TrashIcon size={14} className="" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
))}
|
||||
</Accordion>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function AgentsPage() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<div className="text-center">{t("loading")}</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<AgentsPageContent />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
function CreateAgentPanel({ onClose }: { onClose?: () => void }) {
|
||||
const { t } = useTranslation();
|
||||
const [agentName, setAgentName] = useState("");
|
||||
const [agentContent, setAgentContent] = useState(`---
|
||||
name: your-sub-agent-name
|
||||
description: Description of when this subagent should be invoked
|
||||
tools: tool1, tool2, tool3 # Optional - inherits all tools if omitted
|
||||
model: sonnet # Optional - specify model alias or 'inherit'
|
||||
---
|
||||
|
||||
Your subagent's system prompt goes here. This can be multiple paragraphs
|
||||
and should clearly define the subagent's role, capabilities, and approach
|
||||
to solving problems.
|
||||
|
||||
Include specific instructions, best practices, and any constraints
|
||||
the subagent should follow.`);
|
||||
const writeAgent = useWriteClaudeAgent();
|
||||
const { data: agents } = useClaudeAgents();
|
||||
const codeMirrorTheme = useCodeMirrorTheme();
|
||||
|
||||
const handleCreateAgent = async () => {
|
||||
// Validate agent name
|
||||
if (!agentName.trim()) {
|
||||
await message(t("agents.emptyNameError"), {
|
||||
title: t("agents.validationError"),
|
||||
kind: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if agent already exists
|
||||
const exists = agents?.some((agent) => agent.name === agentName);
|
||||
if (exists) {
|
||||
await message(t("agents.agentExistsError", { agentName }), {
|
||||
title: t("agents.agentExistsTitle"),
|
||||
kind: "info",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate content
|
||||
if (!agentContent.trim()) {
|
||||
await message(t("agents.emptyContentError"), {
|
||||
title: t("agents.validationError"),
|
||||
kind: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
writeAgent.mutate(
|
||||
{
|
||||
agentName,
|
||||
content: agentContent,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
setAgentName("");
|
||||
setAgentContent("");
|
||||
onClose?.();
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4 mt-4">
|
||||
<div className="space-y-2">
|
||||
<Label className="block" htmlFor="agent-name">{t("agents.agentName")}</Label>
|
||||
<Input
|
||||
id="agent-name"
|
||||
value={agentName}
|
||||
onChange={(e) => setAgentName(e.target.value)}
|
||||
placeholder={t("agents.agentNamePlaceholder")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="block" htmlFor="agent-content">{t("agents.agentContent")}</Label>
|
||||
<div className="rounded-lg overflow-hidden border">
|
||||
<CodeMirror
|
||||
value={agentContent}
|
||||
onChange={(value) => setAgentContent(value)}
|
||||
height="200px"
|
||||
theme={codeMirrorTheme}
|
||||
placeholder={t("agents.contentPlaceholder")}
|
||||
extensions={[
|
||||
yamlFrontmatter({
|
||||
content: markdown({
|
||||
base: markdownLanguage,
|
||||
}),
|
||||
}),
|
||||
EditorView.lineWrapping,
|
||||
]}
|
||||
basicSetup={{
|
||||
lineNumbers: false,
|
||||
highlightActiveLineGutter: true,
|
||||
foldGutter: false,
|
||||
dropCursor: false,
|
||||
allowMultipleSelections: false,
|
||||
indentOnInput: true,
|
||||
bracketMatching: true,
|
||||
closeBrackets: true,
|
||||
autocompletion: true,
|
||||
highlightActiveLine: true,
|
||||
highlightSelectionMatches: true,
|
||||
searchKeymap: false,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
onClick={handleCreateAgent}
|
||||
disabled={
|
||||
!agentName.trim() ||
|
||||
!agentContent.trim() ||
|
||||
writeAgent.isPending
|
||||
}
|
||||
>
|
||||
{writeAgent.isPending
|
||||
? t("agents.creating")
|
||||
: t("agents.create")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { markdown, markdownLanguage } from "@codemirror/lang-markdown";
|
||||
import { yamlFrontmatter } from "@codemirror/lang-yaml";
|
||||
import { ask, message } from "@tauri-apps/plugin-dialog";
|
||||
import CodeMirror, { EditorView } from "@uiw/react-codemirror";
|
||||
import { PlusIcon, SaveIcon, TerminalIcon, TrashIcon } from "lucide-react";
|
||||
@@ -158,8 +159,10 @@ function CommandsPageContent() {
|
||||
}
|
||||
placeholder={t("commands.contentPlaceholder")}
|
||||
extensions={[
|
||||
markdown({
|
||||
base: markdownLanguage,
|
||||
yamlFrontmatter({
|
||||
content: markdown({
|
||||
base: markdownLanguage,
|
||||
}),
|
||||
}),
|
||||
EditorView.lineWrapping,
|
||||
]}
|
||||
@@ -307,8 +310,10 @@ function CreateCommandPanel({ onClose }: { onClose?: () => void }) {
|
||||
theme={codeMirrorTheme}
|
||||
placeholder={t("commands.contentPlaceholder")}
|
||||
extensions={[
|
||||
markdown({
|
||||
base: markdownLanguage,
|
||||
yamlFrontmatter({
|
||||
content: markdown({
|
||||
base: markdownLanguage,
|
||||
}),
|
||||
}),
|
||||
EditorView.lineWrapping,
|
||||
]}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { markdown, markdownLanguage } from "@codemirror/lang-markdown";
|
||||
import { yamlFrontmatter } from "@codemirror/lang-yaml";
|
||||
import CodeMirror, { EditorView } from "@uiw/react-codemirror";
|
||||
import { SaveIcon } from "lucide-react";
|
||||
import { Suspense, useEffect, useState } from "react";
|
||||
@@ -117,8 +118,10 @@ function MemoryPageContent() {
|
||||
value={content}
|
||||
height="100%"
|
||||
extensions={[
|
||||
markdown({
|
||||
base: markdownLanguage,
|
||||
yamlFrontmatter({
|
||||
content: markdown({
|
||||
base: markdownLanguage,
|
||||
}),
|
||||
}),
|
||||
EditorView.lineWrapping,
|
||||
]}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { RouteWrapper } from "./components/RouteWrapper";
|
||||
import { CommandsPage } from "./pages/CommandsPage";
|
||||
import { ConfigEditorPage } from "./pages/ConfigEditorPage";
|
||||
import { ConfigSwitcherPage } from "./pages/ConfigSwitcherPage";
|
||||
import { AgentsPage } from "./pages/AgentsPage";
|
||||
import { MCPPage } from "./pages/MCPPage";
|
||||
import { MemoryPage } from "./pages/MemoryPage";
|
||||
import { NotificationPage } from "./pages/NotificationPage";
|
||||
@@ -54,6 +55,14 @@ const router = createBrowserRouter([
|
||||
</RouteWrapper>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "agents",
|
||||
element: (
|
||||
<RouteWrapper>
|
||||
<AgentsPage />
|
||||
</RouteWrapper>
|
||||
)
|
||||
},
|
||||
{
|
||||
path: "usage",
|
||||
element: (
|
||||
|
||||
Reference in New Issue
Block a user