.claude/skills/ztools-guide/SKILL.md
Guides how to integrate the zTools package for ChatGPT, DALL-E image generation, file upload (S3), slug generation, email sending, and document validation in a .NET 8 project. Use when the user wants to use AI features, upload files, generate slugs, send emails, or understand zTools integration.
npx skillsauth add emaginebr/NNews ztools-guideInstall 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.
You are an expert assistant that helps developers integrate the zTools NuGet package for utility services (ChatGPT, DALL-E, file storage, slug generation, email, document validation) in .NET 8 Web API projects.
The user may provide a specific question or context as argument: $ARGUMENTS
If no argument is provided, present a complete overview of the zTools integration.
When the user asks about zTools, use this knowledge base to provide accurate, contextual guidance.
Install: dotnet add package zTools --version 0.3.1
public class zToolsetting { public string ApiUrl { get; set; } }
public class ChatGPTSetting { public string ApiUrl; public string ApiKey; public string Model; }
public class MailerSendSetting { /* MailerSend API config */ }
public class S3Setting { /* S3 bucket, credentials, endpoint config */ }
public class ChatMessage
{
public string Role { get; set; } // "system", "user", or "assistant"
public string Content { get; set; }
}
public class ChatGPTRequest { public string Model; public List<ChatMessage> Messages; }
public class ChatGPTResponse { public List<ChatGPTChoice> Choices; public ChatGPTUsage Usage; }
public class ChatGPTMessageRequest { public string Message; }
public class ChatGPTErrorResponse { /* Error handling */ }
public class DallERequest
{
public string Prompt { get; set; } // Image description
public string Model { get; set; } // "dall-e-3"
public string Size { get; set; } // "1024x1024", "1024x1792", "1792x1024"
public string Quality { get; set; } // "standard" or "hd"
public string Style { get; set; } // "vivid" or "natural"
}
public class DallEResponse { public List<DallEImageData> Data; }
public class DallEImageData { public string Url; public string RevisedPrompt; }
public class MailerInfo { /* Email composition and sending */ }
public class MailerRecipientInfo { /* Recipient info */ }
public class MailerErrorInfo { /* Error handling */ }
Install: dotnet add package zTools --version 0.3.1
public interface IChatGPTClient
{
Task<string> SendMessageAsync(string question);
Task<string> SendConversationAsync(List<ChatMessage> messages);
Task<DallEResponse> GenerateImageAdvancedAsync(DallERequest request);
}
public interface IStringClient
{
Task<string> GenerateSlugAsync(string text); // "Hello World!" -> "hello-world"
}
public interface IFileClient
{
Task<string> UploadFileAsync(string bucketName, IFormFile file); // Returns file name
Task<string> GetFileUrlAsync(string bucketName, string fileName); // Returns public URL
}
public interface IMailClient { /* Email validation and sending via MailerSend */ }
public interface IDocumentClient { /* CPF/CNPJ validation */ }
Implementations: ChatGPTClient, StringClient, FileClient, MailClient, DocumentClient.
{
"zTools": {
"ApiURL": "http://localhost:5001"
}
}
Docker: use "ApiURL": "http://ztools-api:80".
using zTools.ACL;
using zTools.ACL.Interfaces;
using zTools.DTO.Settings;
services.Configure<zToolsetting>(configuration.GetSection("zTools"));
services.AddHttpClient();
// Register only the clients you need
services.AddScoped<IChatGPTClient, ChatGPTClient>(); // ChatGPT + DALL-E
services.AddScoped<IStringClient, StringClient>(); // Slug generation
services.AddScoped<IFileClient, FileClient>(); // File upload/retrieval
services.AddScoped<IMailClient, MailClient>(); // Email sending
services.AddScoped<IDocumentClient, DocumentClient>(); // Document validation
public async Task<string> AskQuestion(string question)
{
return await _chatGPTClient.SendMessageAsync(question);
}
using zTools.DTO.ChatGPT;
public async Task<string> GenerateContent(string userPrompt)
{
var messages = new List<ChatMessage>
{
new ChatMessage { Role = "system", Content = "You are a content writer. Return only valid JSON." },
new ChatMessage { Role = "user", Content = userPrompt }
};
var response = await _chatGPTClient.SendConversationAsync(messages);
// Clean markdown wrapper if present
var clean = response.Trim();
if (clean.StartsWith("```json"))
{
clean = clean.Substring(7);
if (clean.EndsWith("```"))
clean = clean.Substring(0, clean.Length - 3);
clean = clean.Trim();
}
return clean;
}
var messages = new List<ChatMessage>
{
new ChatMessage { Role = "system", Content = "You are a helpful assistant." },
new ChatMessage { Role = "user", Content = "What is .NET?" },
new ChatMessage { Role = "assistant", Content = ".NET is a free, open-source developer platform..." },
new ChatMessage { Role = "user", Content = "How does dependency injection work in .NET?" }
};
var response = await _chatGPTClient.SendConversationAsync(messages);
public async Task<string?> GenerateImage(string description)
{
var request = new DallERequest
{
Prompt = description,
Model = "dall-e-3",
Size = "1024x1024",
Quality = "standard",
Style = "vivid"
};
var response = await _chatGPTClient.GenerateImageAdvancedAsync(request);
if (response?.Data == null || !response.Data.Any())
return null;
return response.Data.First().Url; // Temporary URL — must persist!
}
Important: DALL-E returns temporary URLs. Always download and re-upload to your own storage.
public async Task<string?> GenerateAndUploadImage(string imagePrompt)
{
// 1. Generate image
var imageResponse = await _chatGPTClient.GenerateImageAdvancedAsync(new DallERequest
{
Prompt = imagePrompt, Model = "dall-e-3",
Size = "1024x1024", Quality = "standard", Style = "vivid"
});
if (imageResponse?.Data == null || !imageResponse.Data.Any())
return null;
var imageUrl = imageResponse.Data.First().Url;
if (string.IsNullOrWhiteSpace(imageUrl)) return null;
// 2. Download temporary image
var imageBytes = await _httpClient.GetByteArrayAsync(imageUrl);
// 3. Upload to S3
var fileName = $"ai-generated-{Guid.NewGuid()}.png";
using var stream = new MemoryStream(imageBytes);
IFormFile formFile = new FormFileWrapper(stream, fileName, "image/png");
var uploadedName = await _fileClient.UploadFileAsync("MyBucket", formFile);
return await _fileClient.GetFileUrlAsync("MyBucket", uploadedName);
}
[Route("api/[controller]")]
[ApiController]
public class ImageController : ControllerBase
{
private readonly IFileClient _fileClient;
public ImageController(IFileClient fileClient) { _fileClient = fileClient; }
[RequestSizeLimit(100_000_000)]
[HttpPost("upload")]
[Authorize]
public async Task<ActionResult<string>> Upload(IFormFile file)
{
if (file == null || file.Length == 0)
return BadRequest("No file uploaded");
var fileName = await _fileClient.UploadFileAsync("MyBucket", file);
var url = await _fileClient.GetFileUrlAsync("MyBucket", fileName);
return Ok(url);
}
}
public async Task<string> GenerateUniqueSlug(string title, Func<string, bool> slugExists)
{
string slug;
int counter = 0;
do
{
slug = await _stringClient.GenerateSlugAsync(title);
if (counter > 0) slug += counter.ToString(); // "my-slug", "my-slug1", "my-slug2"
counter++;
} while (slugExists(slug));
return slug;
}
internal class FormFileWrapper : IFormFile
{
private readonly Stream _stream;
private readonly string _fileName;
private readonly string _contentType;
public FormFileWrapper(Stream stream, string fileName, string contentType)
{ _stream = stream; _fileName = fileName; _contentType = contentType; }
public string ContentType => _contentType;
public string ContentDisposition => $"form-data; name=\"file\"; filename=\"{_fileName}\"";
public IHeaderDictionary Headers => new HeaderDictionary();
public long Length => _stream.Length;
public string Name => "file";
public string FileName => _fileName;
public void CopyTo(Stream target) => _stream.CopyTo(target);
public Task CopyToAsync(Stream target, CancellationToken ct = default) => _stream.CopyToAsync(target, ct);
public Stream OpenReadStream() => _stream;
}
public async Task<MyDto> GenerateWithAI(string prompt)
{
var messages = new List<ChatMessage>
{
new ChatMessage { Role = "system", Content = "Return ONLY valid JSON: {\"title\":\"\",\"body\":\"\",\"tags\":\"\"}" },
new ChatMessage { Role = "user", Content = prompt }
};
var response = await _chatGPTClient.SendConversationAsync(messages);
// Strip markdown wrapper, then deserialize
var clean = response.Trim();
if (clean.StartsWith("```json")) { clean = clean[7..]; if (clean.EndsWith("```")) clean = clean[..^3]; clean = clean.Trim(); }
return JsonSerializer.Deserialize<MyDto>(clean, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
}
public async Task<ArticleResult> CreateArticleWithAI(string prompt, bool withImage)
{
var content = await GenerateWithAI(prompt);
string? imageUrl = null;
if (withImage && !string.IsNullOrEmpty(content.ImagePrompt))
imageUrl = await GenerateAndUploadImage(content.ImagePrompt);
var slug = await _stringClient.GenerateSlugAsync(content.Title);
return new ArticleResult { Title = content.Title, Content = content.Body, Slug = slug, ImageUrl = imageUrl };
}
| Issue | Cause | Solution |
|-------|-------|----------|
| ChatGPT returns empty | zTools API unreachable | Verify zTools:ApiURL in appsettings |
| DALL-E URL expired | Temporary URLs not persisted | Download and re-upload to S3 via IFileClient |
| GenerateSlugAsync empty | Empty input | Validate input before calling |
| File upload fails | File too large | Add [RequestSizeLimit] to controller |
| DI error | Missing registration | Add services.AddScoped<IChatGPTClient, ChatGPTClient>() |
| JSON wrapped in markdown | ChatGPT formatting | Strip ```json / ``` before deserializing |
| Slug collision | Duplicate in DB | Use retry loop with counter suffix |
IFileClientdocumentation
Generates a comprehensive, standardized README.md for any project. Use when the user wants to create or regenerate a README file following the project's documentation standard.
development
Guides how to integrate the NNews NuGet package for consuming the NNews CMS API in a .NET 8 project. Use when the user wants to consume articles, categories, tags, images, or AI-powered content generation from the NNews API.
tools
Guides how to integrate the NAuth package for user authentication in a .NET 8 project. Use when the user wants to add authentication, configure NAuth, use IUserClient, or understand the NAuth authentication flow.
data-ai
Creates Mermaid diagrams (.mmd) and generates PNG images from them. Use when the user wants to create flowcharts, sequence diagrams, class diagrams, ER diagrams, Gantt charts, or any other Mermaid-supported diagram.