commit 2fed388c99866464fac8cac770e8cff728cd7d74 Author: Richard Date: Sun Jul 26 18:21:46 2026 +0200 Initial commit: ClawdDotNet Import des bestehenden Projektstands in Git. - .NET 10 WinForms Anwendung (Multi-Agent / Tool-System) - .gitignore fuer Build-Artefakte, Secrets und Runtime-Daten ergaenzt Co-Authored-By: Claude Opus 4.8 diff --git a/.agents/rules/tool-entwickler-anweisungen.md b/.agents/rules/tool-entwickler-anweisungen.md new file mode 100644 index 0000000..d320928 --- /dev/null +++ b/.agents/rules/tool-entwickler-anweisungen.md @@ -0,0 +1,10 @@ +--- +trigger: always_on +--- + +Achtung: Die Entwicklung dieses Projekts ist strikt in "Core" und "Tools" getrennt! +Die Entwicklung des Cores übernimmt ein anderer Agent. Du nimmst unter keinen Umständen änderungen am Core vor! +Du bist nur dafür zuständig neue Tools für dieses Projekt zu entwickeln. +Informationen wie das Tool aufgebaut sein mus findest du im /docs/ToolDevelopmentGuide.md +Falls du ein Tool aufgrund fehlender schnittstellen nicht realisieren kannst gibst du mir eine entsprechende Rückmeldung! +Du wirst auf keinen Fall selbstständig das Core-Tool Interface verändern oder bearbeiten. \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..83752b3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,46 @@ +# ---- Build results ---- +[Bb]in/ +[Oo]bj/ +[Oo]ut/ +[Ll]og/ +[Ll]ogs/ + +# ---- Visual Studio / Rider ---- +.vs/ +.idea/ +*.user +*.suo +*.userosscache +*.sln.docstates +*.userprefs + +# ---- Build / restore artifacts ---- +project.lock.json +project.fragment.lock.json +artifacts/ +*.nupkg +*.snupkg +msbuild.log +msbuild.err +msbuild.wrn + +# ---- Deploy / packaging output ---- +deploy/*.zip + +# ---- Runtime instance/agent data (created next to the binary) ---- +Instances/ +data/ + +# ---- OS files ---- +Thumbs.db +ehthumbs.db +Desktop.ini +$RECYCLE.BIN/ +.DS_Store + +# ---- Secrets / local-only settings ---- +# Local Claude Code settings contain machine-specific permissions and API keys +.claude/settings.local.json +# Real credential configs (commit *.template / placeholder configs only) +*.secrets.json +appsettings.*.local.json diff --git a/ClawdDotNet.csproj b/ClawdDotNet.csproj new file mode 100644 index 0000000..6e1f679 --- /dev/null +++ b/ClawdDotNet.csproj @@ -0,0 +1,105 @@ + + + + WinExe + net10.0-windows + enable + true + enable + ClawdDotNet + Martin-Berube-Flat-Animal-Crab.ico + + + + de;en + + false + + false + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ClawdDotNet.slnx b/ClawdDotNet.slnx new file mode 100644 index 0000000..9025660 --- /dev/null +++ b/ClawdDotNet.slnx @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/ClawdDotNet_Prompt_InternetTools.md b/ClawdDotNet_Prompt_InternetTools.md new file mode 100644 index 0000000..720f70f --- /dev/null +++ b/ClawdDotNet_Prompt_InternetTools.md @@ -0,0 +1,695 @@ +# ClawdDotNet – Prompt-Anhang: Internet-Tools + +Dieser Abschnitt ergänzt die bestehenden Prompt-Anhänge und definiert drei +unabhängige Internet-Tools sowie ein spezialisiertes Monitoring-Tool für +strukturierte Webseiten (z.B. Capitol Trades). + +Alle Tools folgen den bekannten Prinzipien: IAgentTool implementiert, +Konfiguration ausschließlich aus AgentToolContext, keine Tool-zu-Tool-Abhängigkeiten. + +--- + +## Pflicht-Regel: Timestamp in jedem ToolResult + +**Diese Regel gilt für ALLE Internet-Tools ohne Ausnahme.** + +Jedes `ToolResult.Content` das Finanzdaten, Nachrichten oder externe Daten enthält, +muss ein JSON-Objekt zurückgeben das mindestens enthält: + +```json +{ + "fetchedAt": "2026-05-13T07:42:00Z", + "dataAsOf": "2026-05-13T07:40:00Z", + "source": "https://...", + "data": { ... } +} +``` + +- `fetchedAt` = Zeitpunkt des HTTP-Requests (UTC, immer `DateTime.UtcNow`) +- `dataAsOf` = Zeitpunkt der Daten laut Quelle (aus Response-Header, HTML oder API-Feld) + Falls nicht ermittelbar: `null` — NIEMALS schätzen oder weglassen +- `source` = exakte URL die abgerufen wurde + +Der System-Prompt jedes Agenten der Internet-Tools nutzt MUSS enthalten: +> "Verwende niemals Daten ohne `fetchedAt`-Feld. Wenn `dataAsOf` null ist, +> teile dem Nutzer mit dass der Datenzeitpunkt unbekannt ist. +> Erfinde niemals Kurse, Preise oder Daten aus dem Gedächtnis." + +--- + +## Tool 1: DirectAPI — Echtzeit-Finanzdaten + +**Datei: `ClawdDotNet.Tools.DirectAPI/DirectApiTool.cs`** + +Direkter HTTP-Zugriff auf Finanz-APIs. Kein HTML-Parsing. +Strukturiertes JSON mit verifizierbaren Timestamps. + +### AgentConfig-Beispiel + +```json +"DirectAPI": { + "providers": { + "twelvedata": { + "apiKey": "your-key-here", + "baseUrl": "https://api.twelvedata.com" + }, + "alphavantage": { + "apiKey": "your-key-here", + "baseUrl": "https://www.alphavantage.co" + }, + "coingecko": { + "baseUrl": "https://api.coingecko.com/api/v3" + }, + "yahoo": { + "baseUrl": "https://query1.finance.yahoo.com" + } + }, + "defaultProvider": "twelvedata", + "cacheTtlSeconds": 60 +} +``` + +### Tool-Implementierung + +```csharp +namespace ClawdDotNet.Tools.DirectAPI; + +public sealed class DirectApiTool : IAgentTool +{ + public string Name => "DirectAPI"; + public string Description => """ + Ruft Echtzeit-Finanzdaten von verifizierten APIs ab. + Alle Antworten enthalten fetchedAt und dataAsOf Timestamps. + Aktionen: quote, history, crypto, forex, search + """; + + public JsonElement InputSchema => JsonDocument.Parse(""" + { + "type": "object", + "required": ["action", "symbol"], + "properties": { + "action": { + "type": "string", + "enum": ["quote", "history", "crypto", "forex", "search"], + "description": "quote=aktueller Kurs, history=Kursverlauf, crypto=Krypto, forex=Wechselkurs, search=Symbol suchen" + }, + "symbol": { "type": "string", "description": "z.B. NVDA, BTC, EUR/USD" }, + "provider": { "type": "string", "description": "optional: twelvedata|alphavantage|coingecko|yahoo" }, + "interval": { "type": "string", "description": "für history: 1min|5min|1h|1day" }, + "outputsize":{ "type": "integer","description": "für history: Anzahl Datenpunkte, max 500" } + } + } + """).RootElement; + + public async Task ExecuteAsync( + JsonElement input, AgentToolContext ctx, CancellationToken ct) + { + // Config aus Context lesen — nie aus statischen Feldern + var config = ctx.ToolConfig["DirectAPI"] as Dictionary + ?? throw new InvalidOperationException("DirectAPI config missing"); + + var providers = config["providers"] as Dictionary ?? new(); + var cacheTtl = Convert.ToInt32(config.GetValueOrDefault("cacheTtlSeconds") ?? 60); + + var action = input.GetProperty("action").GetString()!; + var symbol = input.GetProperty("symbol").GetString()!; + var provider = input.TryGetProperty("provider", out var p) + ? p.GetString() + : config.GetValueOrDefault("defaultProvider")?.ToString() + ?? "twelvedata"; + + // Cache-Check: agentId + symbol + action als Key + var cacheKey = $"directapi:{ctx.AgentId}:{provider}:{action}:{symbol}"; + // (Cache-Implementierung über IMemoryCache oder Redis aus Core) + + return action switch + { + "quote" => await FetchQuoteAsync(symbol, provider, providers, ct), + "history" => await FetchHistoryAsync(input, symbol, provider, providers, ct), + "crypto" => await FetchCryptoAsync(symbol, providers, ct), + "forex" => await FetchForexAsync(symbol, provider, providers, ct), + "search" => await SearchSymbolAsync(symbol, provider, providers, ct), + _ => new ToolResult(false, "", $"Unknown action: {action}") + }; + } + + private async Task FetchQuoteAsync( + string symbol, string provider, + Dictionary providers, CancellationToken ct) + { + // Jeder Provider hat eigene URL-Struktur + // Gemeinsam: immer Cache-Control: no-cache Header setzen + // Gemeinsam: dataAsOf aus Response extrahieren (nicht schätzen) + + // Twelve Data Quote: + // GET https://api.twelvedata.com/quote?symbol={symbol}&apikey={key} + // Response enthält: "datetime" → das ist dataAsOf + // Response enthält: "timestamp" (Unix) → ebenfalls verwertbar + + // Yahoo Finance Quote (kein Key nötig): + // GET https://query1.finance.yahoo.com/v8/finance/chart/{symbol}?interval=1m&range=1d + // Response: result[0].meta.regularMarketTime (Unix timestamp) → dataAsOf + + // Alpha Vantage Quote: + // GET https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol={symbol}&apikey={key} + // Response: "Global Quote"."07. latest trading day" → dataAsOf (nur Datum, keine Zeit) + + // IMPLEMENTIERUNGSREGEL: dataAsOf IMMER aus der API-Antwort lesen. + // Wenn das Feld fehlt oder leer ist → dataAsOf = null, NICHT DateTime.UtcNow. + + throw new NotImplementedException("Implement per provider"); + } + + // history, crypto, forex, search analog implementieren +} +``` + +### NuGet + +Keine externen HTTP-Bibliotheken. Nur `System.Net.Http.HttpClient` via `IHttpClientFactory`. + +--- + +## Tool 2: WebFetch — Nachrichten & strukturiertes HTML + +**Datei: `ClawdDotNet.Tools.WebFetch/WebFetchTool.cs`** + +HTTP-Abruf mit Whitelist, Timestamp-Extraktion und HTML-zu-Text-Konvertierung. +Kein JavaScript-Rendering (statisches HTML only). + +### AgentConfig-Beispiel + +```json +"WebFetch": { + "allowedDomains": [ + "reuters.com", + "bloomberg.com", + "sec.gov", + "feeds.finance.yahoo.com", + "reddit.com", + "capitoltrades.com" + ], + "maxResponseKb": 512, + "timeoutSeconds": 15, + "userAgent": "ClawdDotNet-Agent/1.0 (Research Bot)" +} +``` + +### Tool-Implementierung + +```csharp +namespace ClawdDotNet.Tools.WebFetch; + +public sealed class WebFetchTool : IAgentTool +{ + public string Name => "WebFetch"; + public string Description => """ + Ruft statische Webseiten oder RSS-Feeds ab und extrahiert Text + Timestamps. + Nur Domains aus der Whitelist erlaubt. Kein JavaScript-Rendering. + Aktionen: fetch, rss + """; + + public JsonElement InputSchema => JsonDocument.Parse(""" + { + "type": "object", + "required": ["action", "url"], + "properties": { + "action": { + "type": "string", + "enum": ["fetch", "rss"], + "description": "fetch=HTML-Seite abrufen und zu Text konvertieren, rss=RSS/Atom-Feed parsen" + }, + "url": { "type": "string" }, + "selector":{ "type": "string", + "description": "optional: CSS-ähnlicher Hint welcher Teil relevant ist, z.B. 'table', 'article'" } + } + } + """).RootElement; + + public async Task ExecuteAsync( + JsonElement input, AgentToolContext ctx, CancellationToken ct) + { + var config = ctx.ToolConfig["WebFetch"] as Dictionary ?? new(); + var allowedDomains = (config.GetValueOrDefault("allowedDomains") + as List) ?? new List(); + + var url = input.GetProperty("url").GetString()!; + var action = input.GetProperty("action").GetString()!; + + // Domain-Whitelist prüfen + var host = new Uri(url).Host.Replace("www.", ""); + if (!allowedDomains.Any(d => host == d || host.EndsWith("." + d))) + return new ToolResult(false, "", + $"Domain '{host}' nicht in der Whitelist dieses Agenten."); + + return action switch + { + "fetch" => await FetchPageAsync(url, input, config, ct), + "rss" => await FetchRssAsync(url, ct), + _ => new ToolResult(false, "", $"Unknown action: {action}") + }; + } + + private async Task FetchPageAsync( + string url, JsonElement input, + Dictionary config, CancellationToken ct) + { + using var http = CreateHttpClient(config); + using var request = new HttpRequestMessage(HttpMethod.Get, url); + + // Kein Cache — immer frische Daten anfordern + request.Headers.CacheControl = new System.Net.Http.Headers.CacheControlHeaderValue + { NoCache = true, NoStore = true }; + + using var response = await http.SendAsync(request, ct); + response.EnsureSuccessStatusCode(); + + // dataAsOf aus HTTP-Headern extrahieren (Reihenfolge: Last-Modified > Date) + DateTimeOffset? dataAsOf = response.Content.Headers.LastModified + ?? response.Headers.Date; + + var html = await response.Content.ReadAsStringAsync(ct); + var maxKb = Convert.ToInt32(config.GetValueOrDefault("maxResponseKb") ?? 512); + + if (html.Length > maxKb * 1024) + html = html[..(maxKb * 1024)]; + + // HTML → lesbarer Text (einfache Implementierung ohne externe Libs) + var text = StripHtml(html); + + // Versuche dataAsOf aus HTML-Meta-Tags zu verfeinern falls Header fehlt + if (dataAsOf == null) + dataAsOf = ExtractDateFromHtml(html); + + var result = new + { + fetchedAt = DateTime.UtcNow, + dataAsOf = dataAsOf?.UtcDateTime, + source = url, + data = new { text } + }; + + return new ToolResult(true, JsonSerializer.Serialize(result)); + } + + private async Task FetchRssAsync(string url, CancellationToken ct) + { + // XML parsen mit System.Xml.Linq + // Einträge: title, link, pubDate (→ dataAsOf), description + // Neueste Einträge zuerst, max 20 + throw new NotImplementedException(); + } + + private static string StripHtml(string html) + { + // Einfaches Regex-basiertes Stripping + // Script- und Style-Tags zuerst entfernen, dann alle anderen Tags + // Anschließend HTML-Entities dekodieren (System.Net.WebUtility.HtmlDecode) + // Mehrfache Leerzeilen auf max. 2 reduzieren + throw new NotImplementedException(); + } + + private static DateTime? ExtractDateFromHtml(string html) + { + // Suche nach: