Instruction file imported from rkttu/cadenza (
.cursor/rules/cadenza.mdc). Copyright stays with the author.
Cadenza skill
Cadenza is .NET's answer to "Python for scripting" with "Go-like single-binary distribution". Every Cadenza program is one .cs file that runs with dotnet run app.cs and publishes to a ~30–40 MB self-contained binary with dotnet publish app.cs -r <rid> -c Release.
Repo: https://github.com/rkttu/cadenza
When to suggest Cadenza
Pick Cadenza when the user wants:
| Goal | SDK | Use case |
|---|---|---|
| CLI / shell automation / build glue | Cadenza |
git deploy guards, file processing, devops scripts |
| Background service / daemon | Cadenza.Worker |
heartbeat, polling, scheduled work |
| Minimal HTTP API / webhook | Cadenza.Web |
a 30-line REST endpoint, internal tool |
| MCP server (AI tool integration) | Cadenza.Mcp |
tools / resources / prompts for Claude Desktop, Cursor, VS Code AI |
| Local AI agent | Cadenza.Agent |
OpenAI-compatible HTTP server fronting Ollama / OpenAI / Anthropic / Azure OpenAI — Codex / Aider / Continue / Cursor speak to it as if it were OpenAI |
Skip Cadenza for multi-project solutions, libraries that ship as DLLs, or anything that needs a full csproj. Cadenza is for the "single file = whole program" case.
Critical: exact version pinning
MSBuild SDK references do NOT support wildcards (1.*). Always pin an exact SemVer version. Latest pin: 1.0.15 across all five SDKs.
#:sdk Cadenza@1.0.15 // console
#:sdk Cadenza.Worker@1.0.15 // worker
#:sdk Cadenza.Web@1.0.15 // web
#:sdk Cadenza.Mcp@1.0.15 // MCP server
#:sdk Cadenza.Agent@1.0.15 // AI agent (OpenAI-compatible HTTP)
Tier 1 — bare names per variant (no namespace prefix needed)
Cadenza(console):Run(cmd),Capture(cmd),ReadText(path),WriteText(path, content),Glob(pattern),TempDir(), plusWriteLine/Write/ReadLine.Cadenza.Worker:Run(Func<CT, Task>)(starts host + BackgroundService),Config<T>(key), plus sharedReadText/WriteText/Glob/WriteLine.Log.Info/Warn/Error/Debugvia ILogger.Cadenza.Web:Get/Post/Put/Delete/Map(path, handler),Run()(starts server).Web.App/Web.Servicesfor raw access.Cadenza.Mcp:Tool(name, desc, handler),Resource(uri, name, handler),Prompt(name, desc, handler),Run()(stdio).Log.*routed to stderr (never useWriteLinehere).Cadenza.Agent:Tool(name, desc, handler),SystemPrompt(text),UseOllama(model)/UseOpenAi(model)/UseAnthropic(model)/UseAzureOpenAi(endpoint, deployment)/UseChatClient(IChatClient),Run()(HTTP server onlocalhost:8080— servesPOST /v1/chat/completionsANDPOST /v1/responsesfor Codex CLI),ChatLoop()(REPL),Reply(prompt)(one-shot). Server-sideTool(...)auto-invokes on Chat Completion only; Codex brings its own toolset.
Gotchas (these have bitten users)
- MCP scripts NEVER write to stdout. stdio carries JSON-RPC; stray text disconnects the client.
Cadenza.Mcpintentionally does not exposeWriteLineas a bare name. UseLog.*(stderr). - Wildcards in
#:sdkdon't work. Pin exact. Useglobal.jsonmsbuild-sdksif you want to centralize. - NativeAOT is opt-in. Add
#:property PublishAot=trueat top of script. All deps must be AOT-compatible (Cadenza's surface already is). - JSON requires
JsonSerializerContext(source-generated).Http.GetJson<T>(url, ctx)andJson.Parse<T>(json, ctx)take an explicit context — no reflection overloads. Prompt.*in CI: setCADENZA_PROMPT_<NAME>env var (NAME = uppercased question, non-alphanumerics →_).- Working directory is the directory holding the
.csfile — globs and relative paths resolve from there.
Canonical patterns
Console: git-aware deploy gate
#!/usr/bin/env dotnet run
#:sdk Cadenza@1.0.15
var branch = Capture("git rev-parse --abbrev-ref HEAD").Trim();
if (branch != "main") { WriteLine($"Refusing to deploy from '{branch}'"); Env.Exit(1); }
if (Run("dotnet test", throwOnError: false) != 0) Env.Exit(2);
Run("dotnet publish -c Release -o ./dist", throwOnError: true);
Console: AOT-clean HTTP fetch with typed JSON
#!/usr/bin/env dotnet run
#:sdk Cadenza@1.0.15
using System.Text.Json.Serialization;
Http.Client.DefaultRequestHeaders.UserAgent.ParseAdd("my-script/1.0");
var repo = await Http.GetJson<Repo>("https://api.github.com/repos/dotnet/runtime", Ctx.Default);
WriteLine($"{repo.full_name}: {repo.stargazers_count:N0} stars");
record Repo(string full_name, int stargazers_count);
[JsonSerializable(typeof(Repo))]
partial class Ctx : JsonSerializerContext { }
Worker: periodic loop with graceful shutdown
#!/usr/bin/env dotnet run
#:sdk Cadenza.Worker@1.0.15
await Run(async (ct) =>
{
while (!ct.IsCancellationRequested)
{
Log.Info($"tick {DateTime.UtcNow:O}");
await Task.Delay(TimeSpan.FromSeconds(30), ct);
}
});
Web: Minimal API with record binding
#!/usr/bin/env dotnet run
#:sdk Cadenza.Web@1.0.15
Get("/", () => "hello");
Get("/health", () => new { status = "ok", time = DateTime.UtcNow });
Post("/echo", (EchoRequest req) => new EchoResponse(req.Message.ToUpper()));
await Run();
record EchoRequest(string Message);
record EchoResponse(string Echoed);
MCP server for Claude Desktop / Cursor / VS Code AI
#!/usr/bin/env dotnet run
#:sdk Cadenza.Mcp@1.0.15
Tool("read_file", "Read a UTF-8 text file from disk",
(string path) => ReadText(path));
Tool("list_files", "List files matching a glob pattern (e.g., **/*.cs)",
(string pattern) => Glob(pattern).ToArray());
await Run();
Register with the client:
{
"mcpServers": {
"cadenza-files": {
"command": "dotnet",
"args": ["run", "/absolute/path/to/server.cs"]
}
}
}
AI agent backing Codex / Aider / Continue / Cursor
#!/usr/bin/env dotnet run
#:sdk Cadenza.Agent@1.0.15
ServedModelName = "cadenza-codex";
SystemPrompt("You are a coding assistant. Ground answers in real files.");
Tool("read_file", "Read a UTF-8 text file from the working directory",
(string path) => ReadText(path));
Tool("list_files", "List files matching a glob pattern (e.g., src/**/*.cs)",
(string pattern) => Glob(pattern).ToArray());
UseOllama("qwen2.5-coder:7b"); // or UseOpenAi / UseAnthropic / UseAzureOpenAi
await Run();
Point the editor at it:
export OPENAI_BASE_URL=http://localhost:8080/v1
export OPENAI_API_KEY=any-non-empty-string
codex # or aider, continue, cursor, sgpt, …
Deployment
# Default: SCD + R2R + SingleFile + Compression (~30-40 MB)
dotnet publish app.cs -r linux-x64 -c Release
# NativeAOT opt-in: add `#:property PublishAot=true` to the top of the script,
# then publish normally — produces a ~10-30 MB native binary.
Supported RIDs: linux-x64, linux-arm64, osx-x64, osx-arm64, win-x64, win-arm64.
Tier 2 — prefixed modules (shared by all variants)
Sh.Run/Capture/Pipe/RunAsync/CaptureAsync— shell exec with async variantsFs.ReadText/WriteText/ReadBytes/WriteBytes/Exists/Delete/Move/Copy/MakeDir/Glob/TempDir/ReadTextAsyncHttp.GetJson<T>/PostJson<TReq,TResp>/GetText/Download,Http.Client(shared singleton)Env.Get/Args/Cwd/Exit/IsCi/IsWindows/IsMacOS/IsLinuxPrompt.Confirm/Select/Text/Password(console + worker only)Json.Parse<T>/Stringify<T>(always takes aJsonSerializerContext)
Reference
- Project README: https://github.com/rkttu/cadenza/blob/main/README.md
- Spec (Korean): https://github.com/rkttu/cadenza/blob/main/spec.md
- Wiki (user docs): https://github.com/rkttu/cadenza/wiki
- Publishing guide: https://github.com/rkttu/cadenza/wiki/Deployment-Single-Binary
- Troubleshooting: https://github.com/rkttu/cadenza/wiki/Troubleshooting
- Samples: https://github.com/rkttu/cadenza/tree/main/samples