Skip to content

brano/dotnetclaw

Repository files navigation

image

logo

πŸ¦€ DotnetClaw

.NET 10 License: MIT Semantic Kernel

πŸ¦€ DotnetClaw is a personal AI assistant developed in .NET framework β€” Semantic Kernel agents, Telegram Bot channel, Agent Skills, MCP client, Playwright browser, Cursor agent CLI integration

What is DotnetClaw?

DotnetClaw is a self-hosted AI coding assistant that runs on your machine. It combines Microsoft Semantic Kernel, MCP (Model Context Protocol), a Telegram bot, and a Playwright browser into a single .NET 10 CLI agent that can read/write files, run shell commands, browse the web, and control your coding agent like Cursor or Claude Code β€” all from natural language.

Supports: OpenAI Β· Azure OpenAI Β· Anthropic Claude

Inspiration

DotnetClaw is inspired by 🦞 OpenClaw (formerly Clawdbot / Moltbot) by Peter Steinberger β€” a personal AI assistant concept that proved how powerful a well-wired agent loop can be. This is a .NET reimagining of that idea.

Projects

DotnetClaw CLI

  • Slash commands (/status /ask /plan /agent /cursor /claude)
  • Chat and control your DotnetClaw agent from terminal
  • Show chat history
  • Install agent skills from DotnetClawHub (hub)

DotnetClaw CLI running in Windows Powershell Terminal:

image

DotnetClaw CLI running in Ubuntu bash terminal (via WSL):

image

DotnetClaw Web UI

  • Onboarding
  • Dashboard
  • Chat
  • Skills
  • Terminal UI
  • Telegram channel configuration

Web UI for DotnetClaw developed in Blazor:

Screenshot 2026-03-26 143759

DotnetClawHub - Hub for Agent Skills

  • Create & Publish own custom (agent) skill
  • Browse agent skills
  • Install agent skill via API
  • SKILL.md support

Agent Skills Hub for DotnetClaw inspired by ClawHub developed in Blazor:

image

Browse & search for skills:

image

Skill details & installation:

image

Prerequisites

  • .NET 10 SDK
  • An OpenAI API key (or Azure OpenAI / Anthropic β€” see providers)

Run

# Set your API key
set OPENAI_API_KEY=sk-...

# Restore & run
cd src/DotnetClaw
dotnet run

Configuration

Environment Variable Description Default
DOTNETCLAW_PROVIDER openai | azure | anthropic openai
OPENAI_API_KEY OpenAI API key β€”
AZURE_OPENAI_ENDPOINT Azure OpenAI endpoint URL β€”
AZURE_OPENAI_API_KEY Azure OpenAI key β€”
AZURE_OPENAI_DEPLOYMENT Azure deployment name Model ID

Or edit appsettings.json directly.

REPL Commands

Command Action
help Show available commands
reset Clear conversation history
history Print full conversation history
exit Quit

Run Tests

cd tests/DotnetClaw.Tests
dotnet test

Agentic Loop

User Input
    β”‚
    β–Ό
ClawAgentLoop.RunTurnAsync()
    β”‚
    β”œβ”€β”€β–Ί ChatCompletionAgent (SK)
    β”‚        β”‚
    β”‚        β”œβ”€β”€ FunctionChoiceBehavior.Auto()  ← auto tool-call selection
    β”‚        β”‚
    β”‚        β”œβ”€β”€ [Tool Call] Shell.run_command
    β”‚        β”œβ”€β”€ [Tool Call] FileSystem.read_file
    β”‚        β”œβ”€β”€ [Tool Call] Dotnet.find_csharp_projects
    β”‚        β”‚        β–²
    β”‚        β”‚        └── results fed back into context
    β”‚        β”‚
    β”‚        └── Final text response  ◄── streamed to terminal
    β”‚
    └──► Max iterations guard (default: 20)

Workspace Identity Documents

