using System.ComponentModel; using System.Text.Json; namespace ClawdDotNet.Models; static class ConfigHelper { public static string GetString(Dictionary config, string key, string fallback = "") { var val = config.GetValueOrDefault(key); return val switch { JsonElement je when je.ValueKind == JsonValueKind.String => je.GetString() ?? fallback, JsonElement je => je.ToString(), string s => s, null => fallback, _ => val.ToString() ?? fallback }; } public static int GetInt(Dictionary config, string key, int fallback = 0) { var val = config.GetValueOrDefault(key); return val switch { JsonElement je when je.ValueKind == JsonValueKind.Number => je.GetInt32(), JsonElement je => int.TryParse(je.ToString(), out var r) ? r : fallback, int i => i, _ => int.TryParse(val?.ToString(), out var r) ? r : fallback }; } public static bool GetBool(Dictionary config, string key, bool fallback = false) { var val = config.GetValueOrDefault(key); return val switch { JsonElement je when je.ValueKind is JsonValueKind.True => true, JsonElement je when je.ValueKind is JsonValueKind.False => false, JsonElement je => bool.TryParse(je.ToString(), out var r) ? r : fallback, bool b => b, _ => bool.TryParse(val?.ToString(), out var r) ? r : fallback }; } public static string GetStringArray(Dictionary config, string key) { var val = config.GetValueOrDefault(key); return val switch { JsonElement je when je.ValueKind == JsonValueKind.Array => string.Join(",", je.EnumerateArray().Select(e => e.GetString())), object[] arr => string.Join(",", arr), string s => s, _ => "" }; } } public enum FileRWAccessLevel { Denied, Read, ReadWrite, Admin } public sealed class FileRWToolSettings { [Category("Persönlicher Workspace")] [DisplayName("Erlaubte Endungen")] [Description("Dateiendungen für den eigenen Agenten-Workspace (z.B. .txt,.json,.md)")] public string PersonalAllowedExtensions { get; set; } = ".txt,.json,.md,.html,.js,.css"; [Category("Shared Workspace")] [DisplayName("Zugriffslevel")] [Description("Legt fest, welche Operationen im SharedWorkspace erlaubt sind")] public FileRWAccessLevel SharedAccessLevel { get; set; } = FileRWAccessLevel.Denied; [Category("Shared Workspace")] [DisplayName("Erlaubte Endungen")] [Description("Dateiendungen für den geteilten Workspace")] public string SharedAllowedExtensions { get; set; } = ".txt,.json,.md"; [Category("Shared Workspace – Schutz")] [DisplayName("Geschützte Pfade")] [Description("Komma-getrennte Pfade im SharedWorkspace die append-only sind (z.B. stocks/,archives/). Dateien dort können nur erstellt, nicht überschrieben oder gelöscht werden. Admin-Level umgeht den Schutz.")] public string ProtectedPaths { get; set; } = "stocks/"; public Dictionary ToConfig() => new() { ["personalAllowedExtensions"] = PersonalAllowedExtensions.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries), ["sharedAccessLevel"] = SharedAccessLevel.ToString(), ["sharedAllowedExtensions"] = SharedAllowedExtensions.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries), ["protectedPaths"] = ProtectedPaths.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) }; public static FileRWToolSettings FromConfig(Dictionary config) => new() { PersonalAllowedExtensions = ConfigHelper.GetStringArray(config, "personalAllowedExtensions") is { Length: > 0 } s1 ? s1 : (ConfigHelper.GetStringArray(config, "allowedExtensions") is { Length: > 0 } sOld ? sOld : ".txt,.json,.md,.html,.js,.css"), SharedAccessLevel = Enum.TryParse(ConfigHelper.GetString(config, "sharedAccessLevel"), true, out var level) ? level : FileRWAccessLevel.Denied, SharedAllowedExtensions = ConfigHelper.GetStringArray(config, "sharedAllowedExtensions") is { Length: > 0 } s2 ? s2 : ".txt,.json,.md", ProtectedPaths = ConfigHelper.GetStringArray(config, "protectedPaths") is { Length: > 0 } s3 ? s3 : "stocks/" }; } public sealed class MailToolSettings { [Category("Mail - Konto")] [DisplayName("Benutzername")] public string Username { get; set; } = ""; [Category("Mail - Konto")] [DisplayName("Passwort")] [PasswordPropertyText(true)] public string Password { get; set; } = ""; [Category("Mail - IMAP")] [DisplayName("IMAP-Host")] public string ImapHost { get; set; } = ""; [Category("Mail - IMAP")] [DisplayName("IMAP-Port")] public int ImapPort { get; set; } = 993; [Category("Mail - SMTP")] [DisplayName("SMTP-Host")] public string SmtpHost { get; set; } = ""; [Category("Mail - SMTP")] [DisplayName("SMTP-Port")] public int SmtpPort { get; set; } = 587; [Category("Mail - Sicherheit")] [DisplayName("Erlaubte Empfänger")] [Description("Komma-getrennte Liste erlaubter E-Mail-Adressen")] public string AllowedRecipients { get; set; } = ""; public Dictionary ToConfig() => new() { ["username"] = Username, ["password"] = Password, ["imapHost"] = ImapHost, ["imapPort"] = ImapPort, ["smtpHost"] = SmtpHost, ["smtpPort"] = SmtpPort, ["allowedRecipients"] = AllowedRecipients.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) }; public static MailToolSettings FromConfig(Dictionary config) => new() { Username = ConfigHelper.GetString(config, "username"), Password = ConfigHelper.GetString(config, "password"), ImapHost = ConfigHelper.GetString(config, "imapHost"), ImapPort = ConfigHelper.GetInt(config, "imapPort", 993), SmtpHost = ConfigHelper.GetString(config, "smtpHost"), SmtpPort = ConfigHelper.GetInt(config, "smtpPort", 587), AllowedRecipients = ConfigHelper.GetStringArray(config, "allowedRecipients") }; } public enum DatabaseType { MySql, Postgres, MsSql, MongoDb } public enum DatabaseAccessLevel { [Description("Nur Lesen (SELECT/find)")] ReadOnly, [Description("Lesen und Schreiben (INSERT/UPDATE/DELETE)")] ReadWrite, [Description("Vollzugriff (Admin/Schema-Änderungen)")] Admin } public sealed class DatabaseToolSettings { [Category("Datenbank")] [DisplayName("Typ")] [Description("Der zu verwendende Datenbanktyp")] public DatabaseType Type { get; set; } = DatabaseType.MySql; [Category("Datenbank")] [DisplayName("Connection-String")] public string ConnectionString { get; set; } = ""; [Category("Datenbank")] [DisplayName("Zugriffsebene")] [Description("Legt fest, welche Operationen der Agent ausführen darf")] public DatabaseAccessLevel AccessLevel { get; set; } = DatabaseAccessLevel.ReadOnly; [Category("Datenbank - Sicherheit")] [DisplayName("Erlaubte Tabellen")] [Description("Komma-getrennte Liste erlaubter Tabellen/Collections")] public string AllowedTables { get; set; } = ""; public Dictionary ToConfig() => new() { ["type"] = Type.ToString().ToLowerInvariant(), ["connectionString"] = ConnectionString, ["accessLevel"] = AccessLevel.ToString(), ["allowedTables"] = AllowedTables.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) }; public static DatabaseToolSettings FromConfig(Dictionary config) => new() { Type = Enum.TryParse(config.GetValueOrDefault("type")?.ToString(), true, out var result) ? result : DatabaseType.MySql, ConnectionString = config.GetValueOrDefault("connectionString")?.ToString() ?? "", AccessLevel = Enum.TryParse(config.GetValueOrDefault("accessLevel")?.ToString() ?? config.GetValueOrDefault("allowWrite")?.ToString(), true, out var level) ? level : (config.GetValueOrDefault("allowWrite") is true or "True" or "true" ? DatabaseAccessLevel.ReadWrite : DatabaseAccessLevel.ReadOnly), AllowedTables = config.GetValueOrDefault("allowedTables") is object[] arr ? string.Join(",", arr) : config.GetValueOrDefault("allowedTables")?.ToString() ?? "" }; } public sealed class FTPToolSettings { [Category("FTP Server")] [DisplayName("Host")] public string Host { get; set; } = ""; [Category("FTP Server")] [DisplayName("Port")] public int Port { get; set; } = 21; [Category("FTP Server")] [DisplayName("Benutzername")] public string Username { get; set; } = ""; [Category("FTP Server")] [DisplayName("Passwort")] [PasswordPropertyText(true)] public string Password { get; set; } = ""; [Category("FTP Lokal")] [DisplayName("Root-Pfad")] [Description("Basisverzeichnis für Dateiübertragungen")] public string RootPath { get; set; } = "./data/"; public Dictionary ToConfig() => new() { ["host"] = Host, ["port"] = Port, ["username"] = Username, ["password"] = Password, ["rootPath"] = RootPath }; public static FTPToolSettings FromConfig(Dictionary config) => new() { Host = ConfigHelper.GetString(config, "host"), Port = ConfigHelper.GetInt(config, "port", 21), Username = ConfigHelper.GetString(config, "username"), Password = ConfigHelper.GetString(config, "password"), RootPath = ConfigHelper.GetString(config, "rootPath", "./data/") }; } public sealed class TelegramToolSettings { [Category("Telegram")] [DisplayName("Bot-Token")] [PasswordPropertyText(true)] public string BotToken { get; set; } = ""; [Category("Telegram")] [DisplayName("Standard Chat-ID")] [Description("Die Standard-ID, an die Nachrichten gesendet werden, wenn keine andere ID angegeben ist.")] public string DefaultChatId { get; set; } = ""; [Category("Telegram - Sicherheit")] [DisplayName("Erlaubte Chat-IDs")] [Description("Komma-getrennte Liste erlaubter Chat-IDs")] public string AllowedChatIds { get; set; } = ""; public Dictionary ToConfig() => new() { ["botToken"] = BotToken, ["defaultChatId"] = DefaultChatId, ["allowedChatIds"] = AllowedChatIds.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) }; public static TelegramToolSettings FromConfig(Dictionary config) => new() { BotToken = ConfigHelper.GetString(config, "botToken"), DefaultChatId = ConfigHelper.GetString(config, "defaultChatId"), AllowedChatIds = ConfigHelper.GetStringArray(config, "allowedChatIds") }; } public sealed class DirectAPIToolSettings { [Category("DirectAPI")] [DisplayName("Standard-Provider")] public string DefaultProvider { get; set; } = "twelvedata"; [Category("DirectAPI")] [DisplayName("Cache TTL (Sekunden)")] public int CacheTtlSeconds { get; set; } = 60; [Category("DirectAPI - API Keys")] [DisplayName("Twelve Data Key")] [PasswordPropertyText(true)] public string TwelveDataKey { get; set; } = ""; [Category("DirectAPI - API Keys")] [DisplayName("Alpha Vantage Key")] [PasswordPropertyText(true)] public string AlphaVantageKey { get; set; } = ""; public Dictionary ToConfig() => new() { ["defaultProvider"] = DefaultProvider, ["cacheTtlSeconds"] = CacheTtlSeconds, ["providers"] = new Dictionary { ["twelvedata"] = new { apiKey = TwelveDataKey }, ["alphavantage"] = new { apiKey = AlphaVantageKey } } }; public static DirectAPIToolSettings FromConfig(Dictionary config) { var settings = new DirectAPIToolSettings { DefaultProvider = ConfigHelper.GetString(config, "defaultProvider", "twelvedata"), CacheTtlSeconds = ConfigHelper.GetInt(config, "cacheTtlSeconds", 60) }; if (config.GetValueOrDefault("providers") is JsonElement providersJe) { var providers = JsonSerializer.Deserialize>(providersJe.GetRawText()); if (providers != null) { if (providers.TryGetValue("twelvedata", out var td) && td.TryGetProperty("apiKey", out var tdk)) settings.TwelveDataKey = tdk.GetString() ?? ""; if (providers.TryGetValue("alphavantage", out var av) && av.TryGetProperty("apiKey", out var avk)) settings.AlphaVantageKey = avk.GetString() ?? ""; } } return settings; } } public sealed class WebFetchToolSettings { [Category("WebFetch")] [DisplayName("Erlaubte Domains")] [Description("Komma-getrennte Liste (z.B. reuters.com,bloomberg.com)")] public string AllowedDomains { get; set; } = ""; [Category("WebFetch")] [DisplayName("Max Response KB")] public int MaxResponseKb { get; set; } = 512; [Category("WebFetch")] [DisplayName("User Agent")] public string UserAgent { get; set; } = "ClawdDotNet-Agent/1.0"; public Dictionary ToConfig() => new() { ["allowedDomains"] = AllowedDomains.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries), ["maxResponseKb"] = MaxResponseKb, ["userAgent"] = UserAgent }; public static WebFetchToolSettings FromConfig(Dictionary config) => new() { AllowedDomains = ConfigHelper.GetStringArray(config, "allowedDomains"), MaxResponseKb = ConfigHelper.GetInt(config, "maxResponseKb", 512), UserAgent = ConfigHelper.GetString(config, "userAgent", "ClawdDotNet-Agent/1.0") }; } public sealed class WebMonitorToolSettings { [Category("WebMonitor")] [DisplayName("Monitore (JSON)")] [Description("JSON-Konfiguration der Monitore")] public string MonitorsJson { get; set; } = "{}"; public Dictionary ToConfig() { try { return new Dictionary { ["monitors"] = JsonSerializer.Deserialize>(MonitorsJson) ?? new() }; } catch { return new Dictionary { ["monitors"] = new Dictionary() }; } } public static WebMonitorToolSettings FromConfig(Dictionary config) => new() { MonitorsJson = config.GetValueOrDefault("monitors") is JsonElement je ? je.GetRawText() : "{}" }; } public sealed class AgentCommToolSettings { [Category("AgentComm")] [DisplayName("Info")] [Description("Dieses Tool benötigt keine Konfiguration. Es ermöglicht Agenten, mit anderen Agenten in der gleichen Instanz zu kommunizieren.")] [ReadOnly(true)] public string Status { get; set; } = "Aktiv"; public Dictionary ToConfig() => new(); public static AgentCommToolSettings FromConfig(Dictionary config) => new(); } public sealed class SocialMediaManagerToolSettings { // ─── X (Twitter) ─── [Category("1. X (Twitter) - API")] [DisplayName("Bearer Token")] [Description("X API v2 Bearer Token für die Authentifizierung")] [PasswordPropertyText(true)] public string XApiKey { get; set; } = ""; [Category("1. X (Twitter) - Monitoring")] [DisplayName("Überwachte Accounts")] [Description("Komma-getrennte Liste von X-Accounts die überwacht werden sollen (ohne @). Beispiel: elonmusk,unusual_whales,DeItaone")] public string XWatchAccounts { get; set; } = ""; // ─── Reddit ─── [Category("2. Reddit - Monitoring")] [DisplayName("Überwachte Subreddits")] [Description("Komma-getrennte Liste von Subreddits die überwacht werden sollen (ohne r/). Beispiel: wallstreetbets,stocks,options")] public string RedditWatchSubreddits { get; set; } = ""; [Category("2. Reddit - Monitoring")] [DisplayName("Posts pro Subreddit")] [Description("Maximale Anzahl Posts die pro Subreddit bei jedem Check abgerufen werden (Standard: 15)")] public int RedditPostLimit { get; set; } = 15; // ─── YouTube / STT ─── [Category("3. YouTube / STT")] [DisplayName("OpenRouter API Key")] [Description("API Key für Speech-to-Text Transkription über OpenRouter")] [PasswordPropertyText(true)] public string OpenRouterApiKey { get; set; } = ""; [Category("3. YouTube / STT")] [DisplayName("STT Modell")] [Description("OpenRouter Modell-ID für die Transkription")] public string STTModel { get; set; } = "openai/whisper-1"; [Category("3. YouTube / STT")] [DisplayName("YouTube Kanäle")] [Description("Komma-getrennte Liste von YouTube Kanal-URLs für automatische Überwachung")] public string YoutubeChannels { get; set; } = ""; public Dictionary ToConfig() => new() { ["xApiKey"] = XApiKey, ["xWatchAccounts"] = SplitToArray(XWatchAccounts), ["redditWatchSubreddits"] = SplitToArray(RedditWatchSubreddits), ["redditPostLimit"] = RedditPostLimit, ["openRouterApiKey"] = OpenRouterApiKey, ["sttModel"] = STTModel, ["youtubeChannels"] = SplitToArray(YoutubeChannels) }; public static SocialMediaManagerToolSettings FromConfig(Dictionary config) => new() { XApiKey = ConfigHelper.GetString(config, "xApiKey"), XWatchAccounts = ConfigHelper.GetStringArray(config, "xWatchAccounts"), RedditWatchSubreddits = ConfigHelper.GetStringArray(config, "redditWatchSubreddits"), RedditPostLimit = ConfigHelper.GetInt(config, "redditPostLimit", 15), OpenRouterApiKey = ConfigHelper.GetString(config, "openRouterApiKey"), STTModel = ConfigHelper.GetString(config, "sttModel", "openai/whisper-1"), YoutubeChannels = ConfigHelper.GetStringArray(config, "youtubeChannels") }; private static string[] SplitToArray(string csv) => csv.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); } public sealed class AgentEditorToolSettings { [Category("AgentEditor")] [DisplayName("Info")] [Description("Erlaubt dem Agenten, Identity und Soul anderer Agenten zu lesen, zu bearbeiten und neue Agenten zu erstellen. Keine weitere Konfiguration nötig.")] [ReadOnly(true)] public string Status { get; set; } = "Aktiv"; public Dictionary ToConfig() => new(); public static AgentEditorToolSettings FromConfig(Dictionary config) => new(); } public sealed class AgentSpawnToolSettings { [Category("AgentSpawn")] [DisplayName("Info")] [Description("Dieses Tool benötigt keine Konfiguration. Es ermöglicht Agenten, andere Agenten zu starten und ihnen Aufgaben zuzuweisen.")] [ReadOnly(true)] public string Status { get; set; } = "Aktiv"; public Dictionary ToConfig() => new(); public static AgentSpawnToolSettings FromConfig(Dictionary config) => new(); } public static class ToolSettingsFactory { public static object? CreateViewModel(string toolName, Dictionary? config) { config ??= new(); return toolName switch { "FileRW" => FileRWToolSettings.FromConfig(config), "Mail" => MailToolSettings.FromConfig(config), "Database" => DatabaseToolSettings.FromConfig(config), "Telegram" => TelegramToolSettings.FromConfig(config), "FTP" => FTPToolSettings.FromConfig(config), "DirectAPI" => DirectAPIToolSettings.FromConfig(config), "WebFetch" => WebFetchToolSettings.FromConfig(config), "WebMonitor" => WebMonitorToolSettings.FromConfig(config), "AgentComm" => AgentCommToolSettings.FromConfig(config), "SocialMediaManager" => SocialMediaManagerToolSettings.FromConfig(config), "AgentSpawn" => AgentSpawnToolSettings.FromConfig(config), "AgentEditor" => AgentEditorToolSettings.FromConfig(config), _ => null }; } public static Dictionary? ToConfig(string toolName, object? viewModel) { return viewModel switch { FileRWToolSettings f => f.ToConfig(), MailToolSettings m => m.ToConfig(), DatabaseToolSettings d => d.ToConfig(), TelegramToolSettings t => t.ToConfig(), FTPToolSettings ftp => ftp.ToConfig(), DirectAPIToolSettings dapi => dapi.ToConfig(), WebFetchToolSettings wf => wf.ToConfig(), WebMonitorToolSettings wm => wm.ToConfig(), AgentCommToolSettings ac => ac.ToConfig(), SocialMediaManagerToolSettings smm => smm.ToConfig(), AgentSpawnToolSettings asp => asp.ToConfig(), AgentEditorToolSettings ae => ae.ToConfig(), _ => null }; } }