一键导入
shiny-contactstore
Generate code using Shiny.Contacts for cross-platform device contact access with CRUD, LINQ queries, and Shiny.Core permissions
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Generate code using Shiny.Contacts for cross-platform device contact access with CRUD, LINQ queries, and Shiny.Core permissions
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Shiny BluetoothLE client/central operations for scanning, connecting, and communicating with BLE peripherals
Core infrastructure, hosting, DI, key-value stores, lifecycle hooks, and platform abstractions for Shiny on .NET MAUI, iOS, Android, Mac Catalyst, macOS, Windows, Linux, and Blazor WebAssembly
Guide for generating code that uses Shiny.NET HTTP Transfers for background uploads and downloads on iOS/Android, Windows, Linux, macOS, and Blazor WASM (Service Worker Background Sync)
GPS tracking, geofence monitoring, and motion activity recognition for .NET MAUI, iOS, and Android using Shiny.Locations
Cross-platform local notification management for .NET MAUI apps using Shiny, supporting scheduled, repeating, and geofence-triggered notifications with channels, badges, and interactive actions.
Guide for generating code that uses Shiny.Data.Sync for reliable, background-capable bidirectional JSON sync over HTTP on iOS, Android, Windows, Linux, macOS, and Blazor WASM
| name | shiny-contactstore |
| description | Generate code using Shiny.Contacts for cross-platform device contact access with CRUD, LINQ queries, and Shiny.Core permissions |
| auto_invoke | true |
| triggers | ["contact store","contacts","IContactStore","ContactStore","AddContactStore","Shiny.Contacts","device contacts","read contacts","write contacts","contact query","contact LINQ","contacts AI tools","Shiny.Contacts.Extensions.AI","AddContactsAITools","ContactAITools","ContactAICapabilities"] |
You are an expert in Shiny.Contacts, a cross-platform library for accessing device contacts on Android and iOS.
Invoke this skill when the user wants to:
GitHub: https://github.com/shinyorg/shiny
NuGet: Shiny.Contacts
Namespace: Shiny.Contacts
Shiny.Contacts provides:
AccessState modeldotnet add package Shiny.Contacts
The app must call .UseShiny() (from Shiny.Hosting.Maui) so platform services like permissions are wired up.
using Shiny;
builder.UseShiny();
builder.Services.AddContactStore();
Android — Add to AndroidManifest.xml:
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
iOS — Add to Info.plist:
<key>NSContactsUsageDescription</key>
<string>This app needs access to your contacts.</string>
Permissions use Shiny.Core's Shiny.AccessState model. IContactStore exposes two members:
// Request access (triggers OS prompt if needed)
var access = await contactStore.RequestAccess();
if (access != AccessState.Available)
{
// Handle denied / restricted
return;
}
// Check current state without prompting
var current = contactStore.GetCurrentAccess();
AccessState.Available — both read and write access grantedAccessState.Restricted — only read or only write granted (not both)AccessState.Denied — neither read nor write grantedAccessState.Available — contacts access authorizedAccessState.Denied — contacts access deniedAccessState.Restricted — contacts access restricted or limitedAccessState.Unknown — not yet determinedpublic interface IContactStore
{
AccessState GetCurrentAccess();
Task<AccessState> RequestAccess(CancellationToken ct = default);
Task<IReadOnlyList<Contact>> GetAll(CancellationToken ct = default);
Task<Contact?> GetById(string contactId, CancellationToken ct = default);
IQueryable<Contact> Query();
Task<string> Create(Contact contact, CancellationToken ct = default);
Task Update(Contact contact, CancellationToken ct = default);
Task Delete(string contactId, CancellationToken ct = default);
}
// Query extensions
Task<IReadOnlyList<char>> contactStore.GetFamilyNameFirstLetters(CancellationToken ct = default);
The library translates LINQ predicates to native queries where possible, with in-memory fallback.
// Filter by name
var results = contactStore.Query()
.Where(c => c.GivenName.Contains("John"))
.ToList();
// Filter by phone number
var results = contactStore.Query()
.Where(c => c.Phones.Any(p => p.Number.Contains("555")))
.ToList();
// Filter by email
var results = contactStore.Query()
.Where(c => c.Emails.Any(e => e.Address.Contains("@example.com")))
.ToList();
// Combine filters
var results = contactStore.Query()
.Where(c => c.GivenName.StartsWith("J") && c.FamilyName.Contains("Smith"))
.ToList();
// Paging
var page = contactStore.Query()
.Where(c => c.FamilyName.StartsWith("A"))
.Skip(10)
.Take(20)
.ToList();
Supported operations: Contains, StartsWith, EndsWith, Equals
Filterable properties: GivenName, FamilyName, MiddleName, NamePrefix, NameSuffix, Nickname, DisplayName, Note
Filterable collections: Phones (by Number), Emails (by Address)
var contact = new Contact
{
GivenName = "John",
FamilyName = "Doe",
Note = "Met at conference"
};
contact.Phones.Add(new ContactPhone("555-1234", PhoneType.Mobile));
contact.Emails.Add(new ContactEmail("john@example.com", EmailType.Work));
string id = await contactStore.Create(contact);
var contact = await contactStore.GetById(id);
contact.GivenName = "Jane";
await contactStore.Update(contact);
await contactStore.Delete(contactId);
| Property | Type |
|---|---|
| Id | string? |
| NamePrefix | string? |
| GivenName | string? |
| MiddleName | string? |
| FamilyName | string? |
| NameSuffix | string? |
| Nickname | string? |
| DisplayName | string |
| Note | string? |
| Organization | ContactOrganization? |
| Photo | byte[]? (see note below) |
| Thumbnail | byte[]? |
| Phones | List<ContactPhone> |
| Emails | List<ContactEmail> |
| Addresses | List<ContactAddress> |
| Dates | List<ContactDate> |
| Relationships | List<ContactRelationship> |
| Websites | List<ContactWebsite> |
PhoneType: Home, Mobile, Work, FaxWork, FaxHome, Pager, Other, Custom
EmailType: Home, Work, Other, Custom
AddressType: Home, Work, Other, Custom
ContactDateType: Birthday, Anniversary, Other, Custom
RelationshipType: Father, Mother, Parent, Brother, Sister, Child, Friend, Spouse, Partner, Assistant, Manager, Other, Custom
GetAll() and Query() populate Thumbnail only; Photo (the full-resolution image) is null on these bulk reads. Decoding every contact's full photo into a byte[] at once spikes memory and can get the app OOM/jetsam-killed on a real device with many photo contacts. To get the full Photo, fetch the single contact with GetById(id) (which populates both Thumbnail and Photo). Bind list rows to Thumbnail and load Photo on a detail screen.
Reading Note and Relationships on iOS requires the com.apple.developer.contacts.notes entitlement. The library auto-detects this at runtime. If absent, Note returns null and Relationships is empty.
await contactStore.RequestAccess() and check for AccessState.Available before any CRUD operationQuery().Where(...) over GetAll() when filtering, as it uses native queriesAccessState.Restricted means partial access (read-only or write-only)Thumbnail, not Photo — bulk reads (GetAll/Query) only load Thumbnail; get the full Photo from GetById on a detail screenIContactStore via primary constructorThe optional Shiny.Contacts.Extensions.AI package exposes IContactStore as Microsoft.Extensions.AI tool functions (AIFunctions) for LLM agents. You opt-in exactly which operations the model can see — a read/write allow-list you control on behalf of the agent (not an OS permission prompt; the platform contact permission must already be granted). Read-only by default; write is opt-in. AOT-compatible (hand-built schemas, JsonNode results — no reflection).
using Shiny.Contacts;
using Shiny.Contacts.Extensions.AI;
builder.Services.AddContactStore(); // registers IContactStore
builder.Services.AddContactsAITools(tools => tools
.AddContacts(ContactAICapabilities.ReadWrite) // Read is the default; ReadWrite adds create/update/delete
);
// resolve the bundle and pass the tools to any IChatClient
var tools = sp.GetRequiredService<ContactAITools>().Tools;
var response = await chatClient.GetResponseAsync(
messages,
new ChatOptions { Tools = [.. tools] }
);
Key types:
AddContactsAITools(Action<IContactAIToolBuilder>) — DI extension; throws if nothing is added.IContactAIToolBuilder — AddContacts(ContactAICapabilities).ContactAICapabilities [Flags] — None, Read (default), Write, ReadWrite.ContactAITools — resolve from DI; .Tools is IReadOnlyList<AITool>.Generated tools (only for opted-in capabilities): search_contacts (free-text over name/phone/email), get_contact (by id), create_contact, update_contact, delete_contact.
The AI tools assume permissions are already granted — they do not trigger the platform permission UI (needs a foreground activity). Call
IContactStore.RequestAccess(...)from the app before invoking the agent.delete_contactis irreversible — instruct the model to confirm with the user first.