On every session start (and on reset), DotnetClaw loads *.md files from ./workspace/ and injects them into the system prompt before any user message is sent.

./workspace/
β”œβ”€β”€ SOUL.md      ← Who the agent is (personality, values, style)
β”œβ”€β”€ AGENTS.md    ← How it uses tools and handles multi-agent flows
β”œβ”€β”€ USER.md      ← Who you are (role, prefs, tech stack)
β”œβ”€β”€ CONTEXT.md   ← What you're working on right now
β”œβ”€β”€ MEMORY.md    ← Persistent facts you want remembered
└── *.md         ← Any additional documents, loaded alphabetically

Loading order is controlled by WorkspaceDocumentPriority in appsettings.json (default: SOUL β†’ AGENTS β†’ USER β†’ CONTEXT β†’ MEMORY β†’ TOOLS β†’ RULES). Remaining *.md files follow alphabetically.

The workspace folder is optional β€” if it doesn't exist, DotnetClaw starts with the base system prompt only.

REPL commands

Command Action
workspace Show loaded documents table
ws reload Force reload from disk without resetting chat
prompt Print the full effective system prompt

Runtime skill

The agent can query workspace docs mid-conversation via the Workspace plugin:

  • list_workspace_docs β€” table of loaded docs
  • get_workspace_doc SOUL β€” fetch a specific doc's content
  • reload_workspace β€” hot-reload from disk
  • get_workspace_context β€” full injected context block

Skills (Plugins)

Shell

Function Description
run_command Execute any shell command
list_directory Tree-style directory listing
get_working_directory Return current working directory

FileSystem

Function Description
read_file Read a text file
write_file Create or overwrite a file
append_file Append to an existing file
delete_file Delete a file
file_exists Check if a path exists
find_files Glob search for files

Dotnet

Function Description
find_csharp_projects Locate .csproj files
summarise_csharp_file Structural summary of a .cs file
get_nuget_packages List NuGet packages in a .csproj

Workspace

Function Description
list_workspace_docs Table of all loaded identity documents
get_workspace_doc Fetch full content of a specific document
reload_workspace Force reload all docs from disk
get_workspace_context Full combined context block (as injected)

Cursor CLI (agent)

Function Mode File changes? Description
cursor_agent agent βœ… Yes Autonomous coding β€” reads, plans, edits files
cursor_plan plan ❌ No Returns a step-by-step plan, no edits
cursor_ask ask ❌ No Q&A about the codebase, read-only
cursor_run any depends Low-level runner with full flag control

CLI structure built by the plugin:

agent --mode=<agent|plan|ask>  --prompt "..."  [--model <model>]  [--yes]  [extraFlags]  <workspace>

Configuration (appsettings.json β†’ DotnetClaw:Cursor):

Key Default Description
ExecutablePath "agent" Path to agent.exe / agent, or bare name if it's on PATH
DefaultTimeoutSeconds 300 Per-invocation timeout (max 1800)
RequireConfirmationForAgentMode true Prompt user before running in agent mode (destructive)
AutoApproveInAgentMode false Pass --yes to suppress Cursor's own confirmations
Model "" Override model, e.g. "claude-3-5-sonnet" or "gpt-4o"
ExtraFlags "" Raw flags appended to every invocation

Finding agent.exe on your system:

# Windows
%LOCALAPPDATA%\Programs\cursor\resources\app\bin\agent.exe

# macOS
/Applications/Cursor.app/Contents/Resources/app/bin/agent

# Linux
~/.local/share/cursor/resources/app/bin/agent

Set ExecutablePath in appsettings.json or add the binary's folder to your PATH.

Add a new skill in 3 steps:

  1. Create Plugins/MyPlugin.cs with [KernelFunction] methods
  2. Register in KernelFactory.cs:
    builder.Plugins.AddFromObject(services.GetRequiredService<MyPlugin>(), "MySkill");
  3. Add DI registration in Program.cs:
    services.AddSingleton<MyPlugin>();

