plugins/shiny-client/skills/shiny-aiconversation/SKILL.md
Generate code for Shiny.AiConversation - a centralized AI service library for .NET MAUI apps with chat client abstraction, wake word detection, speech-to-text/text-to-speech, acknowledgement modes (None/AudioBlip/LessWordy/Full), persistent message store, optional AI chat history lookup tool, and configurable sound effects
npx skillsauth add shinyorg/skills shiny-aiconversationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
reference/ai-service.md - Service orchestration, lifecycle, and configuration detailsreference/registration.md - DI registration patterns and setup optionsreference/message-store.md - IMessageStore guidance and persistence patternsreference/chat-client-provider.md - IChatClientProvider implementation guidancereference/chat-lookup-tool.md - AI chat-history lookup tool behaviorreference/voice-selection-tools.md - Voice discovery, preview, and switching toolsYou are an expert in the Shiny.AiConversation library, a centralized AI service for .NET MAUI applications that integrates chat, speech recognition, wake word detection, text-to-speech, and persistent message storage.
NuGet: Shiny.AiConversation
Namespace: Shiny.AiConversation
Infrastructure Namespace: Shiny.AiConversation.Infrastructure (internal implementations)
The library provides:
IChatClient (from Microsoft.Extensions.AI) — a default implementation (InjectedChatClientProvider) resolves IChatClient from DI; custom implementations handle authentication, token management, and client constructionContextProvider when an IMessageStore is registeredIContextProvider that adds three AI tools — get_available_voices, play_voice_sample, and change_voice — enabling the AI to list voices, play audio samples, and change its own TTS voice mid-conversation. Enabled via opts.AddVoiceSelectionTools().AiContext per request. Each provider's Apply(AiContext) method receives a mutable context and adds its contributions. The ContextProvider handles time-based prompts, acknowledgement-aware voice prompts, and DI-registered AITool instances. Implement custom providers to add domain-specific system prompts, tools, or override speech settings.IContextProvider.Apply() — contains Acknowledgement, SystemPrompts, Tools, QuietWords, SpeechToTextOptions, and TextToSpeechOptions that providers populate or modifyAddContextProvider<T>()Built-in Provider Packages:
OpenAiStaticChatProvider): Static OpenAI-compatible provider. Accepts API key, endpoint URI, and model name. Works with OpenAI, Azure OpenAI, Ollama, or any OpenAI-compatible API. Register with opts.AddStaticOpenAIChatClient(apiToken, endpointUri, modelName).GitHubCopilotChatClientProvider): MAUI-specific provider using GitHub device code OAuth flow and the Copilot API. Self-contained auth — shows a popup with the device code, copies to clipboard, opens browser, polls until authorized. Tokens stored in SecureStorage. Register with opts.AddGithubCopilotChatClient(). Additional API: StartAuthentication(), CancelAuthentication(), SignOut(), IsAuthenticated, AccessTokenChanged event.Microsoft.Extensions.AI — IChatClient, ChatMessage, ChatRole, AITool, ChatOptionsShiny.Speech — ISpeechToTextService, ITextToSpeechService, IAudioPlayer for voice interactions and sound effectsInvoke this skill when the user wants to:
Always register with AddShinyAiConversation():
using Shiny.AiConversation;
// Option A: Register an IChatClient in DI (simplest approach)
builder.Services.AddChatClient(new OpenAIClient("your-api-key").GetChatClient("gpt-4o").AsIChatClient());
// Option B: Use a built-in provider
builder.Services.AddShinyAiConversation(opts =>
{
// OpenAI-compatible (OpenAI, Azure OpenAI, Ollama, etc.)
opts.AddStaticOpenAIChatClient("your-api-key", "https://api.openai.com/v1", "gpt-4o");
// OR GitHub Copilot (MAUI only — self-contained auth with SecureStorage)
opts.AddGithubCopilotChatClient();
opts.SetMessageStore<MyMessageStore>(); // optional — ChatLookupAITool added automatically
});
AddStaticOpenAIChatClient() for OpenAI-compatible APIs, AddGithubCopilotChatClient() for GitHub Copilot on MAUISetChatClientProvider<T>() is for custom providers — if not set and no built-in provider is used, the default InjectedChatClientProvider resolves IChatClient from DISetMessageStore<T>() is optional — enables persistent history; the ContextProvider automatically adds ChatLookupAITool when a store is presentAddVoiceSelectionTools() is optional — registers VoiceSelectionContextProvider, giving the AI tools to list voices, play samples, and change its own voiceAddContextProvider<T>() registers additional IContextProvider implementationsSetSoundProvider<T>() registers a custom ISoundProvider implementationIContextProvider implementations registered in DI (a ContextProvider is auto-registered)Simple approach — register IChatClient in DI (the default provider resolves it automatically):
builder.Services.AddChatClient(new OpenAIClient("your-api-key").GetChatClient("gpt-4o").AsIChatClient());
Advanced approach — implement IChatClientProvider for on-demand auth or token refresh:
public class MyChatClientProvider : IChatClientProvider
{
public async Task<IChatClient> GetChatClient(CancellationToken cancelToken = default)
{
// Obtain/refresh tokens, authenticate if needed, build client
return new OpenAIChatClient(...);
}
}
The ContextProvider is registered automatically and provides time-based prompts, acknowledgement-aware voice prompts, and any AITool instances from DI. To add custom system prompts, tools, or modify speech settings, implement IContextProvider using the visitor pattern and register it:
public class MyContextProvider : IContextProvider
{
public Task Apply(AiContext context)
{
context.SystemPrompts.Add("You are a helpful assistant for our company.");
// context.Tools.Add(...) to add AI tools
// context.Acknowledgement is available to adjust behavior per mode
// context.QuietWords — modify quiet words for voice interruption
// context.SpeechToTextOptions — set/override speech recognition options
// context.TextToSpeechOptions — set/override text-to-speech options
return Task.CompletedTask;
}
}
// Register in DI — multiple providers are supported, executed in sequence
builder.Services.AddShinyAiConversation(opts =>
{
opts.AddContextProvider<MyContextProvider>();
});
public class MyMessageStore : IMessageStore
{
public Task Store(string? userTriggeringMessage, ChatResponse response, CancellationToken cancellationToken) { ... }
public Task Clear(DateTimeOffset? beforeDate = null) { ... }
public Task<IReadOnlyList<AiChatMessage>> Query(
string? messageContains = null,
DateTimeOffset? fromDate = null,
DateTimeOffset? toDate = null,
int? limit = null,
CancellationToken cancellationToken = default) { ... }
}
// Check access before using voice features
var access = await aiService.RequestAccess();
if (access != AccessState.Available)
{
// Speech is not available — handle accordingly
return;
}
// Send a text message
await aiService.TalkTo("What's the weather?", cancellationToken);
// Listen via microphone and send to AI
await aiService.ListenAndTalk(cancellationToken);
// Wake word detection (loops until stopped)
await aiService.StartWakeWord("Hey Assistant");
aiService.StopWakeWord();
// Query chat history
var history = await aiService.GetChatHistory(limit: 25);
var filtered = await aiService.GetChatHistory(messageContains: "weather", startDate: yesterday);
// Clear history
await aiService.ClearChatHistory();
await aiService.ClearChatHistory(beforeDate: oneWeekAgo);
// React to state changes
aiService.StatusChanged += (state) => { /* update UI with new AiState */ };
aiService.AiResponded += (response) =>
{
// response.Response — the complete ChatResponse
// response.Response.Text — the text content of the response
// response.WasReadAloud — whether TTS was used
// response.ExpectsResponse — true if AI asked a question (will auto-listen for reply)
};
| Mode | Behavior |
|------|----------|
| None | No audio feedback or TTS |
| AudioBlip | Short sound effects at state transitions |
| LessWordy | TTS with "be concise" system prompt injected |
| Full | TTS with full unmodified responses |
| State | Description |
|-------|-------------|
| Idle | Ready for input |
| Listening | Actively listening for speech |
| Thinking | Waiting for AI to process |
| Responding | AI is streaming its response |
IContextProvider.Apply(AiContext) — not set directly on the serviceaddAiLookupTool: true to SetMessageStore to allow the AI to search past conversationsdevops
Guide for implementing push notifications in .NET MAUI apps using Shiny.Push (native FCM/APNs) and Shiny.Push.AzureNotificationHubs
tools
Cross-platform local notification management for .NET MAUI apps using Shiny, supporting scheduled, repeating, and geofence-triggered notifications with channels, badges, and interactive actions.
tools
GPS tracking, geofence monitoring, and motion activity recognition for .NET MAUI, iOS, and Android using Shiny.Locations
data-ai
Background job scheduling and execution for .NET MAUI (iOS/Android native OS schedulers) and in-process jobs for plain .NET, Linux, macOS, and Blazor WASM using Shiny.Jobs