diff --git a/agent/app/dto/agents.go b/agent/app/dto/agents.go index 54fd18224..c5c263c7a 100644 --- a/agent/app/dto/agents.go +++ b/agent/app/dto/agents.go @@ -608,7 +608,7 @@ type AgentConfigFile struct { type AgentSkillSearchReq struct { AgentID uint `json:"agentId" validate:"required"` - Source string `json:"source" validate:"required,oneof=clawhub-global clawhub-cn skillhub official skills-sh"` + Source string `json:"source" validate:"required,oneof=clawhub-global clawhub-cn skillhub official skills-sh local-hub"` Keyword string `json:"keyword" validate:"required"` } @@ -645,7 +645,7 @@ type AgentSkillUpdateReq struct { type AgentSkillInstallReq struct { AgentID uint `json:"agentId" validate:"required"` - Source string `json:"source" validate:"required,oneof=clawhub-global clawhub-cn skillhub official skills-sh"` + Source string `json:"source" validate:"required,oneof=clawhub-global clawhub-cn skillhub official skills-sh local-hub"` Slug string `json:"slug" validate:"required"` TaskID string `json:"taskID" validate:"required"` } diff --git a/agent/app/service/agents_skills.go b/agent/app/service/agents_skills.go index d796e142b..6a0792ad6 100644 --- a/agent/app/service/agents_skills.go +++ b/agent/app/service/agents_skills.go @@ -1,9 +1,18 @@ package service import ( + "archive/tar" + "archive/zip" + "compress/gzip" "encoding/json" "fmt" + "io" + "os" + "os/exec" + "path" + "path/filepath" "regexp" + "strconv" "strings" "time" @@ -12,10 +21,14 @@ import ( "github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/common" ) const clawhubGlobalRegistry = "https://clawhub.com" const clawhubChinaRegistry = "https://mirror-cn.clawhub.com" +const localSkillHubSource = "local-hub" +const localSkillHubPublishedStatus = "published" +const hermesManagedSkillsDir = "/opt/data/skills" type openclawSkillsList struct { Skills []openclawSkillListItem `json:"skills"` @@ -38,8 +51,17 @@ type skillhubSearchPayload struct { Results []dto.AgentSkillSearchItem `json:"results"` } +type localSkillHubItem struct { + ID uint + Name string + Status string + ApplicableAgent string + PackagePath string +} + var clawhubSearchLinePattern = regexp.MustCompile(`^(\S+)\s+(.+?)\s+\(([\d.]+)\)$`) var ansiEscapePattern = regexp.MustCompile(`\x1b\[[0-9;?]*[ -/]*[@-~]`) +var safeLocalSkillDirNamePattern = regexp.MustCompile(`[^a-zA-Z0-9._-]+`) func (a AgentService) ListSkills(req dto.AgentIDReq) ([]dto.AgentSkillItem, error) { agent, install, err := a.loadAgentAndInstall(req.AgentID) @@ -66,6 +88,12 @@ func (a AgentService) ListSkills(req dto.AgentIDReq) ([]dto.AgentSkillItem, erro } func (a AgentService) SearchSkills(req dto.AgentSkillSearchReq) ([]dto.AgentSkillSearchItem, error) { + if req.Source == localSkillHubSource { + return nil, fmt.Errorf("local skills hub is provided by enterprise edition") + } + if global.CONF.Base.IsOffline { + return nil, fmt.Errorf("offline environment cannot access remote Skills Hub") + } agent, install, err := a.loadAgentAndInstall(req.AgentID) if err != nil { return nil, err @@ -115,6 +143,9 @@ func (a AgentService) UpdateSkill(req dto.AgentSkillUpdateReq) error { } func (a AgentService) InstallSkill(req dto.AgentSkillInstallReq) error { + if global.CONF.Base.IsOffline && req.Source != localSkillHubSource { + return fmt.Errorf("offline environment cannot access remote Skills Hub") + } agent, install, err := a.loadAgentAndInstall(req.AgentID) if err != nil { return err @@ -122,6 +153,29 @@ func (a AgentService) InstallSkill(req dto.AgentSkillInstallReq) error { if err := ensureContainerRunning(install.ContainerName); err != nil { return err } + if req.Source == localSkillHubSource { + if !global.IsMaster { + return fmt.Errorf("local skills hub is only available on the master node") + } + hostPath, skillName, err := resolveLocalSkillPackage(agent.AgentType, req.Slug) + if err != nil { + return err + } + installTask, err := task.NewTaskWithOps(skillName, task.TaskInstall, task.TaskScopeAI, req.TaskID, req.AgentID) + if err != nil { + return err + } + installTask.AddSubTask("Install local skill", func(t *task.Task) error { + mgr := cmd.NewCommandMgr(cmd.WithTask(*t), cmd.WithContext(t.TaskCtx), cmd.WithTimeout(20*time.Minute)) + return installLocalSkillPackage(mgr, install.ContainerName, agent.AgentType, agent.ConfigPath, hostPath, skillName) + }, nil) + go func() { + if err := installTask.Execute(); err != nil { + global.LOG.Errorf("install local skill failed: %v", err) + } + }() + return nil + } installTask, err := task.NewTaskWithOps(req.Slug, task.TaskInstall, task.TaskScopeAI, req.TaskID, req.AgentID) if err != nil { return err @@ -153,6 +207,498 @@ func (a AgentService) InstallSkill(req dto.AgentSkillInstallReq) error { return nil } +func installLocalSkillPackage(mgr *cmd.CommandHelper, containerName, agentType, configPath, hostPath, skillName string) error { + targetDir, err := resolveAgentLocalSkillDir(agentType) + if err != nil { + return err + } + tempDir, err := os.MkdirTemp("", "1panel-agent-skill-*") + if err != nil { + return err + } + defer os.RemoveAll(tempDir) + extractRoot := filepath.Join(tempDir, "extract") + if err := os.MkdirAll(extractRoot, 0755); err != nil { + return err + } + if err := extractLocalSkillPackage(hostPath, extractRoot); err != nil { + return err + } + copyRoot, installedSkillName, err := normalizeLocalSkillInstallRoot(extractRoot, filepath.Join(tempDir, "install"), skillName) + if err != nil { + return err + } + if err := mgr.Run("docker", "exec", containerName, "mkdir", "-p", targetDir); err != nil { + return err + } + if err := mgr.Run("docker", "cp", copyRoot+"/.", containerName+":"+targetDir); err != nil { + return err + } + if agentType == constant.AppOpenclaw { + return registerOpenclawLocalSkill(containerName, configPath, installedSkillName) + } + return nil +} + +func resolveAgentLocalSkillDir(agentType string) (string, error) { + switch agentType { + case constant.AppOpenclaw: + return openclawManagedSkillsDir, nil + case constant.AppHermesAgent: + return hermesManagedSkillsDir, nil + default: + return "", fmt.Errorf("%s does not support", agentType) + } +} + +func resolveLocalSkillPackage(agentType, skillID string) (string, string, error) { + skillID = strings.TrimSpace(skillID) + id, err := strconv.ParseUint(skillID, 10, 64) + if err != nil || id == 0 { + return "", "", fmt.Errorf("invalid local skill id") + } + dbPath := filepath.Join(global.CONF.Base.InstallDir, "1panel", "db", "enterprise.db") + if _, err := os.Stat(dbPath); err != nil { + return "", "", fmt.Errorf("local skills hub is unavailable") + } + db, err := common.GetDBWithPath(dbPath) + if err != nil { + return "", "", err + } + defer common.CloseDB(db) + var item localSkillHubItem + if err := db.Table("skill_hub_items"). + Select("id,name,status,applicable_agent,package_path"). + Where("id = ? AND status = ?", uint(id), localSkillHubPublishedStatus). + First(&item).Error; err != nil { + return "", "", fmt.Errorf("local skill is not published or does not exist") + } + if !matchLocalSkillAgent(item.ApplicableAgent, agentType) { + return "", "", fmt.Errorf("local skill does not support %s", agentType) + } + packagePath, err := validateLocalSkillPackagePath(item.PackagePath) + if err != nil { + return "", "", err + } + return packagePath, sanitizeLocalSkillDirName(item.Name, packagePath, skillID), nil +} + +func matchLocalSkillAgent(applicableAgent, agentType string) bool { + applicableAgent = strings.TrimSpace(applicableAgent) + if applicableAgent == "" { + return true + } + for _, item := range strings.Split(applicableAgent, ",") { + normalized := normalizeLocalSkillAgent(strings.TrimSpace(item)) + if normalized == "common" || normalized == normalizeLocalSkillAgent(agentType) { + return true + } + } + return false +} + +func normalizeLocalSkillAgent(agentType string) string { + switch strings.ToLower(strings.TrimSpace(agentType)) { + case constant.AppHermesAgent: + return "hermes" + default: + return strings.ToLower(strings.TrimSpace(agentType)) + } +} + +func sanitizeLocalSkillDirName(name, packagePath, fallback string) string { + name = strings.TrimSpace(name) + if name == "" { + name = strings.TrimSuffix(filepath.Base(packagePath), localSkillPackageExtension(packagePath)) + } + name = strings.Trim(safeLocalSkillDirNamePattern.ReplaceAllString(name, "-"), "-") + if name == "" || name == "." || name == ".." { + name = "skill-" + strings.TrimSpace(fallback) + } + if name == "skill-" { + return "skill" + } + return name +} + +func validateLocalSkillPackagePath(packagePath string) (string, error) { + packagePath = strings.TrimSpace(packagePath) + if packagePath == "" { + return "", fmt.Errorf("skill package path is required") + } + if _, err := localSkillPackageFormat(packagePath); err != nil { + return "", err + } + absPath, err := filepath.Abs(packagePath) + if err != nil { + return "", err + } + resolvedPath, err := filepath.EvalSymlinks(absPath) + if err != nil { + return "", err + } + root := filepath.Join(global.CONF.Base.InstallDir, "1panel", "uploads", "skills-hub") + absRoot, err := filepath.Abs(root) + if err != nil { + return "", err + } + resolvedRoot, err := filepath.EvalSymlinks(absRoot) + if err != nil { + // upload directory may not exist yet on agent host; treat as invalid + return "", fmt.Errorf("invalid local skill package path") + } + rel, err := filepath.Rel(resolvedRoot, resolvedPath) + if err != nil || rel == "." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) || rel == ".." { + return "", fmt.Errorf("invalid local skill package path") + } + info, err := os.Stat(resolvedPath) + if err != nil { + return "", err + } + if info.IsDir() { + return "", fmt.Errorf("skill package must be a file") + } + return resolvedPath, nil +} + +func normalizeLocalSkillInstallRoot(extractRoot, installRoot, skillName string) (string, string, error) { + entries, err := os.ReadDir(extractRoot) + if err != nil { + return "", "", err + } + if len(entries) == 0 { + return "", "", fmt.Errorf("skill package is empty") + } + if len(entries) == 1 && entries[0].IsDir() { + return extractRoot, entries[0].Name(), nil + } + skillRoot := filepath.Join(installRoot, skillName) + if err := os.MkdirAll(skillRoot, 0755); err != nil { + return "", "", err + } + if err := copyLocalDirContents(extractRoot, skillRoot); err != nil { + return "", "", err + } + return installRoot, skillName, nil +} + +func registerOpenclawLocalSkill(containerName, configPath, skillName string) error { + conf, err := readOpenclawConfig(configPath) + if err != nil { + return err + } + skillKey, err := getOpenclawSkillKey(containerName, skillName) + if err != nil { + return err + } + setOpenclawSkillEnabled(conf, skillKey, true) + return writeOpenclawConfigRaw(configPath, conf) +} + +func extractLocalSkillPackage(packagePath, targetDir string) error { + format, err := localSkillPackageFormat(packagePath) + if err != nil { + return err + } + switch format { + case "zip": + return unzipLocalSkillPackage(packagePath, targetDir) + case "tar", "targz": + return untarLocalSkillPackage(packagePath, targetDir, format == "targz") + case "7z": + return un7zLocalSkillPackage(packagePath, targetDir) + default: + return fmt.Errorf("unsupported skill package format") + } +} + +func unzipLocalSkillPackage(packagePath, targetDir string) error { + reader, err := zip.OpenReader(packagePath) + if err != nil { + return err + } + defer reader.Close() + root, err := filepath.Abs(targetDir) + if err != nil { + return err + } + for _, file := range reader.File { + name, err := safeLocalSkillZipEntryName(file.Name) + if err != nil { + return err + } + targetPath := filepath.Join(root, name) + rel, err := filepath.Rel(root, targetPath) + if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) { + return fmt.Errorf("invalid zip entry: %s", file.Name) + } + if file.FileInfo().IsDir() { + if err := os.MkdirAll(targetPath, file.Mode()); err != nil { + return err + } + continue + } + if file.Mode()&os.ModeSymlink != 0 { + return fmt.Errorf("unsupported zip entry: %s", file.Name) + } + if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil { + return err + } + rc, err := file.Open() + if err != nil { + return err + } + dst, err := os.OpenFile(targetPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, file.Mode()) + if err != nil { + _ = rc.Close() + return err + } + _, copyErr := io.Copy(dst, rc) + closeErr := dst.Close() + _ = rc.Close() + if copyErr != nil { + return copyErr + } + if closeErr != nil { + return closeErr + } + } + return nil +} + +func untarLocalSkillPackage(packagePath, targetDir string, gzipped bool) error { + src, err := os.Open(packagePath) + if err != nil { + return err + } + defer src.Close() + var reader io.Reader = src + var gzipReader *gzip.Reader + if gzipped { + gzipReader, err = gzip.NewReader(src) + if err != nil { + return err + } + defer gzipReader.Close() + reader = gzipReader + } + root, err := filepath.Abs(targetDir) + if err != nil { + return err + } + tarReader := tar.NewReader(reader) + for { + header, err := tarReader.Next() + if err == io.EOF { + break + } + if err != nil { + return err + } + name, err := safeLocalSkillZipEntryName(header.Name) + if err != nil { + return err + } + targetPath := filepath.Join(root, name) + rel, err := filepath.Rel(root, targetPath) + if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(os.PathSeparator)) { + return fmt.Errorf("invalid tar entry: %s", header.Name) + } + info := header.FileInfo() + if info.IsDir() { + if err := os.MkdirAll(targetPath, info.Mode()); err != nil { + return err + } + continue + } + if header.Typeflag != tar.TypeReg && header.Typeflag != tar.TypeRegA { + continue + } + if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil { + return err + } + dst, err := os.OpenFile(targetPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, info.Mode()) + if err != nil { + return err + } + _, copyErr := io.Copy(dst, tarReader) + closeErr := dst.Close() + if copyErr != nil { + return copyErr + } + if closeErr != nil { + return closeErr + } + } + return nil +} + +func un7zLocalSkillPackage(packagePath, targetDir string) error { + if _, err := listLocal7zEntries(packagePath); err != nil { + return err + } + root, err := filepath.Abs(targetDir) + if err != nil { + return err + } + bin, err := findLocal7zBinary() + if err != nil { + return err + } + tempDir, err := os.MkdirTemp("", "1panel-agent-skill-7z-*") + if err != nil { + return err + } + defer os.RemoveAll(tempDir) + if err := cmd.NewCommandMgr(cmd.WithTimeout(20*time.Minute)).Run(bin, "x", "-y", "-o"+tempDir, packagePath); err != nil { + return err + } + if err := validateExtractedLocalSkillPackage(tempDir); err != nil { + return err + } + return copyLocalDirContents(tempDir, root) +} + +func safeLocalSkillZipEntryName(name string) (string, error) { + clean := path.Clean(strings.ReplaceAll(name, "\\", "/")) + clean = strings.TrimLeft(clean, "/") + if clean == "." || clean == "" || clean == ".." || strings.HasPrefix(clean, "../") { + return "", fmt.Errorf("invalid zip entry: %s", name) + } + return clean, nil +} + +func copyLocalDirContents(sourceRoot, targetRoot string) error { + return filepath.Walk(sourceRoot, func(sourcePath string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if sourcePath == sourceRoot { + return nil + } + if info.Mode()&os.ModeSymlink != 0 || (!info.IsDir() && !info.Mode().IsRegular()) { + return fmt.Errorf("unsupported skill package entry: %s", sourcePath) + } + rel, err := filepath.Rel(sourceRoot, sourcePath) + if err != nil { + return err + } + name, err := safeLocalSkillZipEntryName(rel) + if err != nil { + return err + } + targetPath := filepath.Join(targetRoot, name) + if info.IsDir() { + return os.MkdirAll(targetPath, info.Mode()) + } + return copyLocalSkillFile(sourcePath, targetPath) + }) +} + +func copyLocalSkillFile(source, target string) error { + src, err := os.Open(source) + if err != nil { + return err + } + defer src.Close() + if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil { + return err + } + dst, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) + if err != nil { + return err + } + if _, err := io.Copy(dst, src); err != nil { + _ = dst.Close() + return err + } + return dst.Close() +} + +func validateExtractedLocalSkillPackage(root string) error { + return filepath.Walk(root, func(currentPath string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if currentPath == root { + return nil + } + if info.Mode()&os.ModeSymlink != 0 || (!info.IsDir() && !info.Mode().IsRegular()) { + return fmt.Errorf("unsupported skill package entry: %s", currentPath) + } + return nil + }) +} + +func localSkillPackageExtension(name string) string { + lower := strings.ToLower(filepath.Base(strings.TrimSpace(name))) + if lower == "" || lower == "." { + return "" + } + if strings.HasSuffix(lower, ".tar.gz") { + return ".tar.gz" + } + return strings.ToLower(filepath.Ext(lower)) +} + +func localSkillPackageFormat(name string) (string, error) { + switch localSkillPackageExtension(name) { + case ".zip": + return "zip", nil + case ".7z": + return "7z", nil + case ".tar": + return "tar", nil + case ".tar.gz": + return "targz", nil + default: + return "", fmt.Errorf("only .zip, .7z, .tar, and .tar.gz skill packages are supported") + } +} + +func findLocal7zBinary() (string, error) { + for _, name := range []string{"7z", "7za", "7zr"} { + if path, err := exec.LookPath(name); err == nil { + return path, nil + } + } + return "", fmt.Errorf("7z command is required to process .7z skill packages") +} + +func listLocal7zEntries(packagePath string) ([]string, error) { + bin, err := findLocal7zBinary() + if err != nil { + return nil, err + } + output, err := exec.Command(bin, "l", "-slt", packagePath).CombinedOutput() + if err != nil { + if message := strings.TrimSpace(string(output)); message != "" { + return nil, fmt.Errorf("7z failed: %w: %s", err, message) + } + return nil, fmt.Errorf("7z failed: %w", err) + } + entries := make([]string, 0) + for _, line := range strings.Split(string(output), "\n") { + line = strings.TrimSpace(line) + if !strings.HasPrefix(line, "Path = ") { + continue + } + entry := strings.TrimSpace(strings.TrimPrefix(line, "Path = ")) + if entry == "" || entry == packagePath || entry == filepath.Base(packagePath) { + continue + } + name, err := safeLocalSkillZipEntryName(entry) + if err != nil { + return nil, err + } + entries = append(entries, name) + } + if len(entries) == 0 { + return nil, fmt.Errorf("skill package is empty") + } + return entries, nil +} + func (a AgentService) UninstallSkill(req dto.AgentSkillUninstallReq) error { agent, install, err := a.loadAgentAndInstall(req.AgentID) if err != nil { @@ -313,7 +859,7 @@ func parseOpenclawSkillKey(name, output string) (string, error) { return "", err } if payload.SkillKey == "" { - return "", fmt.Errorf("skill %s does not have a skillKey", name) + return name, nil } return payload.SkillKey, nil } diff --git a/core/init/migration/helper/menu.go b/core/init/migration/helper/menu.go index fb7033bac..1b484398d 100644 --- a/core/init/migration/helper/menu.go +++ b/core/init/migration/helper/menu.go @@ -96,6 +96,15 @@ func LoadMenus() string { Path: "/ai/ai-proxy/model-pool", Sort: 150, }, "AIModel") + item[i].Children = UpsertMenuByLabel(item[i].Children, dto.ShowMenu{ + ID: "47", + Disabled: false, + Title: "aiTools.skillsHub.title", + IsShow: true, + Label: "SkillsHub", + Path: "/ai/skills-hub", + Sort: 155, + }, "AIProxyManagement") item[i].Children = UpsertMenuByLabel(item[i].Children, dto.ShowMenu{ ID: "45", Disabled: false, @@ -104,7 +113,7 @@ func LoadMenus() string { Label: "AIBenchmark", Path: "/ai/benchmark", Sort: 160, - }, "AIProxyManagement") + }, "SkillsHub") continue } if item[i].Label != "Xpack-Menu" { @@ -143,6 +152,7 @@ func MenuSort() []dto.MenuLabelSort { {Label: "Agents", Sort: 50}, {Label: "AIModel", Sort: 100}, {Label: "AIProxyManagement", Sort: 150}, + {Label: "SkillsHub", Sort: 155}, {Label: "AIBenchmark", Sort: 160}, {Label: "MCPServer", Sort: 200}, {Label: "GPU", Sort: 300}, diff --git a/core/init/migration/migrate.go b/core/init/migration/migrate.go index eb5e253ad..7c263be8e 100644 --- a/core/init/migration/migrate.go +++ b/core/init/migration/migrate.go @@ -45,6 +45,7 @@ func Init() { migrations.AddOpsReportThresholdSetting, migrations.AddAIBenchmarkMenu, migrations.AddAIProxyMenu, + migrations.AddSkillsHubMenu, migrations.AddOperationLogUser, migrations.AddIsOfflineSetting, }) diff --git a/core/init/migration/migrations/init.go b/core/init/migration/migrations/init.go index 56f1d5481..2ae703bc8 100644 --- a/core/init/migration/migrations/init.go +++ b/core/init/migration/migrations/init.go @@ -965,6 +965,15 @@ func buildAiMenuChildren(children []dto.ShowMenu) []dto.ShowMenu { Path: "/ai/ai-proxy/model-pool", Sort: 150, }, "AIProxyManagement")) + result = append(result, normalizeAiMenuChild(children, dto.ShowMenu{ + ID: "47", + Label: "SkillsHub", + Disabled: false, + IsShow: true, + Title: "aiTools.skillsHub.title", + Path: "/ai/skills-hub", + Sort: 155, + }, "SkillsHub")) result = append(result, normalizeAiMenuChild(children, dto.ShowMenu{ ID: "45", Label: "AIBenchmark", @@ -1106,6 +1115,54 @@ var AddAIProxyMenu = &gormigrate.Migration{ }, } +var AddSkillsHubMenu = &gormigrate.Migration{ + ID: "20260517-add-skills-hub-menu", + Migrate: func(tx *gorm.DB) error { + if !global.CONF.Base.IsEnterprise { + return nil + } + var menuJSON string + if err := tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Pluck("value", &menuJSON).Error; err != nil { + return err + } + if menuJSON == "" { + menuJSON = helper.LoadMenus() + } + + var menus []dto.ShowMenu + if err := json.Unmarshal([]byte(menuJSON), &menus); err != nil { + return tx.Model(&model.Setting{}). + Where("key = ?", "HideMenu"). + Update("value", helper.LoadMenus()).Error + } + + newItem := dto.ShowMenu{ + ID: "47", + Disabled: false, + Title: "aiTools.skillsHub.title", + IsShow: true, + Label: "SkillsHub", + Path: "/ai/skills-hub", + Sort: 155, + } + for i := range menus { + if menus[i].Label != "AI-Menu" { + continue + } + menus[i].Children = helper.UpsertMenuByLabel(menus[i].Children, newItem, "AIProxyManagement") + break + } + + updatedJSON, err := json.Marshal(menus) + if err != nil { + return tx.Model(&model.Setting{}). + Where("key = ?", "HideMenu"). + Update("value", helper.LoadMenus()).Error + } + return tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Update("value", string(updatedJSON)).Error + }, +} + var AddUserManagementMenu = &gormigrate.Migration{ ID: "20260415-add-user-management-menu", Migrate: func(tx *gorm.DB) error { diff --git a/frontend/src/api/interface/ai.ts b/frontend/src/api/interface/ai.ts index 888fedd7c..e842c238f 100644 --- a/frontend/src/api/interface/ai.ts +++ b/frontend/src/api/interface/ai.ts @@ -901,7 +901,7 @@ export namespace AI { export interface AgentSkillSearchReq { agentId: number; - source: 'clawhub-global' | 'clawhub-cn' | 'skillhub' | 'official' | 'skills-sh'; + source: 'clawhub-global' | 'clawhub-cn' | 'skillhub' | 'official' | 'skills-sh' | 'local-hub'; keyword: string; } @@ -938,7 +938,7 @@ export namespace AI { export interface AgentSkillInstallReq { agentId: number; - source: 'clawhub-global' | 'clawhub-cn' | 'skillhub' | 'official' | 'skills-sh'; + source: 'clawhub-global' | 'clawhub-cn' | 'skillhub' | 'official' | 'skills-sh' | 'local-hub'; slug: string; taskID: string; } diff --git a/frontend/src/extensions/xpack.ts b/frontend/src/extensions/xpack.ts index 792d3c150..7e1677b6d 100644 --- a/frontend/src/extensions/xpack.ts +++ b/frontend/src/extensions/xpack.ts @@ -8,6 +8,10 @@ type EnterpriseUserModule = { getUserInfo?: (currentNode: string) => Promise; }; +type EnterpriseSkillsHubModule = { + listPublishedSkillHub?: (params: { agentType?: string; keyword?: string }) => Promise; +}; + function findModule(modules: Record, suffix: string): T | null { for (const path in modules) { if (path.endsWith(suffix)) { @@ -65,6 +69,22 @@ export async function getEnterpriseUserInfo(currentNode: string) { return module.getUserInfo(currentNode); } +function getEnterpriseSkillsHubModule(): EnterpriseSkillsHubModule | null { + const enterpriseModules = import.meta.glob('@/enterprise/api/modules/skills-hub.ts', { eager: true }) as Record< + string, + EnterpriseSkillsHubModule + >; + return findModule(enterpriseModules, '/api/modules/skills-hub.ts'); +} + +export async function listEnterprisePublishedSkillHub(params: { agentType?: string; keyword?: string }) { + const module = getEnterpriseSkillsHubModule(); + if (!module?.listPublishedSkillHub) { + return null; + } + return module.listPublishedSkillHub(params); +} + export const searchExtensionSetting = searchXpackSetting; export const getExtensionProxyDocker = getXpackProxyDocker; export const updateExtensionSettingByKey = updateXpackSettingByKey; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 3c020ad07..0e283c6c7 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -722,6 +722,7 @@ const message = { skillsMarketSourceClawhubGlobal: 'ClawHub (Global)', skillsMarketSourceClawhubChina: 'ClawHub (China)', skillsMarketSourceSkillhub: 'SkillHub (Tencent)', + skillsMarketSourceLocalHub: 'Local Skill Store', skillsScore: 'Score', skillsMarketSourceOfficial: 'Official', versionUnsupportedTitle: 'This feature is not supported in the current version', @@ -1010,6 +1011,126 @@ const message = { importResult: 'Imported {0}, duplicated {1}, invalid {2}', auditLogs: 'Audit Logs', }, + skillsHub: { + title: 'Skills Hub', + upload: 'Upload Skill', + importSkill: 'Import Skill', + importMethod: 'Import Method', + repositoryAddress: 'Repository Address', + repositoryAddressPlaceholder: 'Enter repository address', + repositoryRef: 'Branch or Tag', + repositoryRefPlaceholder: 'Defaults to main, then tries master if it fails', + repositoryDownloadPreview: 'Target download URL:', + packageURL: 'Package URL', + packageURLPlaceholder: 'Enter .zip package address', + importSuccess: 'Skill imported successfully and is now pending review.', + source: 'Source', + applicableAgent: 'Applicable Agent', + riskLevel: 'Risk Level', + riskReferenceHint: + 'The risk level is generated automatically from the Skill package content and is for review reference only.\nLow: no obvious risk found\nMedium: scripts, dependencies, external URLs, or environment variables\nHigh: remote execution, sensitive information, or dangerous commands', + riskCheckResult: 'Security Check Result', + noRiskFound: 'No obvious risk found.', + riskFilePath: 'File Path', + riskKeyword: 'Matched Keyword or Rule', + riskDescription: 'Risk Description', + publishHighRiskConfirm: + 'This Skill has high-risk items and may contain script execution, external downloads, or sensitive information access. Publish anyway?', + approve: 'Approve', + reject: 'Reject', + publish: 'Publish', + disable: 'Unpublish', + customTarget: 'Custom Agent', + addCustomTarget: 'Add Agent', + installToTarget: 'Install to Custom Agent', + containerized: 'Containerized', + containerName: 'Container Name', + hostSkillDir: 'Skill Directory', + hostSkillDirHelper: 'Enter the Skill directory path on the server where 1Panel is installed.', + containerSkillDir: 'Container Skill Directory', + installMethod: 'Install Method', + copy: 'Copy', + extract: 'Extract', + postInstallCommand: 'Post-install Command', + localSource: 'Local Upload', + uploadDragTitle: 'Drop a Skill package here, or click to upload', + uploadFormatLabel: 'Formats:', + uploadFormatValue: '.zip, .7z, .tar, .tar.gz', + uploadSizeLabel: 'File size:', + uploadSizeValue: 'No more than 5 MB per file', + uploadRequirementLabel: 'Package requirement:', + uploadRequirementPrefix: 'The Skill package must contain', + onlySupportedArchive: 'Only .zip, .7z, .tar, and .tar.gz Skill packages can be uploaded', + uploadSizeExceeded: 'A single skill package must not exceed 5 MB', + uploadSuccess: 'Skill uploaded successfully and is now pending review.', + offlineNotice: 'Current environment is offline. Only skills from the local Skill Store can be installed.', + publishConfirm: 'Publish this skill? Published skills can be installed from the local Skill Store.', + disableConfirm: 'Unpublish this skill? Skills already installed in agents will not be affected.', + deleteConfirm: + 'Delete this skill? This cannot be undone. Skills already installed in agents will not be affected.', + rejectConfirm: 'Reject this skill review? It will move to the rejected status.', + downloadPublishedOnly: 'Only published skill packages can be downloaded.', + containerNameRequired: 'Containerized targets require a container name.', + deleteTargetConfirm: 'Delete this custom agent? Users will no longer be able to select it.', + noInstallTarget: 'No custom agent is available. Configure an install target first.', + postInstallConfirm: + 'This target has a post-install command. Confirm the command is trusted before running.', + status: { + pending: 'Pending', + approved: 'Approved', + published: 'Published', + disabled: 'Unpublished', + rejected: 'Rejected', + }, + agent: { + common: 'Common', + }, + risk: { + low: 'Low', + medium: 'Medium', + high: 'High', + }, + riskType: { + script_file: 'Script File', + dependency_manifest: 'Dependency Manifest', + external_url: 'External URL', + environment_variable: 'Environment Variable', + dockerfile: 'Dockerfile', + command_execution: 'Command Execution', + remote_script_execution: 'Remote Script Execution', + eval_execution: 'Dynamic Execution', + base64_execution: 'Base64 Decode Execution', + privileged_command: 'Privileged Command', + dangerous_delete: 'Dangerous Delete', + world_writable_permission: 'High-risk Permission', + credential: 'Suspected Credential', + sensitive_path: 'Sensitive Path', + suspicious_download_execute: 'Suspicious Download Execution', + binary_content: 'Binary Content', + scan_truncated: 'Scan Truncated', + }, + riskDescriptionText: { + script_file: 'Contains script files that may execute local commands.', + dependency_manifest: + 'Contains dependency manifests that may fetch external dependencies during install.', + external_url: 'Contains external URLs. Verify that the outbound addresses are trusted.', + environment_variable: 'Declares environment variables. Verify their purpose and default values.', + dockerfile: 'Contains a Dockerfile that may define image build or runtime logic.', + command_execution: + 'Contains command execution related calls. Verify that the execution logic is trusted.', + remote_script_execution: 'Contains logic that downloads and directly runs a remote script.', + eval_execution: 'Contains dynamic execution through eval.', + base64_execution: 'Contains logic that executes content after Base64 decoding.', + privileged_command: 'Contains sudo privilege escalation commands.', + dangerous_delete: 'Contains dangerous delete commands.', + world_writable_permission: 'Contains chmod 777 permission changes.', + credential: 'Contains suspected API Key, Token, Secret, or Password.', + sensitive_path: 'Contains access to sensitive directories or files.', + suspicious_download_execute: 'Contains suspicious outbound download or download-and-execute logic.', + binary_content: 'The file contains NUL bytes and may hide executable or binary content.', + scan_truncated: 'The file exceeds the scan limit. Unscanned content still requires manual review.', + }, + }, benchmark: { title: 'Benchmark', create: 'Create', diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts index a9d976ffa..1be65914e 100644 --- a/frontend/src/lang/modules/es-es.ts +++ b/frontend/src/lang/modules/es-es.ts @@ -728,6 +728,7 @@ const message = { skillsMarketSourceClawhubGlobal: 'ClawHub (Global)', skillsMarketSourceClawhubChina: 'ClawHub (China)', skillsMarketSourceSkillhub: 'SkillHub (Tencent)', + skillsMarketSourceLocalHub: 'Tienda local de Skills', skillsScore: 'Puntuación', skillsMarketSourceOfficial: 'Oficial', versionUnsupportedTitle: 'Esta función no es compatible con la versión actual', @@ -1024,6 +1025,129 @@ const message = { importResult: 'Imported {0}, duplicated {1}, invalid {2}', auditLogs: 'Audit Logs', }, + skillsHub: { + title: 'Tienda de Skills', + upload: 'Subir Skill', + importSkill: 'Importar Skill', + importMethod: 'Método de importación', + repositoryAddress: 'Dirección del repositorio', + repositoryAddressPlaceholder: 'Introduzca la dirección del repositorio', + repositoryRef: 'Rama o Tag', + repositoryRefPlaceholder: 'Predeterminado main; si falla, se intentará master', + repositoryDownloadPreview: 'URL de descarga de destino:', + packageURL: 'URL del paquete', + packageURLPlaceholder: 'Introduzca la dirección del paquete .zip', + importSuccess: 'Skill importado correctamente y en estado pendiente de revisión.', + source: 'Fuente', + applicableAgent: 'Agente aplicable', + riskLevel: 'Nivel de riesgo', + riskReferenceHint: + 'El nivel de riesgo se genera automáticamente según el contenido del paquete Skill y es solo una referencia.\nBajo: sin riesgos evidentes\nMedio: scripts, dependencias, URL externas o variables de entorno\nAlto: ejecución remota, información sensible o comandos peligrosos', + riskCheckResult: 'Resultado de seguridad', + noRiskFound: 'No se encontraron riesgos evidentes.', + riskFilePath: 'Ruta del archivo', + riskKeyword: 'Palabra clave o regla coincidente', + riskDescription: 'Descripción del riesgo', + publishHighRiskConfirm: + 'Este Skill tiene elementos de alto riesgo y puede contener ejecución de scripts, descargas externas o acceso a información sensible. ¿Publicarlo de todos modos?', + approve: 'Aprobar', + reject: 'Rechazar', + publish: 'Publicar', + disable: 'Retirar', + customTarget: 'Agente personalizado', + addCustomTarget: 'Agregar agente', + installToTarget: 'Instalar en agente personalizado', + containerized: 'En contenedor', + containerName: 'Nombre del contenedor', + hostSkillDir: 'Directorio de Skills', + hostSkillDirHelper: + 'Introduzca la ruta del directorio de Skills en el servidor donde está instalado 1Panel.', + containerSkillDir: 'Directorio de Skills en el contenedor', + installMethod: 'Método de instalación', + copy: 'Copiar', + extract: 'Extraer', + postInstallCommand: 'Comando posterior a la instalación', + localSource: 'Carga local', + uploadDragTitle: 'Arrastre el paquete Skill aquí o haga clic para subirlo', + uploadFormatLabel: 'Formatos:', + uploadFormatValue: '.zip, .7z, .tar, .tar.gz', + uploadSizeLabel: 'Tamaño:', + uploadSizeValue: 'No más de 5 MB por archivo', + uploadRequirementLabel: 'Requisito:', + uploadRequirementPrefix: 'El paquete Skill debe contener', + onlySupportedArchive: 'Solo se pueden subir paquetes Skill .zip, .7z, .tar y .tar.gz', + uploadSizeExceeded: 'Un paquete Skill no puede superar los 5 MB', + uploadSuccess: 'Skill subido correctamente y en estado pendiente de revisión.', + offlineNotice: 'El entorno actual está sin conexión. Solo se pueden instalar Skills desde la tienda local.', + publishConfirm: '¿Publicar este Skill? Después se podrá instalar desde la tienda local de Skills.', + disableConfirm: '¿Retirar este Skill? Los Skills ya instalados en agentes no se verán afectados.', + deleteConfirm: + '¿Eliminar este Skill? Esta acción no se puede deshacer. Los Skills ya instalados en agentes no se verán afectados.', + rejectConfirm: '¿Rechazar la revisión de este Skill? Pasará al estado rechazado.', + downloadPublishedOnly: 'Solo se pueden descargar paquetes Skill publicados.', + containerNameRequired: 'Los destinos en contenedor requieren un nombre de contenedor.', + deleteTargetConfirm: + '¿Eliminar este agente personalizado? Los usuarios ya no podrán seleccionar este destino de instalación.', + noInstallTarget: 'No hay agentes personalizados disponibles. Configure primero un destino de instalación.', + postInstallConfirm: + 'Este destino tiene un comando posterior a la instalación. Confirme que el comando es confiable antes de ejecutarlo.', + status: { + pending: 'Pendiente', + approved: 'Aprobado', + published: 'Publicado', + disabled: 'Retirado', + rejected: 'Rechazado', + }, + agent: { + common: 'Común', + }, + risk: { + low: 'Bajo', + medium: 'Medio', + high: 'Alto', + }, + riskType: { + script_file: 'Archivo de script', + dependency_manifest: 'Declaración de dependencias', + external_url: 'URL externa', + environment_variable: 'Variable de entorno', + dockerfile: 'Dockerfile', + command_execution: 'Ejecución de comandos', + remote_script_execution: 'Ejecución de script remoto', + eval_execution: 'Ejecución dinámica', + base64_execution: 'Ejecución tras decodificar Base64', + privileged_command: 'Comando privilegiado', + dangerous_delete: 'Eliminación peligrosa', + world_writable_permission: 'Permiso de alto riesgo', + credential: 'Credencial sospechosa', + sensitive_path: 'Ruta sensible', + suspicious_download_execute: 'Descarga y ejecución sospechosa', + binary_content: 'Contenido binario', + scan_truncated: 'Escaneo truncado', + }, + riskDescriptionText: { + script_file: 'Contiene archivos de script que pueden ejecutar comandos locales.', + dependency_manifest: + 'Contiene declaraciones de dependencias que pueden descargar dependencias externas.', + external_url: 'Contiene URL externas. Verifique que las direcciones de salida sean confiables.', + environment_variable: 'Declara variables de entorno. Verifique su uso y valores predeterminados.', + dockerfile: 'Contiene un Dockerfile que puede definir compilación de imagen o lógica de ejecución.', + command_execution: + 'Contiene llamadas relacionadas con ejecución de comandos. Verifique que sean confiables.', + remote_script_execution: 'Contiene lógica que descarga y ejecuta directamente un script remoto.', + eval_execution: 'Contiene ejecución dinámica mediante eval.', + base64_execution: 'Contiene lógica que ejecuta contenido después de decodificar Base64.', + privileged_command: 'Contiene comandos sudo de elevación de privilegios.', + dangerous_delete: 'Contiene comandos de eliminación peligrosos.', + world_writable_permission: 'Contiene cambios de permisos chmod 777.', + credential: 'Contiene posibles API Key, Token, Secret o Password.', + sensitive_path: 'Contiene acceso a directorios o archivos sensibles.', + suspicious_download_execute: 'Contiene lógica sospechosa de descarga externa o descarga y ejecución.', + binary_content: 'El archivo contiene bytes NUL y puede ocultar contenido ejecutable o binario.', + scan_truncated: + 'El archivo supera el límite de escaneo. El contenido no escaneado requiere revisión manual.', + }, + }, benchmark: { title: 'Benchmark', create: 'Crear', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index 8d121cc07..87351af00 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -723,6 +723,7 @@ const message = { skillsMarketSourceClawhubGlobal: 'ClawHub(グローバル)', skillsMarketSourceClawhubChina: 'ClawHub(中国)', skillsMarketSourceSkillhub: 'SkillHub(Tencent)', + skillsMarketSourceLocalHub: 'ローカル Skill ストア', skillsScore: 'スコア', skillsMarketSourceOfficial: '公式', versionUnsupportedTitle: '現在のバージョンではこの機能はサポートされていません', @@ -1014,6 +1015,129 @@ const message = { importResult: 'Imported {0}, duplicated {1}, invalid {2}', auditLogs: 'Audit Logs', }, + skillsHub: { + title: 'Skill ストア', + upload: 'Skill をアップロード', + importSkill: 'Skill をインポート', + importMethod: 'インポート方法', + repositoryAddress: 'リポジトリアドレス', + repositoryAddressPlaceholder: 'リポジトリアドレスを入力してください', + repositoryRef: 'ブランチまたは Tag', + repositoryRefPlaceholder: '既定は main。失敗した場合は master を試します', + repositoryDownloadPreview: '対象ダウンロード URL:', + packageURL: 'パッケージ URL', + packageURLPlaceholder: '.zip パッケージのアドレスを入力してください', + importSuccess: 'Skill のインポートに成功しました。レビュー待ち状態になりました。', + source: 'ソース', + applicableAgent: '対象エージェント', + riskLevel: 'リスクレベル', + riskReferenceHint: + 'リスクレベルは Skill パッケージの内容から自動検出されるレビュー参考情報です。\n低: 明らかなリスクなし\n中: スクリプト、依存関係、外部 URL、環境変数あり\n高: リモート実行、機密情報、高危険コマンドあり', + riskCheckResult: 'セキュリティチェック結果', + noRiskFound: '明らかなリスクは見つかりませんでした。', + riskFilePath: 'ファイルパス', + riskKeyword: '一致したキーワードまたはルール', + riskDescription: 'リスク説明', + publishHighRiskConfirm: + 'この Skill には高リスク項目があり、スクリプト実行、外部ダウンロード、または機密情報へのアクセスが含まれる可能性があります。それでも公開しますか?', + approve: '承認', + reject: '却下', + publish: '公開', + disable: '非公開', + customTarget: 'カスタムエージェント', + addCustomTarget: 'エージェントを追加', + installToTarget: 'カスタムエージェントにインストール', + containerized: 'コンテナ化', + containerName: 'コンテナ名', + hostSkillDir: 'Skill ディレクトリ', + hostSkillDirHelper: + '1Panel がインストールされているサーバー上の Skill ディレクトリパスを入力してください。', + containerSkillDir: 'コンテナ内 Skill ディレクトリ', + installMethod: 'インストール方法', + copy: 'コピー', + extract: '展開', + postInstallCommand: 'インストール後コマンド', + localSource: 'ローカルアップロード', + uploadDragTitle: 'Skill パッケージをここにドラッグするか、クリックしてアップロード', + uploadFormatLabel: '対応形式:', + uploadFormatValue: '.zip, .7z, .tar, .tar.gz', + uploadSizeLabel: 'ファイルサイズ:', + uploadSizeValue: '1 ファイルあたり 5 MB 以下', + uploadRequirementLabel: 'パッケージ要件:', + uploadRequirementPrefix: 'Skill パッケージには次が含まれている必要があります', + onlySupportedArchive: '.zip、.7z、.tar、.tar.gz 形式の Skill パッケージのみアップロードできます', + uploadSizeExceeded: '1 つの Skill パッケージは 5 MB を超えることはできません', + uploadSuccess: 'Skill のアップロードに成功しました。レビュー待ち状態になりました。', + offlineNotice: + '現在の環境はオフラインです。ローカル Skill ストアからの Skill インストールのみ対応しています。', + publishConfirm: 'この Skill を公開しますか?公開後、ローカル Skill ストアからインストールできます。', + disableConfirm: 'この Skill を非公開にしますか?エージェントにインストール済みの Skill には影響しません。', + deleteConfirm: + 'この Skill を削除しますか?削除後は元に戻せません。エージェントにインストール済みの Skill には影響しません。', + rejectConfirm: 'この Skill のレビューを却下しますか?却下後、Skill は「却下」状態になります。', + downloadPublishedOnly: '公開済みの Skill パッケージのみダウンロードできます。', + containerNameRequired: 'コンテナ化された対象にはコンテナ名が必要です。', + deleteTargetConfirm: + 'このカスタムエージェントを削除しますか?削除後、ユーザーはこのインストール先を選択できなくなります。', + noInstallTarget: '利用可能なカスタムエージェントがありません。先にインストール先を設定してください。', + postInstallConfirm: + 'この対象にはインストール後コマンドが設定されています。実行前に信頼できるコマンドか確認してください。', + status: { + pending: 'レビュー待ち', + approved: '承認済み', + published: '公開済み', + disabled: '非公開', + rejected: '却下', + }, + agent: { + common: '共通', + }, + risk: { + low: '低', + medium: '中', + high: '高', + }, + riskType: { + script_file: 'スクリプトファイル', + dependency_manifest: '依存関係定義', + external_url: '外部 URL', + environment_variable: '環境変数', + dockerfile: 'Dockerfile', + command_execution: 'コマンド実行', + remote_script_execution: 'リモートスクリプト実行', + eval_execution: '動的実行', + base64_execution: 'Base64 デコード実行', + privileged_command: '権限昇格コマンド', + dangerous_delete: '危険な削除', + world_writable_permission: '高リスク権限', + credential: '疑わしい認証情報', + sensitive_path: '機密パス', + suspicious_download_execute: '疑わしいダウンロード実行', + binary_content: 'バイナリ内容', + scan_truncated: 'スキャン内容の切り捨て', + }, + riskDescriptionText: { + script_file: 'ローカルコマンドを実行する可能性があるスクリプトファイルが含まれています。', + dependency_manifest: 'インストール時に外部依存関係を取得する可能性がある依存関係定義が含まれています。', + external_url: '外部 URL が含まれています。接続先が信頼できるか確認してください。', + environment_variable: '環境変数を宣言しています。用途と既定値を確認してください。', + dockerfile: 'イメージ構築または実行ロジックを定義する Dockerfile が含まれています。', + command_execution: 'コマンド実行に関連する呼び出しが含まれています。実行ロジックを確認してください。', + remote_script_execution: 'リモートスクリプトをダウンロードして直接実行するロジックが含まれています。', + eval_execution: 'eval による動的実行が含まれています。', + base64_execution: 'Base64 デコード後に内容を実行するロジックが含まれています。', + privileged_command: 'sudo による権限昇格コマンドが含まれています。', + dangerous_delete: '危険な削除コマンドが含まれています。', + world_writable_permission: 'chmod 777 による権限変更が含まれています。', + credential: 'API Key、Token、Secret、Password の疑いがある情報が含まれています。', + sensitive_path: '機密ディレクトリまたはファイルへのアクセスが含まれています。', + suspicious_download_execute: + '疑わしい外部ダウンロードまたはダウンロード後実行ロジックが含まれています。', + binary_content: + 'ファイルに NUL バイトが含まれており、実行可能内容またはバイナリ内容を隠している可能性があります。', + scan_truncated: 'ファイルがスキャン上限を超えています。未スキャン部分は手動確認が必要です。', + }, + }, benchmark: { title: 'ベンチマーク', create: '作成', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index c774456da..228435912 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -714,6 +714,7 @@ const message = { skillsMarketSourceClawhubGlobal: 'ClawHub (글로벌)', skillsMarketSourceClawhubChina: 'ClawHub (중국)', skillsMarketSourceSkillhub: 'SkillHub (Tencent)', + skillsMarketSourceLocalHub: '로컬 Skill 스토어', skillsScore: '점수', skillsMarketSourceOfficial: '공식', versionUnsupportedTitle: '현재 버전에서는 이 기능을 지원하지 않습니다', @@ -998,6 +999,126 @@ const message = { importResult: 'Imported {0}, duplicated {1}, invalid {2}', auditLogs: 'Audit Logs', }, + skillsHub: { + title: 'Skill 스토어', + upload: 'Skill 업로드', + importSkill: 'Skill 가져오기', + importMethod: '가져오기 방식', + repositoryAddress: '저장소 주소', + repositoryAddressPlaceholder: '저장소 주소를 입력하세요', + repositoryRef: '브랜치 또는 Tag', + repositoryRefPlaceholder: '기본값은 main이며, 실패하면 master를 시도합니다', + repositoryDownloadPreview: '대상 다운로드 URL:', + packageURL: '패키지 URL', + packageURLPlaceholder: '.zip 패키지 주소를 입력하세요', + importSuccess: 'Skill을 성공적으로 가져왔으며 검토 대기 상태가 되었습니다.', + source: '출처', + applicableAgent: '적용 에이전트', + riskLevel: '위험 수준', + riskReferenceHint: + '위험 수준은 Skill 패키지 내용을 기반으로 자동 감지되며 검토 참고용입니다.\n낮음: 명확한 위험 없음\n중간: 스크립트/의존성/외부 URL/환경 변수 포함\n높음: 원격 실행/민감 정보/위험 명령 포함', + riskCheckResult: '보안 검사 결과', + noRiskFound: '명확한 위험을 찾지 못했습니다.', + riskFilePath: '파일 경로', + riskKeyword: '일치한 키워드 또는 규칙', + riskDescription: '위험 설명', + publishHighRiskConfirm: + '이 Skill에는 스크립트 실행, 외부 다운로드 또는 민감한 정보 접근이 포함될 수 있는 고위험 항목이 있습니다. 그래도 게시하시겠습니까?', + approve: '승인', + reject: '거부', + publish: '게시', + disable: '게시 해제', + customTarget: '사용자 지정 에이전트', + addCustomTarget: '에이전트 추가', + installToTarget: '사용자 지정 에이전트에 설치', + containerized: '컨테이너화', + containerName: '컨테이너 이름', + hostSkillDir: 'Skill 디렉터리', + hostSkillDirHelper: '1Panel이 설치된 서버의 Skill 디렉터리 경로를 입력하세요.', + containerSkillDir: '컨테이너 내 Skill 디렉터리', + installMethod: '설치 방식', + copy: '복사', + extract: '압축 해제', + postInstallCommand: '설치 후 명령', + localSource: '로컬 업로드', + uploadDragTitle: 'Skill 패키지를 여기에 끌어다 놓거나 클릭하여 업로드', + uploadFormatLabel: '지원 형식:', + uploadFormatValue: '.zip, .7z, .tar, .tar.gz', + uploadSizeLabel: '파일 크기:', + uploadSizeValue: '파일 하나당 5 MB 이하', + uploadRequirementLabel: '패키지 요구 사항:', + uploadRequirementPrefix: 'Skill 패키지에는 다음이 포함되어야 합니다', + onlySupportedArchive: '.zip, .7z, .tar, .tar.gz 형식의 Skill 패키지만 업로드할 수 있습니다', + uploadSizeExceeded: '단일 Skill 패키지는 5 MB를 초과할 수 없습니다', + uploadSuccess: 'Skill이 성공적으로 업로드되었으며 검토 대기 상태가 되었습니다.', + offlineNotice: '현재 환경은 오프라인입니다. 로컬 Skill 스토어에서만 Skill을 설치할 수 있습니다.', + publishConfirm: '이 Skill을 게시하시겠습니까? 게시 후 로컬 Skill 스토어에서 설치할 수 있습니다.', + disableConfirm: '이 Skill 게시를 해제하시겠습니까? 에이전트에 이미 설치된 Skill에는 영향이 없습니다.', + deleteConfirm: + '이 Skill을 삭제하시겠습니까? 삭제 후 복구할 수 없습니다. 에이전트에 이미 설치된 Skill에는 영향이 없습니다.', + rejectConfirm: '이 Skill 검토를 거부하시겠습니까? 거부 후 Skill은 거부 상태가 됩니다.', + downloadPublishedOnly: '게시된 Skill 패키지만 다운로드할 수 있습니다.', + containerNameRequired: '컨테이너화된 대상에는 컨테이너 이름이 필요합니다.', + deleteTargetConfirm: + '이 사용자 지정 에이전트를 삭제하시겠습니까? 삭제 후 사용자는 이 설치 대상을 선택할 수 없습니다.', + noInstallTarget: '사용 가능한 사용자 지정 에이전트가 없습니다. 먼저 설치 대상을 구성하세요.', + postInstallConfirm: + '이 대상에는 설치 후 명령이 구성되어 있습니다. 실행 전에 신뢰할 수 있는 명령인지 확인하세요.', + status: { + pending: '검토 대기', + approved: '승인됨', + published: '게시됨', + disabled: '게시 해제됨', + rejected: '거부됨', + }, + agent: { + common: '공통', + }, + risk: { + low: '낮음', + medium: '중간', + high: '높음', + }, + riskType: { + script_file: '스크립트 파일', + dependency_manifest: '의존성 선언', + external_url: '외부 URL', + environment_variable: '환경 변수', + dockerfile: 'Dockerfile', + command_execution: '명령 실행', + remote_script_execution: '원격 스크립트 실행', + eval_execution: '동적 실행', + base64_execution: 'Base64 디코딩 실행', + privileged_command: '권한 상승 명령', + dangerous_delete: '위험한 삭제', + world_writable_permission: '고위험 권한', + credential: '의심스러운 인증 정보', + sensitive_path: '민감한 경로', + suspicious_download_execute: '의심스러운 다운로드 실행', + binary_content: '바이너리 콘텐츠', + scan_truncated: '검사 내용 잘림', + }, + riskDescriptionText: { + script_file: '로컬 명령을 실행할 수 있는 스크립트 파일이 포함되어 있습니다.', + dependency_manifest: '설치 시 외부 의존성을 가져올 수 있는 의존성 선언 파일이 포함되어 있습니다.', + external_url: '외부 URL이 포함되어 있습니다. 외부 연결 주소를 신뢰할 수 있는지 확인하세요.', + environment_variable: '환경 변수를 선언합니다. 용도와 기본값을 확인하세요.', + dockerfile: '이미지 빌드 또는 실행 로직을 정의할 수 있는 Dockerfile이 포함되어 있습니다.', + command_execution: '명령 실행 관련 호출이 포함되어 있습니다. 실행 로직을 확인하세요.', + remote_script_execution: '원격 스크립트를 다운로드한 뒤 직접 실행하는 로직이 포함되어 있습니다.', + eval_execution: 'eval을 통한 동적 실행이 포함되어 있습니다.', + base64_execution: 'Base64 디코딩 후 내용을 실행하는 로직이 포함되어 있습니다.', + privileged_command: 'sudo 권한 상승 명령이 포함되어 있습니다.', + dangerous_delete: '위험한 삭제 명령이 포함되어 있습니다.', + world_writable_permission: 'chmod 777 권한 변경이 포함되어 있습니다.', + credential: 'API Key, Token, Secret 또는 Password로 의심되는 정보가 포함되어 있습니다.', + sensitive_path: '민감한 디렉터리 또는 파일에 접근하는 동작이 포함되어 있습니다.', + suspicious_download_execute: '의심스러운 외부 다운로드 또는 다운로드 후 실행 로직이 포함되어 있습니다.', + binary_content: + '파일에 NUL 바이트가 포함되어 실행 가능 콘텐츠 또는 바이너리 콘텐츠를 숨길 수 있습니다.', + scan_truncated: '파일이 검사 한도를 초과했습니다. 검사되지 않은 내용은 수동 확인이 필요합니다.', + }, + }, benchmark: { title: '벤치마크', create: '생성', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index 00766eb18..34e3f608e 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -730,6 +730,7 @@ const message = { skillsMarketSourceClawhubGlobal: 'ClawHub (Global)', skillsMarketSourceClawhubChina: 'ClawHub (China)', skillsMarketSourceSkillhub: 'SkillHub (Tencent)', + skillsMarketSourceLocalHub: 'Kedai Skill tempatan', skillsScore: 'Skor', skillsMarketSourceOfficial: 'Rasmi', versionUnsupportedTitle: 'Ciri ini tidak disokong dalam versi semasa', @@ -1023,6 +1024,128 @@ const message = { importResult: 'Imported {0}, duplicated {1}, invalid {2}', auditLogs: 'Audit Logs', }, + skillsHub: { + title: 'Kedai Skill', + upload: 'Muat naik Skill', + importSkill: 'Import Skill', + importMethod: 'Kaedah import', + repositoryAddress: 'Alamat repositori', + repositoryAddressPlaceholder: 'Masukkan alamat repositori', + repositoryRef: 'Cawangan atau Tag', + repositoryRefPlaceholder: 'Lalai main; jika gagal, akan mencuba master', + repositoryDownloadPreview: 'URL muat turun sasaran:', + packageURL: 'URL pakej', + packageURLPlaceholder: 'Masukkan alamat pakej .zip', + importSuccess: 'Skill berjaya diimport dan kini menunggu semakan.', + source: 'Sumber', + applicableAgent: 'Agen berkaitan', + riskLevel: 'Tahap risiko', + riskReferenceHint: + 'Tahap risiko dijana secara automatik daripada kandungan pakej Skill dan hanya untuk rujukan semakan.\nRendah: tiada risiko jelas\nSederhana: skrip, kebergantungan, URL luaran atau pemboleh ubah persekitaran\nTinggi: pelaksanaan jauh, maklumat sensitif atau arahan berbahaya', + riskCheckResult: 'Keputusan pemeriksaan keselamatan', + noRiskFound: 'Tiada risiko jelas ditemui.', + riskFilePath: 'Laluan fail', + riskKeyword: 'Kata kunci atau peraturan sepadan', + riskDescription: 'Penerangan risiko', + publishHighRiskConfirm: + 'Skill ini mempunyai item berisiko tinggi dan mungkin mengandungi pelaksanaan skrip, muat turun luaran atau akses maklumat sensitif. Teruskan terbitkan?', + approve: 'Luluskan', + reject: 'Tolak', + publish: 'Terbitkan', + disable: 'Nyahterbit', + customTarget: 'Agen tersuai', + addCustomTarget: 'Tambah agen', + installToTarget: 'Pasang ke agen tersuai', + containerized: 'Berkontena', + containerName: 'Nama kontena', + hostSkillDir: 'Direktori Skill', + hostSkillDirHelper: 'Masukkan laluan direktori Skill pada pelayan tempat 1Panel dipasang.', + containerSkillDir: 'Direktori Skill dalam kontena', + installMethod: 'Kaedah pemasangan', + copy: 'Salin', + extract: 'Ekstrak', + postInstallCommand: 'Arahan selepas pemasangan', + localSource: 'Muat naik tempatan', + uploadDragTitle: 'Seret pakej Skill ke sini, atau klik untuk memuat naik', + uploadFormatLabel: 'Format:', + uploadFormatValue: '.zip, .7z, .tar, .tar.gz', + uploadSizeLabel: 'Saiz fail:', + uploadSizeValue: 'Tidak melebihi 5 MB bagi setiap fail', + uploadRequirementLabel: 'Keperluan pakej:', + uploadRequirementPrefix: 'Pakej Skill mesti mengandungi', + onlySupportedArchive: 'Hanya pakej Skill .zip, .7z, .tar dan .tar.gz disokong', + uploadSizeExceeded: 'Satu pakej Skill tidak boleh melebihi 5 MB', + uploadSuccess: 'Skill berjaya dimuat naik dan kini menunggu semakan.', + offlineNotice: 'Persekitaran semasa di luar talian. Hanya Skill daripada kedai tempatan boleh dipasang.', + publishConfirm: 'Terbitkan Skill ini? Selepas diterbitkan, ia boleh dipasang dari kedai Skill tempatan.', + disableConfirm: 'Nyahterbit Skill ini? Skill yang telah dipasang pada agen tidak akan terjejas.', + deleteConfirm: + 'Padam Skill ini? Tindakan ini tidak boleh dipulihkan. Skill yang telah dipasang pada agen tidak akan terjejas.', + rejectConfirm: 'Tolak semakan Skill ini? Skill akan masuk ke status ditolak.', + downloadPublishedOnly: 'Hanya pakej Skill yang telah diterbitkan boleh dimuat turun.', + containerNameRequired: 'Sasaran berkontena memerlukan nama kontena.', + deleteTargetConfirm: 'Padam agen tersuai ini? Pengguna tidak lagi boleh memilih sasaran pemasangan ini.', + noInstallTarget: 'Tiada agen tersuai tersedia. Konfigurasikan sasaran pemasangan dahulu.', + postInstallConfirm: + 'Sasaran ini mempunyai arahan selepas pemasangan. Sahkan arahan tersebut dipercayai sebelum dijalankan.', + status: { + pending: 'Menunggu semakan', + approved: 'Diluluskan', + published: 'Diterbitkan', + disabled: 'Dinyahterbit', + rejected: 'Ditolak', + }, + agent: { + common: 'Umum', + }, + risk: { + low: 'Rendah', + medium: 'Sederhana', + high: 'Tinggi', + }, + riskType: { + script_file: 'Fail skrip', + dependency_manifest: 'Manifest kebergantungan', + external_url: 'URL luaran', + environment_variable: 'Pemboleh ubah persekitaran', + dockerfile: 'Dockerfile', + command_execution: 'Pelaksanaan arahan', + remote_script_execution: 'Pelaksanaan skrip jauh', + eval_execution: 'Pelaksanaan dinamik', + base64_execution: 'Pelaksanaan dekod Base64', + privileged_command: 'Arahan berkeistimewaan', + dangerous_delete: 'Pemadaman berbahaya', + world_writable_permission: 'Kebenaran berisiko tinggi', + credential: 'Kredensial disyaki', + sensitive_path: 'Laluan sensitif', + suspicious_download_execute: 'Muat turun dan pelaksanaan mencurigakan', + binary_content: 'Kandungan binari', + scan_truncated: 'Imbasan dipotong', + }, + riskDescriptionText: { + script_file: 'Mengandungi fail skrip yang mungkin menjalankan arahan tempatan.', + dependency_manifest: + 'Mengandungi manifest kebergantungan yang mungkin memuat turun kebergantungan luaran.', + external_url: 'Mengandungi URL luaran. Sahkan alamat keluar tersebut dipercayai.', + environment_variable: 'Mengisytiharkan pemboleh ubah persekitaran. Sahkan tujuan dan nilai lalainya.', + dockerfile: 'Mengandungi Dockerfile yang mungkin mentakrifkan binaan imej atau logik runtime.', + command_execution: + 'Mengandungi panggilan berkaitan pelaksanaan arahan. Sahkan logik tersebut dipercayai.', + remote_script_execution: 'Mengandungi logik yang memuat turun dan terus menjalankan skrip jauh.', + eval_execution: 'Mengandungi pelaksanaan dinamik melalui eval.', + base64_execution: 'Mengandungi logik yang menjalankan kandungan selepas penyahkodan Base64.', + privileged_command: 'Mengandungi arahan sudo untuk peningkatan keistimewaan.', + dangerous_delete: 'Mengandungi arahan pemadaman berbahaya.', + world_writable_permission: 'Mengandungi perubahan kebenaran chmod 777.', + credential: 'Mengandungi API Key, Token, Secret atau Password yang disyaki.', + sensitive_path: 'Mengandungi akses kepada direktori atau fail sensitif.', + suspicious_download_execute: + 'Mengandungi logik muat turun luaran atau muat turun dan laksana yang mencurigakan.', + binary_content: + 'Fail mengandungi bait NUL dan mungkin menyembunyikan kandungan boleh laksana atau binari.', + scan_truncated: 'Fail melebihi had imbasan. Kandungan yang tidak diimbas masih perlu disemak manual.', + }, + }, benchmark: { title: 'Penanda Aras', create: 'Cipta', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index 63155060f..8210a372b 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -724,6 +724,7 @@ const message = { skillsMarketSourceClawhubGlobal: 'ClawHub (Global)', skillsMarketSourceClawhubChina: 'ClawHub (China)', skillsMarketSourceSkillhub: 'SkillHub (Tencent)', + skillsMarketSourceLocalHub: 'Loja local de Skills', skillsScore: 'Pontuação', skillsMarketSourceOfficial: 'Oficial', versionUnsupportedTitle: 'Este recurso não é compatível com a versão atual', @@ -1019,6 +1020,128 @@ const message = { importResult: 'Imported {0}, duplicated {1}, invalid {2}', auditLogs: 'Audit Logs', }, + skillsHub: { + title: 'Loja de Skills', + upload: 'Enviar Skill', + importSkill: 'Importar Skill', + importMethod: 'Método de importação', + repositoryAddress: 'Endereço do repositório', + repositoryAddressPlaceholder: 'Informe o endereço do repositório', + repositoryRef: 'Branch ou Tag', + repositoryRefPlaceholder: 'Padrão main; se falhar, tentará master', + repositoryDownloadPreview: 'URL de download de destino:', + packageURL: 'URL do pacote', + packageURLPlaceholder: 'Informe o endereço do pacote .zip', + importSuccess: 'Skill importada com sucesso e agora está pendente de revisão.', + source: 'Fonte', + applicableAgent: 'Agente aplicável', + riskLevel: 'Nível de risco', + riskReferenceHint: + 'O nível de risco é gerado automaticamente a partir do conteúdo do pacote Skill e serve apenas como referência.\nBaixo: nenhum risco evidente\nMédio: scripts, dependências, URLs externas ou variáveis de ambiente\nAlto: execução remota, informações sensíveis ou comandos perigosos', + riskCheckResult: 'Resultado da verificação de segurança', + noRiskFound: 'Nenhum risco evidente encontrado.', + riskFilePath: 'Caminho do arquivo', + riskKeyword: 'Palavra-chave ou regra correspondente', + riskDescription: 'Descrição do risco', + publishHighRiskConfirm: + 'Esta Skill possui itens de alto risco e pode conter execução de scripts, downloads externos ou acesso a informações sensíveis. Publicar mesmo assim?', + approve: 'Aprovar', + reject: 'Rejeitar', + publish: 'Publicar', + disable: 'Despublicar', + customTarget: 'Agente personalizado', + addCustomTarget: 'Adicionar agente', + installToTarget: 'Instalar no agente personalizado', + containerized: 'Conteinerizado', + containerName: 'Nome do contêiner', + hostSkillDir: 'Diretório de Skills', + hostSkillDirHelper: 'Informe o caminho do diretório de Skills no servidor onde o 1Panel está instalado.', + containerSkillDir: 'Diretório de Skills no contêiner', + installMethod: 'Método de instalação', + copy: 'Copiar', + extract: 'Extrair', + postInstallCommand: 'Comando pós-instalação', + localSource: 'Upload local', + uploadDragTitle: 'Arraste o pacote Skill aqui ou clique para enviar', + uploadFormatLabel: 'Formatos:', + uploadFormatValue: '.zip, .7z, .tar, .tar.gz', + uploadSizeLabel: 'Tamanho:', + uploadSizeValue: 'Até 5 MB por arquivo', + uploadRequirementLabel: 'Requisito:', + uploadRequirementPrefix: 'O pacote Skill deve conter', + onlySupportedArchive: 'Só é possível enviar pacotes Skill .zip, .7z, .tar e .tar.gz', + uploadSizeExceeded: 'Um pacote Skill não pode exceder 5 MB', + uploadSuccess: 'Skill enviada com sucesso e agora está pendente de revisão.', + offlineNotice: 'O ambiente atual está offline. Apenas Skills da loja local podem ser instaladas.', + publishConfirm: 'Publicar esta Skill? Depois disso, ela poderá ser instalada pela loja local de Skills.', + disableConfirm: 'Despublicar esta Skill? Skills já instaladas em agentes não serão afetadas.', + deleteConfirm: + 'Excluir esta Skill? Esta ação não pode ser desfeita. Skills já instaladas em agentes não serão afetadas.', + rejectConfirm: 'Rejeitar a revisão desta Skill? Ela passará para o status rejeitado.', + downloadPublishedOnly: 'Somente pacotes Skill publicados podem ser baixados.', + containerNameRequired: 'Destinos conteinerizados exigem um nome de contêiner.', + deleteTargetConfirm: + 'Excluir este agente personalizado? Os usuários não poderão mais selecionar este destino de instalação.', + noInstallTarget: 'Nenhum agente personalizado disponível. Configure primeiro um destino de instalação.', + postInstallConfirm: + 'Este destino tem um comando pós-instalação. Confirme que o comando é confiável antes de executá-lo.', + status: { + pending: 'Pendente', + approved: 'Aprovado', + published: 'Publicado', + disabled: 'Despublicado', + rejected: 'Rejeitado', + }, + agent: { + common: 'Comum', + }, + risk: { + low: 'Baixo', + medium: 'Médio', + high: 'Alto', + }, + riskType: { + script_file: 'Arquivo de script', + dependency_manifest: 'Manifesto de dependências', + external_url: 'URL externa', + environment_variable: 'Variável de ambiente', + dockerfile: 'Dockerfile', + command_execution: 'Execução de comando', + remote_script_execution: 'Execução de script remoto', + eval_execution: 'Execução dinâmica', + base64_execution: 'Execução após decodificação Base64', + privileged_command: 'Comando privilegiado', + dangerous_delete: 'Exclusão perigosa', + world_writable_permission: 'Permissão de alto risco', + credential: 'Credencial suspeita', + sensitive_path: 'Caminho sensível', + suspicious_download_execute: 'Download e execução suspeitos', + binary_content: 'Conteúdo binário', + scan_truncated: 'Verificação truncada', + }, + riskDescriptionText: { + script_file: 'Contém arquivos de script que podem executar comandos locais.', + dependency_manifest: + 'Contém manifestos de dependências que podem baixar dependências externas durante a instalação.', + external_url: 'Contém URLs externas. Verifique se os endereços de saída são confiáveis.', + environment_variable: 'Declara variáveis de ambiente. Verifique sua finalidade e valores padrão.', + dockerfile: 'Contém um Dockerfile que pode definir build de imagem ou lógica de execução.', + command_execution: + 'Contém chamadas relacionadas à execução de comandos. Verifique se a lógica é confiável.', + remote_script_execution: 'Contém lógica que baixa e executa diretamente um script remoto.', + eval_execution: 'Contém execução dinâmica por eval.', + base64_execution: 'Contém lógica que executa conteúdo após decodificação Base64.', + privileged_command: 'Contém comandos sudo de elevação de privilégio.', + dangerous_delete: 'Contém comandos de exclusão perigosos.', + world_writable_permission: 'Contém alterações de permissão chmod 777.', + credential: 'Contém possível API Key, Token, Secret ou Password.', + sensitive_path: 'Contém acesso a diretórios ou arquivos sensíveis.', + suspicious_download_execute: 'Contém lógica suspeita de download externo ou download e execução.', + binary_content: 'O arquivo contém bytes NUL e pode ocultar conteúdo executável ou binário.', + scan_truncated: + 'O arquivo excede o limite de verificação. O conteúdo não verificado ainda requer revisão manual.', + }, + }, benchmark: { title: 'Benchmark', create: 'Criar', diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index aee4ab7d7..0ca355ef3 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -721,6 +721,7 @@ const message = { skillsMarketSourceClawhubGlobal: 'ClawHub (Глобальный)', skillsMarketSourceClawhubChina: 'ClawHub (Китай)', skillsMarketSourceSkillhub: 'SkillHub (Tencent)', + skillsMarketSourceLocalHub: 'Локальный магазин Skills', skillsScore: 'Оценка', skillsMarketSourceOfficial: 'Официальный', versionUnsupportedTitle: 'Эта функция не поддерживается в текущей версии', @@ -1013,6 +1014,128 @@ const message = { importResult: 'Imported {0}, duplicated {1}, invalid {2}', auditLogs: 'Audit Logs', }, + skillsHub: { + title: 'Магазин Skills', + upload: 'Загрузить Skill', + importSkill: 'Импортировать Skill', + importMethod: 'Способ импорта', + repositoryAddress: 'Адрес репозитория', + repositoryAddressPlaceholder: 'Введите адрес репозитория', + repositoryRef: 'Ветка или Tag', + repositoryRefPlaceholder: 'По умолчанию main; при ошибке будет попытка master', + repositoryDownloadPreview: 'Целевой URL загрузки:', + packageURL: 'URL пакета', + packageURLPlaceholder: 'Введите адрес пакета .zip', + importSuccess: 'Skill успешно импортирован и ожидает проверки.', + source: 'Источник', + applicableAgent: 'Применимый агент', + riskLevel: 'Уровень риска', + riskReferenceHint: + 'Уровень риска автоматически определяется по содержимому пакета Skill и служит только справкой для проверки.\nНизкий: явные риски не найдены\nСредний: скрипты, зависимости, внешние URL или переменные окружения\nВысокий: удаленное выполнение, чувствительные данные или опасные команды', + riskCheckResult: 'Результат проверки безопасности', + noRiskFound: 'Явные риски не обнаружены.', + riskFilePath: 'Путь к файлу', + riskKeyword: 'Совпавшее ключевое слово или правило', + riskDescription: 'Описание риска', + publishHighRiskConfirm: + 'Этот Skill содержит элементы высокого риска: выполнение скриптов, внешние загрузки или доступ к чувствительной информации. Все равно опубликовать?', + approve: 'Одобрить', + reject: 'Отклонить', + publish: 'Опубликовать', + disable: 'Снять с публикации', + customTarget: 'Пользовательский агент', + addCustomTarget: 'Добавить агента', + installToTarget: 'Установить в пользовательский агент', + containerized: 'В контейнере', + containerName: 'Имя контейнера', + hostSkillDir: 'Каталог Skills', + hostSkillDirHelper: 'Введите путь к каталогу Skills на сервере, где установлен 1Panel.', + containerSkillDir: 'Каталог Skills в контейнере', + installMethod: 'Способ установки', + copy: 'Копировать', + extract: 'Распаковать', + postInstallCommand: 'Команда после установки', + localSource: 'Локальная загрузка', + uploadDragTitle: 'Перетащите пакет Skill сюда или нажмите для загрузки', + uploadFormatLabel: 'Форматы:', + uploadFormatValue: '.zip, .7z, .tar, .tar.gz', + uploadSizeLabel: 'Размер файла:', + uploadSizeValue: 'Не более 5 MB на файл', + uploadRequirementLabel: 'Требование:', + uploadRequirementPrefix: 'Пакет Skill должен содержать', + onlySupportedArchive: 'Можно загружать только пакеты Skill форматов .zip, .7z, .tar и .tar.gz', + uploadSizeExceeded: 'Один пакет Skill не должен превышать 5 MB', + uploadSuccess: 'Skill успешно загружен и ожидает проверки.', + offlineNotice: 'Текущая среда офлайн. Можно устанавливать только Skills из локального магазина.', + publishConfirm: + 'Опубликовать этот Skill? После публикации его можно установить из локального магазина Skills.', + disableConfirm: 'Снять этот Skill с публикации? Уже установленные в агентах Skills не будут затронуты.', + deleteConfirm: + 'Удалить этот Skill? Это действие нельзя отменить. Уже установленные в агентах Skills не будут затронуты.', + rejectConfirm: 'Отклонить проверку этого Skill? Он перейдет в статус отклоненного.', + downloadPublishedOnly: 'Можно скачать только опубликованные пакеты Skill.', + containerNameRequired: 'Для контейнерных целей требуется имя контейнера.', + deleteTargetConfirm: + 'Удалить этого пользовательского агента? Пользователи больше не смогут выбрать эту цель установки.', + noInstallTarget: 'Нет доступных пользовательских агентов. Сначала настройте цель установки.', + postInstallConfirm: + 'Для этой цели настроена команда после установки. Перед выполнением убедитесь, что команда надежна.', + status: { + pending: 'Ожидает проверки', + approved: 'Одобрен', + published: 'Опубликован', + disabled: 'Снят с публикации', + rejected: 'Отклонен', + }, + agent: { + common: 'Общий', + }, + risk: { + low: 'Низкий', + medium: 'Средний', + high: 'Высокий', + }, + riskType: { + script_file: 'Файл скрипта', + dependency_manifest: 'Манифест зависимостей', + external_url: 'Внешний URL', + environment_variable: 'Переменная окружения', + dockerfile: 'Dockerfile', + command_execution: 'Выполнение команды', + remote_script_execution: 'Выполнение удаленного скрипта', + eval_execution: 'Динамическое выполнение', + base64_execution: 'Выполнение после декодирования Base64', + privileged_command: 'Привилегированная команда', + dangerous_delete: 'Опасное удаление', + world_writable_permission: 'Права высокого риска', + credential: 'Подозрительные учетные данные', + sensitive_path: 'Чувствительный путь', + suspicious_download_execute: 'Подозрительная загрузка и выполнение', + binary_content: 'Двоичное содержимое', + scan_truncated: 'Проверка усечена', + }, + riskDescriptionText: { + script_file: 'Содержит файлы скриптов, которые могут выполнять локальные команды.', + dependency_manifest: 'Содержит манифесты зависимостей, которые могут загружать внешние зависимости.', + external_url: 'Содержит внешние URL. Проверьте, что исходящие адреса надежны.', + environment_variable: + 'Объявляет переменные окружения. Проверьте их назначение и значения по умолчанию.', + dockerfile: 'Содержит Dockerfile, который может задавать сборку образа или логику выполнения.', + command_execution: 'Содержит вызовы, связанные с выполнением команд. Проверьте эту логику.', + remote_script_execution: 'Содержит логику загрузки и прямого выполнения удаленного скрипта.', + eval_execution: 'Содержит динамическое выполнение через eval.', + base64_execution: 'Содержит логику выполнения содержимого после декодирования Base64.', + privileged_command: 'Содержит команды sudo для повышения привилегий.', + dangerous_delete: 'Содержит опасные команды удаления.', + world_writable_permission: 'Содержит изменение прав chmod 777.', + credential: 'Содержит возможный API Key, Token, Secret или Password.', + sensitive_path: 'Содержит доступ к чувствительным каталогам или файлам.', + suspicious_download_execute: + 'Содержит подозрительную внешнюю загрузку или логику загрузки и выполнения.', + binary_content: 'Файл содержит байты NUL и может скрывать исполняемое или двоичное содержимое.', + scan_truncated: 'Файл превышает лимит проверки. Непроверенное содержимое требует ручной проверки.', + }, + }, benchmark: { title: 'Бенчмарк', create: 'Создать', diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index e89357980..1ff4925e6 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -726,6 +726,7 @@ const message = { skillsMarketSourceClawhubGlobal: 'ClawHub (Global)', skillsMarketSourceClawhubChina: 'ClawHub (Çin)', skillsMarketSourceSkillhub: 'SkillHub (Tencent)', + skillsMarketSourceLocalHub: 'Yerel Skill Mağazası', skillsScore: 'Puan', skillsMarketSourceOfficial: 'Resmi', versionUnsupportedTitle: 'Bu özellik mevcut sürümde desteklenmiyor', @@ -1021,6 +1022,126 @@ const message = { importResult: 'Imported {0}, duplicated {1}, invalid {2}', auditLogs: 'Audit Logs', }, + skillsHub: { + title: 'Skill Mağazası', + upload: 'Skill yükle', + importSkill: 'Skill içe aktar', + importMethod: 'İçe aktarma yöntemi', + repositoryAddress: 'Depo adresi', + repositoryAddressPlaceholder: 'Depo adresini girin', + repositoryRef: 'Dal veya Tag', + repositoryRefPlaceholder: 'Varsayılan main; başarısız olursa master denenir', + repositoryDownloadPreview: 'Hedef indirme URLsi:', + packageURL: 'Paket URLsi', + packageURLPlaceholder: '.zip paket adresini girin', + importSuccess: 'Skill başarıyla içe aktarıldı ve inceleme bekliyor.', + source: 'Kaynak', + applicableAgent: 'Uygulanabilir ajan', + riskLevel: 'Risk düzeyi', + riskReferenceHint: + 'Risk düzeyi Skill paketi içeriğinden otomatik oluşturulur ve yalnızca inceleme referansıdır.\nDüşük: belirgin risk yok\nOrta: script, bağımlılık, harici URL veya ortam değişkeni\nYüksek: uzak çalıştırma, hassas bilgi veya tehlikeli komut', + riskCheckResult: 'Güvenlik kontrolü sonucu', + noRiskFound: 'Belirgin bir risk bulunamadı.', + riskFilePath: 'Dosya yolu', + riskKeyword: 'Eşleşen anahtar kelime veya kural', + riskDescription: 'Risk açıklaması', + publishHighRiskConfirm: + 'Bu Skill yüksek riskli öğeler içeriyor olabilir: script çalıştırma, harici indirme veya hassas bilgi erişimi. Yine de yayınlansın mı?', + approve: 'Onayla', + reject: 'Reddet', + publish: 'Yayınla', + disable: 'Yayından kaldır', + customTarget: 'Özel ajan', + addCustomTarget: 'Ajan ekle', + installToTarget: 'Özel ajana kur', + containerized: 'Konteynerli', + containerName: 'Konteyner adı', + hostSkillDir: 'Skill dizini', + hostSkillDirHelper: '1Panelin kurulu olduğu sunucudaki Skill dizini yolunu girin.', + containerSkillDir: 'Konteyner içi Skill dizini', + installMethod: 'Kurulum yöntemi', + copy: 'Kopyala', + extract: 'Çıkart', + postInstallCommand: 'Kurulum sonrası komut', + localSource: 'Yerel yükleme', + uploadDragTitle: 'Skill paketini buraya sürükleyin veya yüklemek için tıklayın', + uploadFormatLabel: 'Biçimler:', + uploadFormatValue: '.zip, .7z, .tar, .tar.gz', + uploadSizeLabel: 'Dosya boyutu:', + uploadSizeValue: 'Dosya başına en fazla 5 MB', + uploadRequirementLabel: 'Paket gereksinimi:', + uploadRequirementPrefix: 'Skill paketi şunu içermelidir', + onlySupportedArchive: 'Yalnızca .zip, .7z, .tar ve .tar.gz Skill paketleri yüklenebilir', + uploadSizeExceeded: 'Tek bir Skill paketi 5 MBı aşamaz', + uploadSuccess: 'Skill başarıyla yüklendi ve inceleme bekliyor.', + offlineNotice: 'Geçerli ortam çevrimdışı. Yalnızca yerel Skill Mağazasından Skill kurulabilir.', + publishConfirm: + 'Bu Skilli yayınlamak istiyor musunuz? Yayınlandıktan sonra yerel Skill Mağazasından kurulabilir.', + disableConfirm: 'Bu Skilli yayından kaldırmak istiyor musunuz? Ajanlara kurulu Skilller etkilenmez.', + deleteConfirm: + 'Bu Skilli silmek istiyor musunuz? Bu işlem geri alınamaz. Ajanlara kurulu Skilller etkilenmez.', + rejectConfirm: 'Bu Skill incelemesini reddetmek istiyor musunuz? Skill reddedildi durumuna geçer.', + downloadPublishedOnly: 'Yalnızca yayınlanmış Skill paketleri indirilebilir.', + containerNameRequired: 'Konteynerli hedefler için konteyner adı gereklidir.', + deleteTargetConfirm: 'Bu özel ajan silinsin mi? Kullanıcılar artık bu kurulum hedefini seçemez.', + noInstallTarget: 'Kullanılabilir özel ajan yok. Önce bir kurulum hedefi yapılandırın.', + postInstallConfirm: + 'Bu hedefte kurulum sonrası komut yapılandırılmış. Çalıştırmadan önce komutun güvenilir olduğunu doğrulayın.', + status: { + pending: 'İnceleme bekliyor', + approved: 'Onaylandı', + published: 'Yayınlandı', + disabled: 'Yayından kaldırıldı', + rejected: 'Reddedildi', + }, + agent: { + common: 'Ortak', + }, + risk: { + low: 'Düşük', + medium: 'Orta', + high: 'Yüksek', + }, + riskType: { + script_file: 'Script dosyası', + dependency_manifest: 'Bağımlılık bildirimi', + external_url: 'Harici URL', + environment_variable: 'Ortam değişkeni', + dockerfile: 'Dockerfile', + command_execution: 'Komut çalıştırma', + remote_script_execution: 'Uzak script çalıştırma', + eval_execution: 'Dinamik çalıştırma', + base64_execution: 'Base64 çözme sonrası çalıştırma', + privileged_command: 'Ayrıcalıklı komut', + dangerous_delete: 'Tehlikeli silme', + world_writable_permission: 'Yüksek riskli izin', + credential: 'Şüpheli kimlik bilgisi', + sensitive_path: 'Hassas yol', + suspicious_download_execute: 'Şüpheli indirme ve çalıştırma', + binary_content: 'İkili içerik', + scan_truncated: 'Tarama kesildi', + }, + riskDescriptionText: { + script_file: 'Yerel komutları çalıştırabilecek script dosyaları içerir.', + dependency_manifest: + 'Kurulum sırasında harici bağımlılıkları indirebilecek bağımlılık bildirimleri içerir.', + external_url: 'Harici URLler içerir. Dış bağlantı adreslerinin güvenilir olduğunu doğrulayın.', + environment_variable: 'Ortam değişkenleri tanımlar. Amaçlarını ve varsayılan değerlerini doğrulayın.', + dockerfile: 'İmaj oluşturma veya çalışma zamanı mantığını tanımlayabilecek Dockerfile içerir.', + command_execution: 'Komut çalıştırma ile ilişkili çağrılar içerir. Çalıştırma mantığını doğrulayın.', + remote_script_execution: 'Uzak bir scripti indirip doğrudan çalıştıran mantık içerir.', + eval_execution: 'eval ile dinamik çalıştırma içerir.', + base64_execution: 'Base64 çözümlemesinden sonra içeriği çalıştıran mantık içerir.', + privileged_command: 'sudo ayrıcalık yükseltme komutları içerir.', + dangerous_delete: 'Tehlikeli silme komutları içerir.', + world_writable_permission: 'chmod 777 izin değişiklikleri içerir.', + credential: 'Şüpheli API Key, Token, Secret veya Password içerir.', + sensitive_path: 'Hassas dizinlere veya dosyalara erişim içerir.', + suspicious_download_execute: 'Şüpheli harici indirme veya indirip çalıştırma mantığı içerir.', + binary_content: 'Dosya NUL baytları içerir ve çalıştırılabilir veya ikili içeriği gizleyebilir.', + scan_truncated: 'Dosya tarama sınırını aşıyor. Taranmayan içerik manuel inceleme gerektirir.', + }, + }, benchmark: { title: 'Benchmark', create: 'Oluştur', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index 0d9b42911..6c0329d6c 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -687,6 +687,7 @@ const message = { skillsMarketSourceClawhubGlobal: 'ClawHub(全球)', skillsMarketSourceClawhubChina: 'ClawHub(中國)', skillsMarketSourceSkillhub: 'SkillHub(騰訊)', + skillsMarketSourceLocalHub: '本地 Skill 商店', skillsScore: '評分', skillsMarketSourceOfficial: '官方', versionUnsupportedTitle: '當前版本暫不支援該功能', @@ -958,6 +959,121 @@ const message = { importResult: '匯入成功 {0} 條,重複 {1} 條,無效 {2} 條', auditLogs: '審計日誌', }, + skillsHub: { + title: 'Skills Hub', + upload: '上傳 Skill', + importSkill: '匯入 Skill', + importMethod: '匯入方式', + repositoryAddress: '倉庫地址', + repositoryAddressPlaceholder: '請輸入倉庫地址', + repositoryRef: '分支或 Tag', + repositoryRefPlaceholder: '預設為 main,失敗後將嘗試 master', + repositoryDownloadPreview: '目標下載地址:', + packageURL: '壓縮包 URL', + packageURLPlaceholder: '請輸入 .zip 壓縮包地址', + importSuccess: 'Skill 匯入成功,已進入待審核狀態。', + source: '來源', + applicableAgent: '適用智能體', + riskLevel: '風險等級', + riskReferenceHint: + '風險等級由系統根據 Skill 包內容自動檢測生成,僅作為審核參考,不代表絕對安全。\n低:未發現明顯風險\n中:包含腳本、依賴、外部 URL 或環境變數\n高:包含遠端執行、敏感資訊或高危命令', + riskCheckResult: '安全檢查結果', + noRiskFound: '暫未發現明顯風險。', + riskFilePath: '檔案路徑', + riskKeyword: '命中的關鍵字或規則', + riskDescription: '風險說明', + publishHighRiskConfirm: '該 Skill 存在高風險項,可能包含腳本執行、外部下載或敏感資訊存取。確認仍要上架嗎?', + approve: '審核通過', + reject: '審核駁回', + publish: '上架', + disable: '下架', + customTarget: '自訂智能體', + addCustomTarget: '添加智能體', + installToTarget: '安裝到自訂智能體', + containerized: '容器化', + containerName: '容器名稱', + hostSkillDir: 'Skill 目錄', + hostSkillDirHelper: '請填寫 1Panel 所在伺服器上的 Skill 目錄路徑。', + containerSkillDir: '容器內 Skill 目錄', + installMethod: '安裝方式', + copy: '複製', + extract: '解壓', + postInstallCommand: '安裝後命令', + localSource: '本地上傳', + uploadDragTitle: '拖拽 Skill 包到此處,或點擊上傳', + uploadFormatLabel: '支援格式:', + uploadFormatValue: '.zip、.7z、.tar、.tar.gz', + uploadSizeLabel: '檔案大小:', + uploadSizeValue: '單個檔案不超過 5 MB', + uploadRequirementLabel: '包內要求:', + uploadRequirementPrefix: 'Skill 包內必須包含', + onlySupportedArchive: '僅支援上傳 .zip、.7z、.tar、.tar.gz 格式的 Skill 包', + uploadSizeExceeded: '單個 Skill 包不能超過 5 MB', + uploadSuccess: 'Skill 上傳成功,已進入待審核狀態。', + offlineNotice: '當前為離線環境,僅支援從本地 Skill 商店安裝技能。', + publishConfirm: '確認上架該 Skill?上架後可在智能體中從本地 Skill 商店安裝。', + disableConfirm: '確認下架該 Skill?已安裝到智能體中的 Skill 不受影響。', + deleteConfirm: '確認刪除該 Skill?刪除後不可恢復,已安裝到智能體中的 Skill 不受影響。', + rejectConfirm: '確認駁回該 Skill 的審核?駁回後 Skill 將進入「審核未通過」狀態。', + downloadPublishedOnly: '僅支援下載已上架的 Skill 包。', + containerNameRequired: '容器化目標需要填寫容器名稱。', + deleteTargetConfirm: '確認刪除該自訂智能體?刪除後使用者不能繼續選擇該安裝目標。', + noInstallTarget: '暫無可用的自訂智能體,請先設定安裝目標。', + postInstallConfirm: '該目標設定了安裝後命令,執行前請確認該命令可信。', + status: { + pending: '待審核', + approved: '已審核', + published: '已上架', + disabled: '已下架', + rejected: '審核未通過', + }, + agent: { + common: '通用', + }, + risk: { + low: '低', + medium: '中', + high: '高', + }, + riskType: { + script_file: '腳本檔案', + dependency_manifest: '依賴聲明', + external_url: '外部 URL', + environment_variable: '環境變數', + dockerfile: 'Dockerfile', + command_execution: '命令執行', + remote_script_execution: '遠端腳本執行', + eval_execution: '動態執行', + base64_execution: 'Base64 解碼執行', + privileged_command: '提權命令', + dangerous_delete: '危險刪除', + world_writable_permission: '高危權限', + credential: '疑似密鑰', + sensitive_path: '敏感路徑', + suspicious_download_execute: '可疑下載執行', + binary_content: '二進位內容', + scan_truncated: '掃描內容截斷', + }, + riskDescriptionText: { + script_file: '包含腳本檔案,可能執行本地命令。', + dependency_manifest: '包含依賴聲明檔案,安裝時可能拉取外部依賴。', + external_url: '包含外部 URL,請確認外聯地址可信。', + environment_variable: '聲明了環境變數,請確認用途和預設值。', + dockerfile: '包含 Dockerfile,可能定義映像構建或執行邏輯。', + command_execution: '包含命令執行相關呼叫,請確認執行邏輯可信。', + remote_script_execution: '包含遠端腳本下載後直接執行邏輯。', + eval_execution: '包含 eval 動態執行邏輯。', + base64_execution: '包含 Base64 解碼後執行邏輯。', + privileged_command: '包含 sudo 提權命令。', + dangerous_delete: '包含危險刪除命令。', + world_writable_permission: '包含 chmod 777 權限放開命令。', + credential: '包含疑似 API Key、Token、Secret 或 Password。', + sensitive_path: '包含存取敏感目錄或敏感檔案的行為。', + suspicious_download_execute: '包含可疑外聯下載或下載後執行邏輯。', + binary_content: '檔案中包含 NUL 位元組,可能隱藏可執行內容或二進位內容。', + scan_truncated: '檔案內容超過掃描上限,未掃描部分仍需人工確認。', + }, + }, benchmark: { title: '基準測試', create: '建立', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index 7439a6fe1..aab43127d 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -686,6 +686,7 @@ const message = { skillsMarketSourceClawhubGlobal: 'ClawHub(全球)', skillsMarketSourceClawhubChina: 'ClawHub(中国)', skillsMarketSourceSkillhub: 'SkillHub(腾讯)', + skillsMarketSourceLocalHub: '本地 Skill 商店', skillsScore: '评分', skillsMarketSourceOfficial: '官方', versionUnsupportedTitle: '当前版本暂不支持该功能', @@ -952,6 +953,121 @@ const message = { importResult: '导入成功 {0} 条,重复 {1} 条,无效 {2} 条', auditLogs: '审计日志', }, + skillsHub: { + title: 'Skills 商店', + upload: '上传 Skill', + importSkill: '导入 Skill', + importMethod: '导入方式', + repositoryAddress: '仓库地址', + repositoryAddressPlaceholder: '请输入仓库地址', + repositoryRef: '分支或 Tag', + repositoryRefPlaceholder: '默认为 main,失败后将尝试 master', + repositoryDownloadPreview: '目标下载地址:', + packageURL: '压缩包 URL', + packageURLPlaceholder: '请输入 .zip 压缩包地址', + importSuccess: 'Skill 导入成功,已进入待审核状态。', + source: '来源', + applicableAgent: '适用智能体', + riskLevel: '风险等级', + riskReferenceHint: + '风险等级由系统根据 Skill 包内容自动检测生成,仅作为审核参考,不代表绝对安全。\n低:未发现明显风险\n中:包含脚本、依赖、外部 URL 或环境变量\n高:包含远程执行、敏感信息或高危命令', + riskCheckResult: '安全检查结果', + noRiskFound: '暂未发现明显风险。', + riskFilePath: '文件路径', + riskKeyword: '命中的关键字或规则', + riskDescription: '风险说明', + publishHighRiskConfirm: '该 Skill 存在高风险项,可能包含脚本执行、外部下载或敏感信息访问。确认仍要上架吗?', + approve: '审核通过', + reject: '审核驳回', + publish: '上架', + disable: '下架', + customTarget: '自定义智能体', + addCustomTarget: '添加智能体', + installToTarget: '安装到自定义智能体', + containerized: '容器化', + containerName: '容器名称', + hostSkillDir: 'Skill 目录', + hostSkillDirHelper: '请填写 1Panel 所在服务器上的 Skill 目录路径。', + containerSkillDir: '容器内 Skill 目录', + installMethod: '安装方式', + copy: '复制', + extract: '解压', + postInstallCommand: '安装后命令', + localSource: '本地上传', + uploadDragTitle: '拖拽 Skill 包到此处,或点击上传', + uploadFormatLabel: '支持格式:', + uploadFormatValue: '.zip、.7z、.tar、.tar.gz', + uploadSizeLabel: '文件大小:', + uploadSizeValue: '单个文件不超过 5 MB', + uploadRequirementLabel: '包内要求:', + uploadRequirementPrefix: 'Skill 包内必须包含', + onlySupportedArchive: '仅支持上传 .zip、.7z、.tar、.tar.gz 格式的 Skill 包', + uploadSizeExceeded: '单个 Skill 包不能超过 5 MB', + uploadSuccess: 'Skill 上传成功,已进入待审核状态。', + offlineNotice: '当前为离线环境,仅支持从本地 Skill 商店安装技能。', + publishConfirm: '确认上架该 Skill?上架后可在智能体中从本地 Skill 商店安装。', + disableConfirm: '确认下架该 Skill?已安装到智能体中的 Skill 不受影响。', + deleteConfirm: '确认删除该 Skill?删除后不可恢复,已安装到智能体中的 Skill 不受影响。', + rejectConfirm: '确认驳回该 Skill 的审核?驳回后 Skill 将进入“审核未通过”状态。', + downloadPublishedOnly: '仅支持下载已上架的 Skill 包。', + containerNameRequired: '容器化目标需要填写容器名称。', + deleteTargetConfirm: '确认删除该自定义智能体?删除后用户不能继续选择该安装目标。', + noInstallTarget: '暂无可用的自定义智能体,请先配置安装目标。', + postInstallConfirm: '该目标配置了安装后命令,执行前请确认该命令可信。', + status: { + pending: '待审核', + approved: '已审核', + published: '已上架', + disabled: '已下架', + rejected: '审核未通过', + }, + agent: { + common: '通用', + }, + risk: { + low: '低', + medium: '中', + high: '高', + }, + riskType: { + script_file: '脚本文件', + dependency_manifest: '依赖声明', + external_url: '外部 URL', + environment_variable: '环境变量', + dockerfile: 'Dockerfile', + command_execution: '命令执行', + remote_script_execution: '远程脚本执行', + eval_execution: '动态执行', + base64_execution: 'Base64 解码执行', + privileged_command: '提权命令', + dangerous_delete: '危险删除', + world_writable_permission: '高危权限', + credential: '疑似密钥', + sensitive_path: '敏感路径', + suspicious_download_execute: '可疑下载执行', + binary_content: '二进制内容', + scan_truncated: '扫描内容截断', + }, + riskDescriptionText: { + script_file: '包含脚本文件,可能执行本地命令。', + dependency_manifest: '包含依赖声明文件,安装时可能拉取外部依赖。', + external_url: '包含外部 URL,请确认外联地址可信。', + environment_variable: '声明了环境变量,请确认用途和默认值。', + dockerfile: '包含 Dockerfile,可能定义镜像构建或运行逻辑。', + command_execution: '包含命令执行相关调用,请确认执行逻辑可信。', + remote_script_execution: '包含远程脚本下载后直接执行逻辑。', + eval_execution: '包含 eval 动态执行逻辑。', + base64_execution: '包含 Base64 解码后执行逻辑。', + privileged_command: '包含 sudo 提权命令。', + dangerous_delete: '包含危险删除命令。', + world_writable_permission: '包含 chmod 777 权限放开命令。', + credential: '包含疑似 API Key、Token、Secret 或 Password。', + sensitive_path: '包含访问敏感目录或敏感文件的行为。', + suspicious_download_execute: '包含可疑外联下载或下载后执行逻辑。', + binary_content: '文件中包含 NUL 字节,可能隐藏可执行内容或二进制内容。', + scan_truncated: '文件内容超过扫描上限,未扫描部分仍需人工确认。', + }, + }, benchmark: { title: '基准测试', create: '创建', diff --git a/frontend/src/views/ai/agents/agent/config/tabs/skills/hermes.vue b/frontend/src/views/ai/agents/agent/config/tabs/skills/hermes.vue index 37fe35e30..f53a71503 100644 --- a/frontend/src/views/ai/agents/agent/config/tabs/skills/hermes.vue +++ b/frontend/src/views/ai/agents/agent/config/tabs/skills/hermes.vue @@ -24,8 +24,12 @@