MCP Client Skill

DotnetClaw connects to any Model Context Protocol server and exposes its tools directly to the Semantic Kernel agent β€” no hand-written glue code per tool.

Packages used:

  • ModelContextProtocol.Client 0.1.0-preview.10 β€” official .NET MCP SDK (stdio + SSE transports)
  • Microsoft.SemanticKernel.Plugins.MCP 1.30.0 β€” auto-converts MCP tool schemas β†’ SK KernelFunctions via IMcpClient.AsKernelPluginAsync()

How it works

appsettings.json: Mcp:Servers[]
        β”‚
        β–Ό
McpConnectionManager (IHostedService)
  β€’ Launches stdio servers as child processes (npx/uvx/python/custom binary)
  β€’ OR connects to SSE servers over HTTP
  β€’ Holds live IMcpClient instances, one per server
  β€’ Connects in parallel at startup; failures are logged, not fatal
        β”‚
        β–Ό
McpKernelLoader  (called from ClawAgentLoop.InitialiseAsync)
  β€’ Calls IMcpClient.AsKernelPluginAsync("Mcp_{name}") for each connected client
  β€’ Each MCP tool β†’ SK KernelFunction with auto-generated description + parameters
  β€’ Agent sees them identically to built-in skills
        β”‚
        β–Ό
Agent turn: "Read the file /project/README.md"
  β†’ Mcp_filesystem.read_file(path: "/project/README.md")
  β†’ MCP server returns content
  β†’ Agent gets the result

Configuration

Enable servers in appsettings.json under DotnetClaw:Mcp:Servers:

"Mcp": {
  "ConnectionTimeoutSeconds": 30,
  "LogToolCallDetails": false,
  "Servers": [
    {
      "Name": "filesystem",
      "Description": "Read/write local files",
      "Transport": "Stdio",
      "Command": "npx",
      "Arguments": [ "-y", "@modelcontextprotocol/server-filesystem", "/my/projects" ],
      "Enabled": true
    },
    {
      "Name": "github",
      "Description": "Search repos, issues, files on GitHub",
      "Transport": "Stdio",
      "Command": "npx",
      "Arguments": [ "-y", "@modelcontextprotocol/server-github" ],
      "Environment": { "GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_..." },
      "Enabled": true
    },
    {
      "Name": "my-remote-server",
      "Description": "Custom SSE-based MCP server",
      "Transport": "Sse",
      "Url": "http://localhost:3000",
      "Enabled": true
    }
  ]
}

Transport types

Transport How it connects Use for
Stdio Spawns a child process, communicates over stdin/stdout Local tools: npx, uvx, Python scripts, custom binaries
Sse HTTP long-lived connection (Server-Sent Events) Remote servers, Docker containers, shared team servers

Popular public MCP servers (stdio)

Server Command What it does
@modelcontextprotocol/server-filesystem npx -y @modelcontextprotocol/server-filesystem <path> Read/write/search files
@modelcontextprotocol/server-github npx -y @modelcontextprotocol/server-github Repos, issues, PRs
@modelcontextprotocol/server-postgres npx -y @modelcontextprotocol/server-postgres <conn> Read-only SQL
mcp-server-fetch uvx mcp-server-fetch Fetch + Markdown convert URLs
@modelcontextprotocol/server-brave-search npx -y @modelcontextprotocol/server-brave-search Web search

SK plugin naming

Each server becomes a plugin named Mcp_{serverName}. Special characters in server names are replaced with _:

Server "filesystem"  β†’ plugin "Mcp_filesystem"
Server "my-github"   β†’ plugin "Mcp_my_github"

The agent calls these exactly like built-in skills. With a filesystem server connected:

You: "List all .cs files in /src and find the one with the most lines"
β†’ Mcp_filesystem.list_directory(path: "/src")
β†’ [agent loops through results]
β†’ Mcp_filesystem.read_file(path: "/src/biggest.cs")
β†’ "The file with the most lines is ClawAgentLoop.cs (181 lines)"

