Live demos¶
Every demo on this page is a real Capy library + script + generated output. Where the output is something a browser can render (HTML, CSS, Markdown, Mermaid), the rendered version is embedded inline below the code so you can see exactly what comes out.
For non-renderable targets (Python, SQL, JSON, …) you see the source-and-generated pair.
All demos live in the samples/
directory if you want to clone and run them yourself.
✨ New-feature showcase — 22 examples in the playground¶
The round-1, round-2 and round-6 language features each ship with a
focused, runnable example. They're grouped under the ✨ Features
category at the top of the playground dropdown — pick
one, read the source, edit it live, and watch the output change. Every
example below is a real samples/<id>/ directory with a golden-tested
output.
| Example | Feature it shows off |
|---|---|
html-xml-parser |
parse HTML / XML — one generic element function matches any <tag>…</tag> via a capture-bound block_close_seq + attribute* nonterminal; mismatched nesting is a hard error |
template-sugar |
template … end instead of multi-line backtick write literals |
optional-args |
trailing capture with a default — one function, many call shapes |
line-mapping |
${line} / ${col} render locals for source↔output mapping |
string-decoded |
${decoded} round-trips \n / \t and embedded \" quotes |
verbatim-pre |
block_verbatim — raw byte-fidelity code blocks |
backtick-codespan |
escapable backticks so \`code\` survives inside a capture |
utf8-prose |
bare UTF-8 prose (accents, CJK, emoji) — no quoting |
multiline-strings |
multi-line backtick captures in user scripts |
inline-markdown |
group types — link [text](url), bold **x**, strike ~~x~~ |
feature-faq |
FAQ: optional open flag + template + decode/escape |
feature-pricing |
pricing tiers with optional period / CTA defaults |
feature-callouts |
note / tip / warning callouts via one optional arg |
feature-svg-badge |
inline SVG captured verbatim, byte-for-byte |
feature-menu-i18n |
multilingual menu — UTF-8 prose + tail capture |
feature-changelog |
release notes: ${line} + optional kind + verbatim block |
feature-social-card |
social preview cards with an optional theme |
feature-stepper |
onboarding stepper stamping ${line} for scroll-sync |
feature-glossary |
definition list: multi-line backticks + code spans |
feature-quiz |
multiple-choice quiz with an optional correct flag |
mcp-widgets |
nested blocks + templates + decode/escape + context, together |
math-plots |
template … end + canvas plots (rendered live below) |
Open the playground → and choose the ✨ Features category. New to the language features? Start with What's new.
🔢 Indexed reads — lists & maps read back by index or key¶
A [<expr>] index now works in read position, mirroring the
write side: ${context.buf[i]}, ${context.palette[name]},
${context.memo[(sub n 1)]}, ${context.stack[-1]} and nested
${context.grid[r][c]}. Map parents key on the index's string form,
list parents key on an integer (negative counts from the end);
missing keys / out-of-range indices render empty. These ten samples —
grouped under the 🔢 Indexed reads category in the
playground — turn the idiom into real programs:
| Example | What it indexes |
|---|---|
word-frequency |
map read-modify-write: set context.counts[w] (add context.counts[w] 1) |
memo-fib |
computed indices ${context.memo[(sub n 1)]} over a memo table |
stack-top |
negative indices ${context.stack[-1]} / [-2] to peek a stack |
enum-lookup |
list-by-index lookup tables (days[d], months[m]) |
leaderboard |
fixed positions [0]/[1]/[2] + [-1] for last place |
color-palette |
map-by-name ${context.palette[name]} to emit colored chips |
symbol-types |
a symbol table keyed by captured name |
register-allocator |
a name→register map read during emission |
grid-game |
a flat board indexed by a computed r*cols + c |
emoji-react |
a key→glyph map read back by name in a template |
See value-position indexing for the design notes and read/write parity rules.
📈 Math plots — plotting equations from a one-line DSL¶
A tiny DSL (plot "sin(x)" … end) turns into a self-contained HTML
page with canvas plots, axes, and an inline plotter. Showcases every
primitive shipped in this release: template … end sugar, the
${escapeHtml} / ${decoded} helpers, UTF-8 prose in titles, and
context.plots accumulation. The library also ships a command
"run" block so capy math-plots run page.plots writes the HTML
next to the source and opens it in the browser.
title "A few familiar shapes"
plot "sin(x)"
domain -6.28 6.28
color "#4ef"
end
plot "sin(x) + 0.5*sin(3*x) + 0.25*sin(5*x)"
domain -6.28 6.28
color "#f4b"
end
plot "exp(-x*x)"
domain -3 3
color "#bf4"
samples 400
end
Rendered live below:
Full library + script: samples/math-plots/.
Walk-through of every primitive it uses: What's new.
🏡 Capy for everyday things — no coding needed¶
These four demos use vocabularies designed for ordinary tasks: a recipe, an invitation, a meal plan, a child's reading log. The "language" you write is plain English with a handful of keywords. Open the iframes to see the polished output.
Source you write:
recipe "Lemon olive oil cake"
serves 8
time "45 minutes"
ingredient "all-purpose flour" "1 1/2 cups"
ingredient "olive oil" "3/4 cup"
ingredient "sugar" "1 cup"
ingredient "lemon zest" "2 tablespoons"
step "Preheat oven to 350F."
step "Whisk flour and sugar."
step "Add oil and lemon zest."
step "Bake for 35-40 minutes."
tip "Glaze with powdered sugar and lemon juice."
end
Generated HTML card (open it, print it, email it):
invite "Maya turns 6!"
host "The Patel family"
when "Saturday, June 14"
time "2:00 - 5:00 pm"
where "Lincoln Park, Pavilion 3"
address "200 Park Avenue, Springfield"
rsvp_by "June 7"
rsvp_to "maya@example.com"
note "There will be a unicorn cake and a butterfly hunt."
note "Wear something you can run in. Sunscreen recommended."
bring "A book to add to Maya's library (any age)"
end
week "March 10 - 16"
serves 4
monday "Sheet-pan salmon with broccoli and lemon"
tuesday "Pasta with brown butter and sage"
wednesday "Black bean tacos with avocado and lime"
thursday "Leftover salmon salads with greens"
friday "Homemade pizza night (kids choose toppings)"
saturday "Slow-cooker chicken stew"
sunday "Roast vegetables and quinoa bowls"
note "Buy fresh fish on Sunday or Monday for best quality."
note "Make extra rice on Wednesday for Thursday lunches."
end
log "Emma's reading log" age 7
goal 500
book "Charlotte's Web" pages 184 rating 5
book "The Wild Robot" pages 277 rating 5
book "Mr. Popper's Penguins" pages 138 rating 4
book "Frog and Toad Together" pages 64 rating 5
book "Junie B. Jones #1" pages 72 rating 3
end
Progress bar fills toward the yearly goal. Stars come from the
rating number. Update through the year by adding more book lines.
Why this matters. You don't need a degree in computer science
to use Capy. The vocabularies above (recipe, serves,
ingredient, step; invite, host, when, where; monday,
tuesday, …) are designed for ordinary tasks — and someone (you,
a teammate, or an AI) can design a new vocabulary for any task you
do more than twice.
Read the non-programmer guide → docs/for-everyone.md
🎚️ Progressive abstraction — pick your level of control¶
Same library, three abstraction levels. Start with a 4-line one-shot; take more control as your needs grow; drop to raw HTML/CSS via escape hatches when the abstraction isn't enough.
landing "Capy"
tagline "Describe what you want. Capy produces what you need."
cta_text "Open the playground"
cta_link "https://olivierdevelops.github.io/capy/playground/"
You declare WHAT; the library decides EVERYTHING else.
landing "Capy"
hero "Capy" "Describe what you want. Capy produces what you need."
feature "Zero default grammar" "Every keyword is defined by the library."
feature "55+ samples" "Recipes, invoices, Android apps."
feature "Browser playground" "Compiler runs as WebAssembly."
feature "MCP server included" "Plug into Claude / Cursor / Zed."
cta "Open the playground" "https://..."
end
You take control of WHICH sections appear and in what order; visual identity stays library-owned.
landing "Capy — Pro"
raw_head "<meta name='theme-color' content='#4f46e5'>"
style_override "body { background: linear-gradient(...); } .hero h1 { ... }"
hero "Capy — Pro" "Same engine. Same grammar. Take exactly the control you need."
feature "..." "..."
feature "Metaprogramming" "Source declares its own DSL primitives."
raw_section "<section style='...'>...custom HTML...</section>"
cta "Open the playground" "..."
raw_footer "<a href='...'>github.com/olivierdevelops/capy</a>"
end
Now you have escape hatches: literal HTML in <head>,
stylesheet overrides, custom sections, replaced footer. The
library never gets in your way.
| Concern | Capy's answer |
|---|---|
| "Tools are great until you need something they didn't anticipate" | Same library exposes Level 1 → 2 → 3. Drop a level, never switch tools. |
| "Different teammates want different control" | Marketing uses Level 1, product uses Level 2, brand uses Level 3 — same library. |
| "Escape hatches are second-class citizens" | raw_head / style_override / raw_section are first-class primitives; libraries are designed around them. |
| "What if I need to go off-script for one launch?" | Drop to Level 3 for that page; revert to Level 1 for the next. |
Full sample → samples/progressive-abstraction/ ·
Pattern docs → progressive-abstraction.md
🧬 Metaprogramming — source extends its own grammar¶
A define NAME ... end block in a Capy source file introduces a new
function. The rest of the source can then call it — no library
change required. Use it to DRY up repetitive boilerplate or
prototype DSL extensions before promoting them to the library.
``
define heading
arg literal "heading"
arg capture text string
write# ${unquote text}
` end
define quote
arg literal "quote"
arg capture text string
arg capture who string
write `> ${unquote text}
— ${unquote who}
` end
define checklist_item
arg literal "todo"
arg capture done ident
arg capture text string
if eq done "yes"
write `- [x] ${unquote text}
else
write- [ ] ${unquote text}
`
end
end
# Use them — library has no `heading`, `quote`, or `todo`.
heading "Today's todos"
todo yes "Ship metaprogramming"
todo no "Update the docs"
quote "Description over implementation." "Capy"
```
| Pattern | Right tool |
|---|---|
| Repetitive boilerplate in one source file | define |
| Reused across many sources in one project | shared .capy file + @import |
| Reused across many projects | library-level function |
| Truly project-specific UI / behavior | define (don't bloat the shared library) |
Source defines OVERRIDE library functions of the same name — use it to specialize without forking.
Full sample → samples/metaprogramming/ ·
Pattern docs →
📦 Cross-platform installer — one source, three shells¶
Declare install steps once; emit install.sh, install.ps1, AND
install.bat from the same 9-line source via multi-file output.
Every platform stays in lock-step because they're regenerated from
one declaration.
#!/usr/bin/env bash
set -euo pipefail
if command -v apt-get >/dev/null 2>&1; then
PM="sudo apt-get install -y"
elif command -v brew >/dev/null 2>&1; then
PM="brew install"
fi
$PM jq
$PM curl
$PM ripgrep
mkdir -p /var/log/capy-agent
export CAPY_AGENT_PORT=8080
# systemd unit emitted here
sudo systemctl enable --now capy-agent
$ErrorActionPreference = "Stop"
choco install -y jq
choco install -y curl
choco install -y ripgrep
New-Item -ItemType Directory -Force -Path "/var/log/capy-agent"
[Environment]::SetEnvironmentVariable("CAPY_AGENT_PORT", "8080", "Machine")
sc.exe create capy-agent binPath= "..." start= auto
sc.exe start capy-agent
- No drift. Add
install "tmux"once → all three scripts get it. - No bash-in-PowerShell horror. Each target speaks its native syntax; the library handles translation.
- CI-friendly. Generate all three in one step, ship them all, let the installer pick the right one at runtime.
🔌 WebSocket server — 10 lines → ~80 lines of Go¶
A chat-server DSL declares routes, typed fields, and abstract
handler verbs (broadcast, announce). The source contains zero
target-language code — no Go fragments, no JSON literals, no embedded
expressions — so the same source survives unchanged when you swap the
library to a Node / Python / Rust ws library.
type ChatMsg struct {
User string `json:"user"`
Text string `json:"text"`
}
type JoinMsg struct {
User string `json:"user"`
}
func wsHandler(hub *Hub) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
conn, _ := upgrader.Upgrade(w, r, nil)
hub.Add(conn); defer hub.Remove(conn); defer conn.Close()
for {
_, raw, err := conn.ReadMessage()
if err != nil { return }
var env Envelope
json.Unmarshal(raw, &env)
switch env.Type {
case "chat":
var m ChatMsg; json.Unmarshal(env.Data, &m)
// verb=broadcast → echo to all clients.
hub.Broadcast("chat", m)
case "join":
var m JoinMsg; json.Unmarshal(env.Data, &m)
// verb=announce → log + broadcast.
log.Printf("join: %+v", m)
hub.Broadcast("join", m)
}
}
}
}
- Zero target-language code in the source. No Go fragments
embedded as strings, no
hub.Broadcast(...)literals to escape. Every handler is an abstract verb the library translates. - Adding a verb = adding a library case. Want
archiveorrate_limit? Add it once to the dispatch switch inlib.capy; every source that mentions the verb gets the new behaviour. - Network plumbing is library territory. Upgrade headers, JSON envelope, dispatch — written once in the library.
- Source = contract. The 10-line source IS the protocol spec. Generate the server, a TypeScript client, and OpenAPI docs from the same declaration.
🌐 Same WebSocket DSL → Go AND Node AND Python¶
The exact same 7-line source produces a runnable echo server in three target languages by swapping just the library file. Spec lives in the source; implementation choice lives in the library.
case strings.HasPrefix(text, "ping "):
payload := strings.TrimPrefix(text, "ping ")
_ = conn.WriteMessage(websocket.TextMessage,
[]byte("pong "+payload))
case strings.HasPrefix(text, "shout "):
payload := strings.TrimPrefix(text, "shout ")
_ = conn.WriteMessage(websocket.TextMessage,
[]byte(strings.ToUpper(payload)))
case strings.HasPrefix(text, "bye "):
return
- Source survives target changes. Same source still parses if you rewrite the library to emit Rust or Elixir tomorrow.
- No translation drift. The dispatcher logic is encoded once per library and applies uniformly to every handler.
- Test once, ship N times. The behaviour contract is one source; pick the runtime per deployment.
⚙️ Custom assembly — one source, many ISAs¶
A 5-op assembly DSL (data, func, write, exit, end) +
three libraries = runnable hello-world on x86_64, ARM64, AND RISC-V64.
New architecture? Add a library; the source survives.
- Source survives ISA changes. The 5-line DSL doesn't mention registers, syscall numbers, or trap instructions. The library owns everything platform-specific.
- Adding a target = adding a library. Want POWER, MIPS,
s390x, or FreeBSD-on-x86_64 (different syscall numbers)?
Write a new
lib-<arch>-<os>.capy. The source doesn't change. (os)and(arch)host primitives let one smart library detect the host at transpile time and emit code for IT — useful for cross-platform install scripts that vary their own asm.- Pedagogical. Students write one program, see it materialise in three ISAs side by side. Differences become visible at the instruction level instead of buried in compiler internals.
🔌 Host capabilities — env vars, CLI args, file reads¶
Libraries can pull values from outside the source at transpile time via
four inner-DSL primitives: (env "NAME"), (arg N), (args),
(read_file "PATH"). The CLI's OSHost exposes real os.Getenv /
os.Args / os.ReadFile; the playground's sandboxed NoOpHost returns
empty values so libraries can't smuggle your filesystem into a browser.
function service
arg literal "service"
arg capture name string
set context.service name
# Pull deploy metadata from the host.
set context.environment (env "ENV")
set context.database_url (env "DATABASE_URL")
set context.version (arg 0)
set context.region (arg 1)
end
function load_keys_from
arg literal "load_keys_from"
arg capture path string
# read_file resolves relative to the script dir.
# Errors abort the transpilation cleanly.
set context.api_keys (read_file path)
end
apiVersion: apps/v1
kind: Deployment
metadata:
name: checkout-api
labels:
environment: production
region: us-west-2
version: v2.3.1
spec:
replicas: 6
template:
spec:
containers:
- name: checkout-api
image: registry/checkout-api:v2.3.1
env:
- name: DATABASE_URL
value: postgres://db.internal/prod
- name: REGION
value: us-west-2
args:
- "--key=sk_live_alpha_e91f2c0a"
- "--key=sk_live_beta_77d4ab19"
- "--key=sk_live_telemetry_2bc88f01"
- Same source, every environment. The 5-line script is identical in staging, production, and prod-eu. The host supplies what changes; the library encodes what doesn't.
- Secrets stay where they belong.
read_filekeeps API keys in a sibling file (gitignored, KMS-encrypted, mounted from a CSI driver) rather than baked into source. - CI-friendly.
capy run lib.capy script.capy $VERSION $REGIONflows naturally from a build pipeline. No template-language gymnastics. - Sandboxed by default. The wasm playground and embedded Go
API both use
NoOpHost— no library author can exfiltrate$AWS_SECRET_ACCESS_KEYfrom a hosted playground. Opt in to real host access withlib.SetHost(infra.OSHost{...}).
📖 Auto-generated library reference docs¶
Annotate functions, args, and types with description "..." and
capy docs lib.capy produces a Markdown reference. The same
renderer powers the DOCS tab in the playground — every library
ships with browsable, regenerable documentation.
extension html
description "Recipe DSL for home cooks. Six keywords produce a
polished printable HTML recipe card."
function recipe
description "Open a new recipe with a title. Wraps the rest
of the file; closed by `end`."
arg literal "recipe"
arg capture title string "Display name of the dish, shown as the H1."
block_closer end
set context.title title
end
function ingredient
description "Add one ingredient. Listed in a two-column grid."
arg literal "ingredient"
arg capture name string "Ingredient name, e.g. `\"olive oil\"`."
arg capture qty string "Quantity with unit, e.g. `\"3/4 cup\"`."
append context.ingredients {name: name, qty: qty}
end
# Library reference (→ `.html`)
Recipe DSL for home cooks. Six keywords produce a polished
printable HTML recipe card.
| | |
|---|---|
| **Output extension** | `.html` |
| **Functions** | 7 |
| **Types** | 0 |
## Functions
### `recipe`
Open a new recipe with a title. Wraps the rest of the file;
closed by `end`.
```
recipe <title>
```
| Argument | Type | Description |
|---|---|---|
| `title` | `string` | Display name of the dish, shown as the H1. |
**Opens an indented block** — body runs until `end`.
### `ingredient`
Add one ingredient. Listed in a two-column grid.
```
ingredient <name> <qty>
```
| Argument | Type | Description |
|---|---|---|
| `name` | `string` | Ingredient name, e.g. `"olive oil"`. |
| `qty` | `string` | Quantity with unit, e.g. `"3/4 cup"`. |
- Onboarding — new contributors read the auto-generated docs and know every function the team uses without grep-ing the library file.
- CI gate — fail the build if
capy docs lib.capydoesn't match the committedLIB_REFERENCE.md. Docs stay in lock-step with implementation. - AI agents — handing the agent a typed library + a human-readable reference improves call-site accuracy.
- Marketing — "50-keyword schema DSL" lands harder when each keyword has a one-sentence explanation a non-expert can read.
See Auto-generated library docs for the full pattern + CI workflow.
🩺 Errors that tell you how to fix them¶
Every Capy error names what went wrong, hints at how to fix it, and points at the exact column with a caret. Did-you-mean suggestions cover typos in DSL keywords, type names, and enum values.
Source:
Capy output:
error: no library function matches token "endpiont"
hint: did you mean "endpoint"?
1 │ endpiont GET "/users"
│ ^
Levenshtein-distance lookup against every library function name. When no close match exists, the hint lists what is available.
Full guide → errors-and-debugging.md
🔗 Source-file imports — @import for content reuse¶
Inside a script.capy, the @import "path" directive splices the
contents of another .capy file into place. Indentation auto-tracks
so imported content nests inside the surrounding block.
# Capy Cafe — Spring 2026
## Mains
- **House pasta** — $16
- **Sheet-pan salmon** — $22
- **Black bean tacos (3)** — $14
## Drinks
- **Espresso** — $3
- **Cappuccino** — $4.50
- **Cold brew** — $5
- **Mint lemonade** — $5
## Desserts
- **Olive oil cake** — $8
- **Chocolate mousse** — $9
- **Affogato** — $7
> All dishes are made fresh.
Use it for: shared sections of authored content (price lists,
menu sections, footers, author bios), splitting long sources for
readability, multi-environment configs. Cycles are detected by
absolute path. @include is a synonym of @import.
Full sample → samples/source-imports/
🎨 Design systems — one component, three frameworks¶
Encode your house style in a library; generate React, Vue, and Svelte with identical visual semantics. Same composition, three frameworks, zero drift.
page "Settings"
button "Save changes" variant primary size lg
button "Discard" variant ghost size lg
card title "Profile"
field email "alice@example.com"
field display "Alice Chen"
field timezone "America/Los_Angeles"
end
card title "Danger zone"
button "Delete account" variant danger size md
end
end
const BUTTON_VARIANT = {
primary: "bg-indigo-600 hover:bg-indigo-700 text-white",
ghost: "bg-transparent hover:bg-slate-100 text-slate-700 border border-slate-200",
danger: "bg-red-600 hover:bg-red-700 text-white",
} as const;
// Button / Card / Field defined identically across frameworks
export default function SettingsPage() {
return (
<main className="max-w-2xl mx-auto p-8 space-y-3">
<h1 className="text-3xl font-bold text-slate-900">Settings</h1>
<Button variant="primary" size="lg">Save changes</Button>
<Button variant="ghost" size="lg">Discard</Button>
<Card title="Profile">
<Field label="email" value="alice@example.com" />
<Field label="display" value="Alice Chen" />
</Card>
<Card title="Danger zone">
<Button variant="danger" size="md">Delete account</Button>
</Card>
</main>
);
}
<script lang="ts">
import Button from "./Button.svelte";
import Card from "./Card.svelte";
import Field from "./Field.svelte";
</script>
<main class="max-w-2xl mx-auto p-8 space-y-3">
<h1 class="text-3xl font-bold text-slate-900">Settings</h1>
<Button variant="primary" size="lg">Save changes</Button>
<Card title="Profile">
<Field label="email" value="alice@example.com" />
...
</Card>
</main>
Identical Tailwind classes across all three. Change a token in the library; every framework regenerates. Add a 4th target (Lit, Solid, Astro, SwiftUI) by writing a 40-line library.
Full sample → samples/design-system-components/ ·
Pattern → docs/design-systems.md
🧪 Backend code with auto-wired tests¶
Every handler declaration produces a Go stub AND a smoke test.
Directory layout and the "every handler has a test" rule live in
the library — new contributors can't deviate.
package handlers
import "net/http"
func Mount(mux *http.ServeMux) {
mux.HandleFunc("GET /users", ListUsers)
mux.HandleFunc("GET /users/{id}", GetUser)
mux.HandleFunc("POST /users", CreateUser)
mux.HandleFunc("DELETE /users/{id}", DeleteUser)
}
// GET /users — returns []User
func ListUsers(w http.ResponseWriter, r *http.Request) {
http.Error(w, "ListUsers not implemented", http.StatusNotImplemented)
}
// ... three more stubs ...
package handlers
import (
"net/http"
"net/http/httptest"
"testing"
)
func Test_ListUsers_RouteRegistered(t *testing.T) {
mux := http.NewServeMux()
Mount(mux)
req := httptest.NewRequest("GET", "/users", nil)
rr := httptest.NewRecorder()
mux.ServeHTTP(rr, req)
if rr.Code != http.StatusNotImplemented {
t.Errorf("expected 501, got %d", rr.Code)
}
}
// ... one test per declared handler ...
go test ./... on the generated output passes — verified.
As the developer implements each handler, the stub stops
returning 501 → the test fails → they replace it with real
assertions.
There's no way to declare a handler without its matching test.
Full sample → samples/backend-with-tests/ ·
Pattern → docs/backend-codegen.md
💡 Capy as an idea language¶
The most ambitious framing: Capy lets you describe ideas; libraries are implementers. When the Go server isn't fast enough and you want Rust, you swap libraries — not rewrite the code.
The multi-language-demo ships this concretely: the same 10-line source compiles to Python, JavaScript, Go, Rust, AND C. Identical semantics, five idiomatic implementations.
script.capy (the idea)
│
┌───┼───┬───┬───┐
▼ ▼ ▼ ▼ ▼
.py .js .go .rs .c
└───┴───┴───┴───┘
every output verified to print 12 when run
Mobile apps follow the same pattern: samples/android-app/ and
samples/ios-app/ accept the same source shape and emit
Kotlin or SwiftUI. The "habit tracker" idea is platform-agnostic;
which native stack to ship is a library decision.
The implication: outgrowing an implementation no longer means rewriting the system. Add a new target library; run benchmarks; switch over a service at a time. The contract stays stable.
Pattern → docs/idea-language.md
🛠️ Generate a whole project — web app, Android, iOS, libtorch¶
The same multi-file mechanism scaffolds real projects across very different stacks. Below are four canonical demos. Each takes 12–17 lines of plain DSL and emits a full, compilable file tree for its target.
Source:
app "Habit Tracker"
description "A tiny daily-habit tracker."
color_primary "#4f46e5"
color_bg "#0f172a"
habit drink_water "Drink 8 glasses of water"
habit read "Read 20 pages"
habit walk "30 min walk"
habit code "Practice coding"
habit meditate "Meditate 10 min"
end
Output:
app "Habit Tracker"
package "com.example.habits"
min_sdk 24
target_sdk 34
version_name "0.1.0"
screen Home "Today's habits"
screen History "Past 30 days"
feature Home "Daily checklist"
feature History "Calendar view"
end
→
Same source shape as the Android demo — only the library swaps. One declaration, two platforms.
app "Habit Tracker"
bundle_id "com.example.habits"
version "0.1.0"
deployment_target "16.0"
screen Home "Today's habits"
feature Home "Daily checklist"
end
→
out/
├── Info.plist
├── Package.swift
└── Sources/
├── App.swift (@main App entry)
├── RootView.swift
└── Screens.swift
HabitTrackerApp comes from "Habit Tracker" via the
pascalCase template helper.
model "MNIST classifier"
dataset MNIST
batch_size 64
epochs 10
optimizer adam
layer conv2d in 1 out 32 kernel 3
layer relu
layer maxpool kernel 2
layer conv2d in 32 out 64 kernel 3
layer relu
layer maxpool kernel 2
layer flatten
layer linear in 1600 out 128
layer relu
layer linear in 128 out 10
end
→
out/
├── CMakeLists.txt
├── run.sh
└── src/
├── model.h (torch::nn::Module + register_module + forward)
└── main.cpp (training loop with optimizer + checkpointing)
The same architecture could target PyTorch Python, TensorFlow/Keras, ONNX, TFLite — write a new library, keep the source.
🌳 Multi-file projects (basic) — one source, a whole project tree¶
A single Capy library can declare any number of output files. Run
with --out-dir generated and Capy writes the entire tree
(subdirectories included).
generated/
├── README.md
├── pyproject.toml
├── .gitignore
├── src/
│ ├── main.py ← FastAPI app with 5 routes mounted
│ └── handlers.py ← Handler stubs
└── tests/
└── test_smoke.py ← Smoke tests
Add a route line to the source, re-run, and every file that
mentions routes regenerates. The other files (.gitignore,
pyproject.toml) stay identical.
The library has six file "..." blocks at the top level:
``
file "README.md"
write# ${context.name | unquote}
${context.description | unquote} ` end
file "src/main.py"
write `"""Generated FastAPI app for ${context.name | unquote}."""
from fastapi import FastAPI from . import handlers
app = FastAPI(title=${context.name | toQuoted})
for r in context.routes
write@app.${r.method | lower}(${r.path | toQuoted})
async def ${r.handler}_endpoint(args, **kwargs):
return await handlers.${r.handler}(args, **kwargs)
` end end
file "tests/test_smoke.py"
...
end
```
Each block has a path (subdirectories OK) and a write-style
body with access to `context` and (for block functions) `body`.
Full sample → samples/multi-file-project/ ·
Pattern docs → multi-file & imports
🧩 Library composition — split shared types & syntax¶
Libraries can import other libraries. Use this to keep validators
(Email, URL, Semver) and shared syntax helpers (tag, note,
meta) in one canonical place that every project imports.
``` import "common/types.capy" import "common/syntax.capy"
extension md
function post arg literal "post" arg capture title string block_closer end write `# ${unquote title}
By ${unquote context.meta.author}
Tags: for t in context.tags
write#${t} end
write
${body} ` end ```
``` function tag arg literal "tag" arg capture name ident append context.tags name end
function note arg literal "note" arg capture text string write `> Note: ${unquote text}
` end ```
After capy check lib.capy: 6 functions, 4 types — three
functions and all four types come from imports, three functions
are local. Conflicts resolve importer-wins; cycles are detected.
Full sample → samples/lib-composition/
📜 Grammar as contract — one source, many consumers¶
A Capy grammar isn't just a parser definition — it's a machine- verified contract. Once it parses, downstream consumers can start building against it before the libraries that target it are implemented. Add new targets later without touching the source.
api "PetStore" version "1.0.0"
endpoint GET "/pets"
summary "List all pets"
returns "Pet[]"
end
endpoint POST "/pets"
summary "Create a pet"
param body "Pet"
returns "Pet"
end
endpoint GET "/pets/{id}"
summary "Get a pet by ID"
param path id int
returns "Pet"
end
endpoint DELETE "/pets/{id}"
summary "Delete a pet"
param path id int
returns "void"
end
Frontend devs can mock against this today. Library implementations can land next week, next month, never — the contract is stable.
openapi: 3.0.3
info:
title: PetStore
version: 1.0.0
paths:
- path: "/pets"
method: GET
summary: "List all pets"
returns: "Pet[]"
- path: "/pets"
method: POST
summary: "Create a pet"
param: { in: body, schema: "Pet" }
returns: "Pet"
- path: "/pets/{id}"
method: GET
param: { in: path, name: id, type: int }
returns: "Pet"
// PetStore — generated TypeScript client.
// GET "/pets"
// List all pets
export async function GET_handler(path: string): Promise<unknown> {
const res = await fetch(path, { method: "GET" });
return res.json();
}
// POST "/pets"
// Create a pet
// @param body "Pet"
export async function POST_handler(path: string): Promise<unknown> {
const res = await fetch(path, { method: "POST" });
return res.json();
}
All three outputs come from the same script.capy. Every commit
runs a golden test that proves they still match. Add a 4th target
(Postman? FastAPI server? Rust client?) by writing one 30-line
library — the contract guarantees compatibility.
Full sample → samples/contract-first-api/ ·
Pattern docs → grammar-as-contract.md
⚡ Supercharge existing syntax — Capy as a preprocessor¶
Capy doesn't have to invent a new language. The most practical pattern is to put Capy macros on top of an existing target — SQL, Markdown, HTML, Dockerfile, Kubernetes — so authors get rich declarations while the runtime still consumes plain target syntax.
Source (script.capy):
table users
pk id
col email "varchar(255) UNIQUE NOT NULL"
col name "varchar(100)"
timestamps
end
table posts
pk id
fk author_id -> users
col title "varchar(255) NOT NULL"
col body "text"
timestamps
soft_delete
end
index posts author_id
Expanded Postgres DDL (capy run lib.capy script.capy):
CREATE TABLE users (
id bigserial PRIMARY KEY,
email varchar(255) UNIQUE NOT NULL,
name varchar(100),
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE TABLE posts (
id bigserial PRIMARY KEY,
author_id bigint NOT NULL REFERENCES users(id),
title varchar(255) NOT NULL,
body text,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now(),
deleted_at timestamptz
);
CREATE INDEX ix_posts_author_id ON posts(author_id);
psql -f schema.sql — your database doesn't know Capy exists.
Capy is just a preprocessor that ran before psql.
Source:
post "Adopting Capy at Acme" date "2026-05-24" author "Alice"
tag rust
tag devtools
para "We replaced 3 generators with one Capy library."
h2 "Why Capy"
bullet "Single source, multiple targets."
bullet "Library doubles as the spec."
callout note "This post is itself generated by Capy."
card "Generators retired" "3" "all replaced by 1 library"
end
Output is real Markdown — YAML frontmatter, blockquote callouts, inline HTML cards. Drop it into Hugo / Jekyll / MkDocs / Next.js / Astro; they all render it natively.
---
title: "Adopting Capy at Acme"
date: "2026-05-24"
author: "Alice"
tags: ["rust", "devtools"]
---
# Adopting Capy at Acme
*By Alice · 2026-05-24*
We replaced 3 generators with one Capy library.
## Why Capy
- Single source, multiple targets.
- Library doubles as the spec.
> **NOTE:** This post is itself generated by Capy.
<div class="metric-card">
<h3>Generators retired</h3>
<p class="metric">3</p>
<p class="caption">all replaced by 1 library</p>
</div>
Any textual host format can be supercharged this way:
| Host format | What Capy adds |
|---|---|
| SQL DDL | pk / fk / timestamps / soft_delete macros |
| Markdown | Frontmatter, callouts, cards, code blocks |
| HTML | Component primitives → plain HTML+CSS+JS |
| Dockerfile | base / apt / pip / multi-stage shortcuts |
| GitHub Actions | job / steps shorthand → full workflow YAML |
| Terraform HCL | Module shortcuts, env-aware defaults |
| Kubernetes | One-liner deployments → full manifests |
| OpenAPI | Endpoint shorthand → full operation + schema |
| Mermaid | High-level diagram syntax → node + edge DSL |
The recipe is always identical: define a Capy library whose
file_template outputs the host format. Authors compose at
the high level; the existing runtime consumes the low-level
output unchanged.
🔒 Named variables + type checking¶
Capy captures are named and typed. Built-in kinds (int,
string, bool, ident, …) get checked at the token level;
library-defined types add pattern: (regex), options: (enum), and
base: (inheritance). Bad input becomes a precise transpile-time
error pointing at the offending value — not a silent mis-render or
a runtime surprise.
```
Custom types¶
type Email pattern "[@]+@[@]+\.[@]+$" end
type Semver pattern "^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$" end
type LogLevel options "trace" "debug" "info" "warn" "error" "fatal" end
type Env options "dev" "staging" "prod" end
type Port base int # validation inheritance end
type ServiceName pattern "^[a-z][a-z0-9-]{1,30}$" end
Named typed captures¶
function service arg literal "service" arg capture name ServiceName # ← named "name", typed ServiceName arg literal "version" arg capture ver Semver block_closer end write `service ${name} {
version = ${ver} ${indent 2 body} } ` end
function port
arg literal "port"
arg capture n Port # ← named "n", typed Port (= int)
write `port = ${n}
` end
function owner
arg literal "owner"
arg capture who Email
write `owner = ${who}
` end # ... env / log_level / brand_color / tls ... ```
Source:
service "api-gateway" version "2.4.1"
env prod
port 8443
owner "platform@example.com"
log_level info
brand_color "#4dd9c0"
tls true
end
Generated HCL:
Each line below violates its declared type:
service "Bad Name!" version "v2"
env production
port 99999
owner "not-an-email"
log_level verbose
brand_color "blue"
tls maybe
end
Running it:
$ capy run lib.capy script-invalid.capy
error: function "service" arg "name": value "Bad Name!"
does not match pattern for type "ServiceName"
Fix that line, re-run, hit the next error. The transpilation refuses to emit until every value satisfies its type.
Why this matters. The library is your config schema. New
contributors learn what fields exist and what values are valid by
reading the type: blocks — no separate spec to maintain. Typos
like log_level verbose are caught at the boundary instead of
becoming a silent no-op in production.
Full sample → samples/typed-config-dsl/
🕹️ Event-driven game DSL — bindings & handlers, not just config¶
The Capy source for these games isn't a list of constants — it declares entities, key bindings, AND event handlers. The library compiles those declarations into a JS dispatch table + action implementations. Rebind a key, change a scoring rule, or delete a game-over condition by editing one line. The library never changes.
Source (script.capy):
game "Breakout" 480 320
paddle width 80 height 10 color "#3df" speed 7
ball radius 6 color "#fff" speed 4
bricks rows 5 cols 8 width 56 height 14 gap 4
brick_color 0 "#f55"
brick_color 1 "#fa4"
brick_color 2 "#ff4"
brick_color 3 "#4f6"
brick_color 4 "#4af"
on_key "ArrowLeft" paddle_left
on_key "ArrowRight" paddle_right
on_key " " launch_ball
on_key "r" reset
on_event brick_hit destroy_brick add_score 10
on_event paddle_hit bounce_with_spin
on_event ball_lost lose_life
on_event all_cleared win
lives 3
The four on_event lines are the entire game-logic glue.
Want bricks worth 50 points instead of 10? Change one number.
Want the ball to NOT bounce off the paddle? Delete one line.
← / → to move · space to launch · R to restart
Source:
game "Snake" 400 400
grid cols 20 rows 20
tick every 110
on_key "ArrowUp" turn_up
on_key "ArrowDown" turn_down
on_key "ArrowLeft" turn_left
on_key "ArrowRight" turn_right
on_key "w" turn_up
on_key "s" turn_down
on_key "a" turn_left
on_key "d" turn_right
on_key " " pause_toggle
on_key "r" reset
on_event eat_food grow add_score 10
on_event hit_wall game_over
on_event hit_self game_over
snake_color "#9fa"
food_color "#f44"
save_best "snake_best"
Both arrow keys AND WASD map to the same actions — two
on_key lines per direction. Delete on_event hit_self and
the snake passes through itself. Change add_score 10 to
add_score 25 for double points.
The DSL above compiles to a JS dispatch table that looks like:
const KEY_BINDINGS = {
"ArrowLeft": "paddle_left",
"ArrowRight": "paddle_right",
" ": "launch_ball",
"r": "reset",
};
const EVENT_HANDLERS = {
brick_hit: (arg) => {
ACTIONS["destroy_brick"](arg);
ACTIONS["add_score"](arg, 10); // ← the number from the DSL
},
paddle_hit: (arg) => { ACTIONS["bounce_with_spin"](arg); },
ball_lost: (arg) => { ACTIONS["lose_life"](arg); },
all_cleared: (arg) => { ACTIONS["win"](arg); },
};
document.addEventListener("keydown", (e) => {
const a = KEY_BINDINGS[e.key];
if (a) ACTIONS[a]();
});
ACTIONS is a table of named JS functions baked into the
library's file_template — paddle_left, launch_ball,
bounce_with_spin, lose_life, etc. The user's DSL never
contains JS; it just names which action runs for which input
or which event.
This is the pattern: Capy DSLs can carry behavior, not just configuration. The library provides primitives; the source composes them.
🎮 Interactive HTML — see the rendered output¶
These are full HTML documents. The Capy source is short; the
generated HTML+CSS+JS is dropped into an <iframe> so you can
actually use it.
Source (script.capy):
game "Block Hopper" 480 320
sprite player "#4dd" 220 280 40 20
sprite enemy "#f64" 100 100 30 30
sprite goal "#fd0" 420 20 20 20
on_key "ArrowLeft" player -4 0
on_key "ArrowRight" player 4 0
on_key "ArrowUp" player 0 -4
on_key "ArrowDown" player 0 4
tick enemy_bounce "sprites.enemy.x += 1; if (sprites.enemy.x > 450) sprites.enemy.x = 0;"
Generated HTML (full file, ~67 lines):
<!doctype html>
<html lang="en">
<head>
<title>Block Hopper</title>
<style>
body { background: #111; display: grid; place-items: center; }
canvas { background: #222; border: 1px solid #444; }
</style>
</head>
<body>
<canvas id="c" width="480" height="320"></canvas>
<script>
const canvas = document.getElementById("c");
const ctx = canvas.getContext("2d");
const keys = {};
window.addEventListener("keydown", e => keys[e.key] = true);
window.addEventListener("keyup", e => keys[e.key] = false);
const sprites = {
player: { x: 220, y: 280, w: 40, h: 20, color: "#4dd" },
enemy: { x: 100, y: 100, w: 30, h: 30, color: "#f64" },
goal: { x: 420, y: 20, w: 20, h: 20, color: "#fd0" },
};
function update() {
if (keys["ArrowLeft"]) sprites.player.x += -4;
if (keys["ArrowRight"]) sprites.player.x += 4;
if (keys["ArrowUp"]) sprites.player.y += -4;
if (keys["ArrowDown"]) sprites.player.y += 4;
sprites.enemy.x += 1; if (sprites.enemy.x > 450) sprites.enemy.x = 0;
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (const s of Object.values(sprites)) {
ctx.fillStyle = s.color;
ctx.fillRect(s.x, s.y, s.w, s.h);
}
}
function loop() { update(); draw(); requestAnimationFrame(loop); }
loop();
</script>
</body>
</html>
Rendered — click the canvas, then use arrow keys:
Source (script.capy):
title "Capy — DSLs in YAML"
hero "Define a language. Get a transpiler." "Capy is a tiny engine that turns a YAML file into a working code generator."
feature "🌱" "Zero default grammar" "Your library is the language."
feature "⚡" "Fast" "Single-binary Go. Boots in milliseconds."
feature "🧩" "50 sample DSLs" "From Python to Mermaid to a real x86-64 transpiler."
cta "Get started" "/docs/getting-started"
cta "GitHub" "https://github.com/olivierdevelops/capy"
Generated — a complete responsive HTML page with embedded CSS, hero section, features grid, and CTAs.
Rendered:
Source (script.capy):
subject "Welcome to Capy"
preview "Your account is ready."
heading "Welcome to Capy!"
para "Thanks for signing up. You're all set to start building DSLs."
para "Click below to read the getting-started guide."
button "Get started" "https://capy.dev/getting-started"
divider
footer "Sent by Capy. Unsubscribe at capy.dev/unsubscribe."
Generated — an HTML email with all styles inlined (the format that survives Gmail, Outlook, etc.).
Rendered:
Source (script.capy):
form "/contact"
field name "text" "Your name"
field email "email" "Email address"
textarea message "Message"
end
Generated:
<form action="/contact" method="post">
<label for="name">Your name</label>
<input id="name" name="name" type="text" />
<label for="email">Email address</label>
<input id="email" name="email" type="email" />
<label for="message">Message</label>
<textarea id="message" name="message"></textarea>
<button type="submit">Submit</button>
</form>
Rendered — try typing in the fields:
Source (script.capy):
Generated:
<div id="card" class="card">
<h3>"Welcome"</h3>
<p>"Capy makes transpilers easy."</p>
<p>"Try editing this component."</p>
</div>
Rendered:
Source (script.capy):
keyframe pulse
at 0 transform = "scale(1)"
at 50 transform = "scale(1.1)"
at 100 transform = "scale(1)"
end
class ".card"
background = "#fff"
border_radius = "8px"
animate slide_in "0.4s" "ease-out"
end
class ".badge"
animate pulse "1.2s" "ease-in-out"
end
Generated CSS — @keyframes rules and animated classes.
Rendered — the card slides in; the badge pulses:
📊 Diagrams — generated Mermaid¶
Capy emits Mermaid; the docs site renders it inline.
Source (script.capy):
flowchart LR
node a "Source"
node b "Lexer"
node c "Parser"
node d "Evaluator"
node e "Output"
a -> b
b -> c
c -> d : "match + render"
d -> e
end
Generated Mermaid — rendered live:
flowchart LR
a[Source]
b[Lexer]
c[Parser]
d[Evaluator]
e[Output]
a --> b
b --> c
c -->|match + render| d
d --> e
Source (script.capy):
machine Order
state Pending
state Paid
state Shipped
state Delivered
Pending -> Paid on "payment"
Paid -> Shipped on "fulfill"
Shipped -> Delivered on "arrival"
final Delivered
end
Generated state diagram — rendered live:
stateDiagram-v2
[*] --> Order
state Pending
state Paid
state Shipped
state Delivered
Pending --> Paid : payment
Paid --> Shipped : fulfill
Shipped --> Delivered : arrival
Delivered --> [*]
📝 Rendered Markdown — generated and shown inline¶
The Capy output IS Markdown, so MkDocs renders it directly on this page. What you see below is the actual generated text, formatted.
Source (script.capy):
section "Today"
todo "Write the launch blog post"
done "Tag v0.1.0"
todo "Test install script on Linux"
section "This week"
todo "Publish VS Code extension"
done "Move .codestyle to docs"
Generated and rendered:
Today¶
- Write the launch blog post
- Tag v0.1.0
- Test install script on Linux
This week¶
- Publish VS Code extension
- Move .codestyle to docs
Source (script.capy):
number "INV-2026-001"
date "2026-05-23"
bill_to "Acme Corp"
item "Consulting hours" 8 "$120.00"
item "Capy enterprise plan" 1 "$2000.00"
item "Onboarding workshop" 2 "$500.00"
Generated and rendered:
Invoice INV-2026-001¶
To: Acme Corp Date: 2026-05-23
| Item | Qty | Unit price |
|---|---|---|
| Consulting hours | 8 | $120.00 |
| Capy enterprise plan | 1 | $2000.00 |
| Onboarding workshop | 2 | $500.00 |
Source (script.capy):
version "0.2.0" "2026-06-15"
added "Configurable surface syntax"
added "else arm on inner if"
fixed "Indentation tokenisation edge case"
end
version "0.1.0" "2026-05-23"
added "Initial public release"
added "Type system with pattern + options"
added "Two block modes"
end
Generated and rendered:
[0.2.0] — 2026-06-15¶
- Added: Configurable surface syntax
- Added: else arm on inner if
- Fixed: Indentation tokenisation edge case
[0.1.0] — 2026-05-23¶
- Added: Initial public release
- Added: Type system with pattern + options
- Added: Two block modes
💻 Code generation — source + generated, side by side¶
These targets are runnable code rather than rendered output. Save the generated file, run it.
Source:
Generated out.py:
Source:
table users
pk id
unique email "varchar(255)"
col name "varchar(255) NOT NULL"
end
table posts
pk id
fk author_id -> users
col title "varchar(255) NOT NULL"
end
index users email
index posts author_id
Generated schema.sql:
CREATE TABLE users (
id bigserial PRIMARY KEY,
email varchar(255) UNIQUE NOT NULL,
name varchar(255) NOT NULL
);
CREATE TABLE posts (
id bigserial PRIMARY KEY,
author_id bigint NOT NULL REFERENCES users(id),
title varchar(255) NOT NULL
);
CREATE INDEX ix_users_email ON users(email);
CREATE INDEX ix_posts_author_id ON posts(author_id);
Source:
port 8080
use "morgan('combined')"
get "/health" "res.json({ok: true})"
post "/users" "const u = req.body; res.status(201).json({id: 42, ...u})"
Generated server.js:
const express = require("express");
const app = express();
app.use(express.json());
app.use(morgan('combined'));
app.get("/health", (req, res) => {
res.json({ok: true})
});
app.post("/users", (req, res) => {
const u = req.body; res.status(201).json({id: 42, ...u})
});
app.listen(8080, () => { console.log("listening on", 8080); });
Source:
provider "aws" "us-east-1"
resource "aws_instance" web
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tag "Name" "capy-web"
end
Generated main.tf:
Source:
Generated deployment.yaml:
Source:
header "📦 Build complete"
section "Branch *main* built in *4m 12s* and is ready to deploy."
divider
section "Tests: 124/124 passing"
button "View build" "https://ci.example.com/build/1234"
Generated JSON (POST to a Slack webhook):
{
"blocks": [
{ "type": "header", "text": { "type": "plain_text", "text": "📦 Build complete" } },
{ "type": "section", "text": { "type": "mrkdwn", "text": "Branch *main* built in *4m 12s* and is ready to deploy." } },
{ "type": "divider" },
{ "type": "section", "text": { "type": "mrkdwn", "text": "Tests: 124/124 passing" } },
{ "type": "actions", "elements": [{ "type": "button", "text": { "type": "plain_text", "text": "View build" }, "url": "https://ci.example.com/build/1234" }] }
]
}
🏗️ One scene → five 3D / game tools¶
The same 8-line scene description compiled to runnable scripts for Blender, SketchUp, Rhino, Unity, and Unreal. No host-API vocabulary in the source — just primitives.
import bpy
_COLORS = {
"red": (1.0, 0.1, 0.1, 1.0),
"blue": (0.1, 0.3, 1.0, 1.0),
"green": (0.1, 0.8, 0.2, 1.0),
"gray": (0.6, 0.6, 0.6, 1.0),
}
def _paint(obj, color):
mat = bpy.data.materials.new(name=color)
mat.diffuse_color = _COLORS.get(color, (1, 1, 1, 1))
obj.data.materials.append(mat)
bpy.context.scene.name = "Studio"
# cube (red)
bpy.ops.mesh.primitive_cube_add(location=(0, 0, 0), size=2)
_paint(bpy.context.active_object, "red")
# sphere (blue)
bpy.ops.mesh.primitive_uv_sphere_add(location=(4, 0, 0), radius=1)
_paint(bpy.context.active_object, "blue")
# plane (gray)
bpy.ops.mesh.primitive_plane_add(location=(0, 0, 0), size=10)
_paint(bpy.context.active_object, "gray")
bpy.ops.object.light_add(type='POINT', location=(5, 5, 5))
bpy.ops.object.camera_add(location=(7, 7, 3))
Paste into Blender's Scripting tab. Done.
# Generated by Capy — paste into the SketchUp Ruby Console.
model = Sketchup.active_model
ent = model.active_entities
# … helpers _add_box / _add_sphere / _add_plane / _paint elided …
model.name = "Studio"
# cube (red)
_add_box(ent, 0, 0, 0, 2, "red")
# sphere (blue)
_add_sphere(ent, 4, 0, 0, 1, "blue")
# plane (gray)
_add_plane(ent, 0, 0, 0, 10, "gray")
model.active_view.camera = Sketchup::Camera.new([7, 7, 3], [0, 0, 0], [0, 0, 1])
using System.Drawing;
using Rhino;
using Rhino.DocObjects;
using Rhino.Geometry;
public static class CapyScene
{
public static void Build(RhinoDoc doc)
{
// cube (red)
{
var c = new Point3d(0, 0, 0);
double s = 2 / 2.0;
var box = new Box(new Plane(c, Vector3d.ZAxis),
new Interval(-s, s), new Interval(-s, s), new Interval(-s, s));
doc.Objects.AddBox(box, Attr("red"));
}
// sphere (blue)
{
var sph = new Sphere(new Point3d(4, 0, 0), 1);
doc.Objects.AddSphere(sph, Attr("blue"));
}
// … plane / light / camera …
}
}
Drop into a Grasshopper C# scripting component, or compile as a Rhino plugin command.
using UnityEngine;
using System.Collections.Generic;
public class CapyScene : MonoBehaviour
{
// … COLORS table + Spawn helper elided …
void Start()
{
gameObject.name = "Studio";
// cube (red)
Spawn(PrimitiveType.Cube, new Vector3(0, 0, 0), Vector3.one * 2, "red");
// sphere (blue)
Spawn(PrimitiveType.Sphere, new Vector3(4, 0, 0), Vector3.one * 1 * 2f, "blue");
// plane (gray)
Spawn(PrimitiveType.Plane, new Vector3(0, 0, 0), Vector3.one * 10 * 0.1f, "gray");
// point light
{
var go = new GameObject("PointLight");
go.transform.position = new Vector3(5, 5, 5);
go.AddComponent<Light>().type = LightType.Point;
}
// camera
{
var go = new GameObject("Camera");
go.transform.position = new Vector3(7, 7, 3);
go.transform.LookAt(Vector3.zero);
go.AddComponent<Camera>();
}
}
}
Drop into Assets/Scripts/CapyScene.cs and attach to an empty
GameObject.
import unreal
# … _PRIMITIVES table + _spawn_primitive helper elided …
unreal.log("Studio")
# cube (red)
_spawn_primitive("Cube", unreal.Vector(0*100, 0*100, 0*100), 2, "red")
# sphere (blue)
_spawn_primitive("Sphere", unreal.Vector(4*100, 0*100, 0*100), 1*2, "blue")
# plane (gray)
_spawn_primitive("Plane", unreal.Vector(0*100, 0*100, 0*100), 10, "gray")
unreal.EditorLevelLibrary.spawn_actor_from_class(
unreal.PointLight, unreal.Vector(5*100, 5*100, 5*100))
unreal.EditorLevelLibrary.spawn_actor_from_class(
unreal.CameraActor, unreal.Vector(7*100, 7*100, 3*100))
Note the *100 — Capy's library handles the meters-to-Unreal-cm
conversion so the source stays in human units.
Why this matters for 3D / game pipelines. The same procedural building gets rewritten in five different host APIs — and they drift. An algorithm change means hand-editing five scripts. LLMs hallucinate API names because each tool's vocabulary is slightly different. With Capy you write the scene once; each library encodes one tool's quirks. Add Maya MEL, Houdini Python, Three.js, glTF — write a 50-line library, never touch the source.
Full sample → samples/3d-tools-demo/
🌍 One source → five programming languages¶
The same 10-line script.capy compiled to five different
programming languages by five different libraries. Each output is a
real, runnable program that prints 12.
Ten lines. Defines a function, calls it, prints the result. The
grammar (fn, return, main, let, print) is defined by the
libraries — not by Capy itself.
Why this matters. Maintaining the "same logic in N languages" problem is real: client SDKs that drift, an algorithm needed in Python and C++, a validator that runs in the browser and on the server. With Capy you write the logic once; adding a sixth target is a ~50-line library file. The next time you change the algorithm, all five (or six, or ten) outputs regenerate.
Full sample → samples/multi-language-demo/
Bonus: the library itself¶
Libraries are .capy files. Same grammar as the user scripts above —
one parser, one mental model.
extension c
function fn
arg literal "fn"
arg capture name ident
arg literal "("
arg capture a ident
arg literal ","
arg capture b ident
arg literal ")"
block_closer end
write `int ${name}(int ${a}, int ${b}) {
${indent 4 body}
}
`
end
See .capy libraries for the full grammar.
🔀 Same source, three targets¶
The clearest demonstration of "the library is the grammar". One input file, three libraries, three completely different artifacts.
Running capy run lib_sql.yaml script.capy produces SQL inserts:
Running capy run lib_json.yaml script.capy produces JSON:
Running capy run lib_md.yaml script.capy produces Markdown:
| Name | Age | Status |
|---|---|---|
| alice | 30 | active |
| bob | 25 | inactive |
| carol | 42 | active |
The libraries are 8–15 lines each. Add a fourth target (CSV, YAML, HTML table, …) by writing a fourth library — never touch the source.
What's not shown here¶
The 50 sample demos in the repo include more code-gen targets that don't fit on a single doc page:
- Backend frameworks: Flask, FastAPI, Cobra CLI
- Schemas: Prisma, Zod, XState v5, Protobuf, GraphQL
- Config: nginx, systemd, GitHub Actions, cron, .env, Dockerfile, Makefile, OpenAPI, Prometheus alerts, Chrome MV3 manifest
- Other: CSV, Markdown CV, Markdown blog with YAML front matter, Markdown API reference
How to run any of these locally¶
go install github.com/olivierdevelops/capy/cmd/capy@latest
git clone https://github.com/olivierdevelops/capy
cd capy
capy run samples/transpile-canvas-game/lib.capy samples/transpile-canvas-game/script.capy > game.html
open game.html
Or just look at the lib.capy — it's the entire grammar in 30–60 lines.