Management functions (McpPlugin)

Function Description
mcp_list_servers List all servers, connection status, tool counts
mcp_list_tools List tools + parameters for a specific server
mcp_call_tool Call a tool with raw JSON args (debug/fallback)
mcp_reconnect Reconnect a server and reload its tools into kernel
mcp_list_resources List MCP resources exposed by a server
mcp_read_resource Read a resource by URI

Browser Skill (Playwright)

DotnetClaw has a full headless browser via Microsoft Playwright. The agent can navigate the web, take screenshots, fill forms, click buttons, and push screenshots directly to Telegram β€” all from natural language instructions.

One-time setup

Install browser binaries after first dotnet build:

# Install the Playwright CLI tool
dotnet tool install --global Microsoft.Playwright.CLI

# Install Chromium (default). Add firefox or webkit if needed.
playwright install chromium

Or without the global tool:

pwsh bin/Debug/net10.0/playwright.ps1 install chromium

Agent usage examples

You: "Go to https://github.com and take a screenshot"
β†’ browser_navigate(url: "https://github.com")
β†’ browser_screenshot_and_send()           ← sends photo to Telegram

You: "Log into the admin panel at https://app.example.com"
β†’ browser_navigate(url: "https://app.example.com/login")
β†’ browser_fill(cssSelector: "#username", value: "admin")
β†’ browser_fill(cssSelector: "#password", value: "secret")
β†’ browser_click(cssSelector: "button[type='submit']")
β†’ browser_screenshot_and_send(caption: "Login result")

You: "Fill the contact form and submit it"
β†’ browser_submit_form(
      fields: "#name=Alice\n#email=alice@example.com\n#message=Hello",
      submitSelector: "#send-btn",
      successSelector: ".thank-you-message")

Telegram bot commands (Browser)

Command Description
/goto <url> Navigate browser to URL, auto-send screenshot
/screenshot Screenshot current page β†’ Telegram photo
/screenshot <selector> Screenshot a specific CSS element

Kernel functions

Function Description
browser_navigate Navigate to a URL, returns title + status + load time
browser_screenshot Save screenshot to disk, returns file path
browser_screenshot_and_send Screenshot + send as Telegram photo in one step
browser_get_text Extract visible text from page or element
browser_fill Type value into a form field by CSS selector
browser_click Click any element by CSS selector
browser_submit_form Fill multiple fields + click submit atomically
browser_evaluate Run JavaScript on the page, return the result

Configuration (appsettings.json)

"Browser": {
  "BrowserType": "chromium",      // chromium | firefox | webkit
  "Headless": true,               // false to watch the browser window
  "DefaultTimeoutMs": 30000,
  "ScreenshotDirectory": "screenshots",
  "ScreenshotFormat": "png",      // png | jpeg
  "JpegQuality": 90,
  "PersistBrowserSession": true,  // reuse page between calls (cookies persist)
  "ViewportWidth": 1280,
  "ViewportHeight": 800,
  "SlowMoMs": 0                   // slow down ops (ms) β€” useful for debugging
}

Architecture

BrowserSessionManager                 ← IHostedService, owns IPlaywright + IBrowser
    β”‚  lazy-inits on first use
    β”‚  persistent mode: reuses IPage (login state survives between turns)
    β”‚  isolated mode: fresh IPage per call (clean state)
    β”‚
    β–Ό
PlaywrightBrowserSession              ← IBrowserSession wrapping a real Playwright IPage
    β”‚  navigate, screenshot, fill, click, evaluate, waitForSelector
    β”‚
    β–Ό
BrowserPlugin                         ← 8 KernelFunctions
    β”‚
    β”œβ”€β”€ browser_navigate
    β”œβ”€β”€ browser_screenshot         β†’ saves to ./screenshots/
    β”œβ”€β”€ browser_screenshot_and_send β†’ ITelegramBotClient.SendPhotoAsync (multipart upload)
    β”œβ”€β”€ browser_get_text
    β”œβ”€β”€ browser_fill
    β”œβ”€β”€ browser_click
    β”œβ”€β”€ browser_submit_form        β†’ fills fields sequentially, then clicks submit
    └── browser_evaluate           β†’ runs JS on page

TelegramCommandRouter
    β”œβ”€β”€ /goto <url>       β†’ browser_navigate + browser_screenshot_and_send
    └── /screenshot [sel] β†’ browser_screenshot_and_send

Telegram Bot

Control DotnetClaw remotely via Telegram β€” no port forwarding or webhook required. Uses long-polling (getUpdates) and raw HttpClient β€” zero Telegram SDK dependency.

Setup

  1. Message @BotFather β†’ /newbot β†’ copy the token
  2. Message @userinfobot to get your chat ID
  3. Set config in appsettings.json:
"Telegram": {
  "Enabled": true,
  "BotToken": "123456789:ABCdef...",
  "AllowedChatIds": [ 123456789 ]
}

Or via environment variable (recommended for production):

export TELEGRAM_BOT_TOKEN=123456789:ABCdef...

Bot Commands

Command Description Touches files?
/ask <question> Ask DotnetClaw anything ❌
<free text> Same as /ask ❌
/plan <prompt> Cursor plan mode ❌
/cursor_ask <q> Cursor Q&A about codebase ❌
/agent <prompt> Cursor agent (edits files!) βœ…
/reset Clear conversation + reload workspace β€”
/status Show bot status β€”
/help Show command list β€”

Configuration

Key Default Description
Enabled false Must be true to activate
BotToken "" Token from @BotFather (or TELEGRAM_BOT_TOKEN env var)
AllowedChatIds [] Whitelist of authorised chat IDs
LongPollTimeoutSeconds 30 Seconds per getUpdates call
MaxMessageLength 4000 Auto-split threshold (Telegram limit is 4096)
ParseMode MarkdownV2 Message formatting mode
SendTypingIndicator true Show "typing…" while processing

Proactive Notifications (Agent Skill)

The agent can push Telegram messages mid-task using the Telegram kernel plugin:

You: "Run the tests and notify me on Telegram when done"
β†’ agent calls Shell.run_command("dotnet test")
β†’ agent calls Telegram.send_telegram_notification("Tests Complete", "12 passed, 0 failed")
β†’ πŸ“± You receive a Telegram message immediately

Architecture

Telegram long-poll (getUpdates, 30s)
        β”‚
        β–Ό
TelegramPollingService        ← IHostedService, runs alongside the REPL
        β”‚
        β”œβ”€β”€ AllowedChatIds whitelist check
        β”œβ”€β”€ Per-chat SemaphoreSlim   (serialises concurrent messages)
        β”œβ”€β”€ SendChatAction "typing…" (instant feedback)
        β”‚
        β–Ό
TelegramCommandRouter         ← Parses /commands and free text
        β”‚
        β”œβ”€β”€ /ask + freetext β†’ ClawAgentLoop.RunTurnAsync(outputSink: ResponseCollector)
        β”œβ”€β”€ /plan           β†’ CursorPlugin.CursorPlanAsync
        β”œβ”€β”€ /agent          β†’ CursorPlugin.CursorAgentAsync
        β”œβ”€β”€ /cursor_ask     β†’ CursorPlugin.CursorAskAsync
        └── /reset /status /help β†’ inline string responses
        β”‚
        β–Ό
ITelegramBotClient            ← Raw HttpClient, no SDK
  sendMessage (MarkdownV2, auto-splits >4000 chars, retries as plain text on parse error)

Solution structure

DotnetClaw/
β”œβ”€β”€ workspace/                      ← Identity documents (loaded every session)
β”‚   β”œβ”€β”€ SOUL.md                     ← Agent personality & values
β”‚   β”œβ”€β”€ AGENTS.md                   ← Tool-use rules & orchestration behaviour
β”‚   β”œβ”€β”€ USER.md                     ← User profile & preferences
β”‚   β”œβ”€β”€ CONTEXT.md                  ← Current project / session context
β”‚   β”œβ”€β”€ MEMORY.md                   ← Persistent facts across resets
β”‚   └── <custom>.md                 ← Any additional documents
β”œβ”€β”€ src/DotnetClaw/
β”‚   β”œβ”€β”€ Program.cs                  ← Entry point + REPL loop
β”‚   β”œβ”€β”€ appsettings.json            ← Configuration
β”‚   β”œβ”€β”€ Config/
β”‚   β”‚   └── DotnetClawOptions.cs    ← Typed config model
β”‚   β”œβ”€β”€ Workspace/
β”‚   β”‚   β”œβ”€β”€ WorkspaceDocument.cs    ← Typed record for a loaded identity doc
β”‚   β”‚   └── WorkspaceLoader.cs      ← Scans ./workspace, loads in priority order
β”‚   β”œβ”€β”€ Agents/
β”‚   β”‚   β”œβ”€β”€ ClawAgentLoop.cs        ← Core agentic loop (SK ChatCompletionAgent)
β”‚   β”‚   └── KernelFactory.cs        ← Kernel + plugin wiring, provider selection
β”‚   β”œβ”€β”€ Telegram/
β”‚   β”‚   β”œβ”€β”€ TelegramModels.cs         ← TelegramUpdate, Message, Chat, User, ApiResponse<T>
β”‚   β”‚   β”œβ”€β”€ TelegramBotClient.cs      ← ITelegramBotClient + raw HttpClient impl
β”‚   β”‚   β”œβ”€β”€ TelegramCommandRouter.cs  ← Command parser + dispatch to agent/Cursor
β”‚   β”‚   └── TelegramPollingService.cs ← IHostedService long-poll loop
β”‚   β”‚   β”œβ”€β”€ ShellPlugin.cs          ← run_command, list_directory
β”‚   β”‚   β”œβ”€β”€ FileSystemPlugin.cs     ← read/write/find files
β”‚   β”‚   β”œβ”€β”€ DotnetPlugin.cs         ← .csproj / C# project analysis
β”‚   β”‚   β”œβ”€β”€ WorkspacePlugin.cs      ← Runtime workspace query skill
β”‚   β”‚   β”œβ”€β”€ CursorPlugin.cs         ← cursor_agent / cursor_plan / cursor_ask / cursor_run
β”‚   β”‚   β”œβ”€β”€ CursorTypes.cs          ← CursorMode enum, CursorResult
β”‚   β”‚   β”œβ”€β”€ CursorProcessRunner.cs  ← ICursorProcessRunner + real OS process impl
β”‚   β”‚   └── TelegramPlugin.cs       ← send_telegram_message, send_telegram_notification
β”‚   └── UI/
β”‚       └── SpectreConsoleRenderer.cs ← Rich terminal UI via Spectre.Console
└── tests/DotnetClaw.Tests/
    β”œβ”€β”€ ShellPluginTests.cs
    β”œβ”€β”€ FileSystemPluginTests.cs
    β”œβ”€β”€ WorkspaceLoaderTests.cs
    β”œβ”€β”€ CursorPluginTests.cs        ← FakeCursorProcessRunner, all modes + edge cases
    β”œβ”€β”€ TelegramBotClientTests.cs   ← MockHttpMessageHandler, send/receive/split
    └── TelegramCommandRouterTests.cs ← Command parsing, routing, Markdown escaping

License

MIT

About

πŸ¦€ DotnetClaw β€” Personal AI assistant in .NET β€” Semantic Kernel agents, Telegram Bot channel, Skills, MCP, Playwright browser, Cursor CLI agent integrations. Inspired by Open Claw developed in .NET framework.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors