cr8script · plate III.II
github ↗
plate III.II · recipes
i / i

how do I do X

canonical cr8script idioms for common tasks

Each recipe is a question, a short script, and a one-line note on why this is the canonical way. Try any of them in the playground; full examples that string several together live in examples/.

i

filter, map, sum a list

"Sum the amount field for orders over $10."

let orders = [
  { id: 101, amount: 12.50 },
  { id: 102, amount:  8.00 },
  { id: 103, amount: 99.00 },
]

let total = orders
  | where amount is greater than 10
  | map amount
  | sum _

# total: 111.5

whyBare names inside where and map auto-resolve to row fields. sum(xs) takes a list directly — the underscore in sum _ is just the implicit pipeline argument.

ii

group by + summarize

"Total sales per product, sorted descending."

let sales = [
  { product: "widget", amount: 12.50 },
  { product: "gadget", amount:  8.00 },
  { product: "widget", amount: 15.00 },
  { product: "doodad", amount: 99.00 },
]

let by_product = sales
  | group by product
  | summarize { total: sum(amount), n: length(items) }
  | sort by total descending

whygroup by name produces records with a name field (the key) and an items list. Inside summarize, bare names resolve to fields of the grouped items.

iii

safe field access

"What if the field might be missing?"

let r = { name: "Ada" }

# r.age would error -- the field doesn't exist.
# r.get("age") returns nothing safely.
let age = r.get("age")

if age is nothing then
  show "age unknown"
else
  show f"age is {age}"
end

whyDirect field access is strict on purpose — typos turn into hard errors with "did you mean" hints. r.get("key") opts into nullable lookup when the field really might be absent.

iv

validate records

"Take a list of records, return { ok, errors } per item."

to validate(items)
  var errors = []
  for each i in 1..length(items)
    let r = items[i]
    if r.get("name") is nothing then
      errors = errors + [{ index: i, field: "name", msg: "missing" }]
    end
  end
  return { ok: length(errors) is 0, errors: errors }
end

show json.stringify(validate(records))

whyThe agent-loop primitive: take upstream LLM output, return structured diagnostics another tool can parse. full example ↗

v

fetch JSON, handle non-2xx

"Hit an API and bail safely on errors."

let r = http.get("https://api.example.com/data")

# r.ok is connection-level (true even on 4xx/5xx).
# Decide success ourselves -- the language doesn't guess.
if r.ok is false then
  show f"network error: {r.error}"
else if r.status is less than 200 or r.status is at least 300 then
  show f"HTTP {r.status}"
else
  let data = json.parse(r.body)
  show f"got {length(data)} records"
end

whyhttp.get returns { ok, status, body, time_ms, error }. ok only flags network-level failure; the script decides what counts as success. (Note: http.get is disabled in the in-browser playground.)

vi

parse and transform CSV

"Read CSV, filter, write CSV."

let input = "name,score\nAda,90\nBob,72\nCara,85"
let rows = csv.parse(input)

# csv.parse returns a list of records keyed by header row.
let passing = rows
  | where to_number(score) is at least 80

show csv.write(passing)

whyCSV cells arrive as text. Cast explicitly with to_number — cr8 will never silently coerce "72" for you, which is the whole point.

vii

emit a markdown report

"Structured tickets —> markdown standup."

show "# daily standup"
show ""
show f"_{length(tickets)} tickets_"
show ""

let by_owner = tickets | group by owner

for each g in by_owner
  show f"### {g.owner}"
  for each t in g.items
    show f"- `{t.id}` ({t.status}) -- {t.title}"
  end
  show ""
end

whyshow f"..." is the entire templating story. No Jinja, no templating engine. Pipe stdout to a .md file.

viii

mermaid Gantt for an IT project

"Sprint tickets —> Gantt chart grouped by team, with milestones."

let tickets = [
  # engineering
  { id: "PLAT-201", title: "Discovery + RFC",   owner: "eng",    status: "done",   start: "2026-04-06", days: 5 },
  { id: "PLAT-202", title: "Auth service",      owner: "eng",    status: "done",   start: "2026-04-13", days: 7 },
  { id: "PLAT-205", title: "API gateway",       owner: "eng",    status: "active", start: "2026-04-27", days: 6 },
  { id: "PLAT-210", title: "Cutover",           owner: "eng",    status: "todo",   start: "2026-05-11", days: 2 },
  # data
  { id: "DATA-118", title: "Schema review",     owner: "data",   status: "done",   start: "2026-04-13", days: 3 },
  { id: "DATA-122", title: "Migration scripts", owner: "data",   status: "active", start: "2026-04-20", days: 5 },
  { id: "DATA-125", title: "Backfill + verify", owner: "data",   status: "todo",   start: "2026-04-27", days: 4 },
  # devops
  { id: "OPS-077",  title: "Terraform modules", owner: "devops", status: "done",   start: "2026-04-06", days: 6 },
  { id: "OPS-081",  title: "Staging stack",     owner: "devops", status: "active", start: "2026-04-20", days: 5 },
  { id: "OPS-084",  title: "Prod stack + DNS",  owner: "devops", status: "todo",   start: "2026-05-04", days: 4 },
  # qa
  { id: "QA-040",   title: "Test plan",         owner: "qa",     status: "done",   start: "2026-04-13", days: 4 },
  { id: "QA-046",   title: "Load rig",          owner: "qa",     status: "active", start: "2026-04-27", days: 4 },
  { id: "QA-051",   title: "Smoke + soak",      owner: "qa",     status: "todo",   start: "2026-05-04", days: 5 },
]

let milestones = [
  { title: "RFC sign-off", id: "M1", on: "2026-04-10" },
  { title: "Code freeze",  id: "M2", on: "2026-05-08" },
  { title: "Go live",      id: "M3", on: "2026-05-12" },
]

to status_tag(s)
  if s is "done"   then return "done, "
  else if s is "active" then return "active, "
  else return "" end
end

show "```mermaid"
show "gantt"
show "  title Q2 platform migration"
show "  dateFormat YYYY-MM-DD"
show "  axisFormat %b %d"
show "  tickInterval 1week"
show "  weekday monday"
show "  excludes weekends"
show ""

# milestones lane first -- diamonds along the top
show "  section milestones"
for each m in milestones
  show f"    {m.title} :milestone, {m.id}, {m.on}, 0d"
end

# one swimlane per team
let by_owner = tickets | group by owner

for each g in by_owner
  show f"  section {g.owner}"
  for each t in g.items
    let tag = status_tag(t.status)
    show f"    {t.title} :{tag}{t.id}, {t.start}, {t.days}d"
  end
end

show "```"
renders to
gantt title Q2 platform migration dateFormat YYYY-MM-DD axisFormat %b %d tickInterval 1week weekday monday section milestones RFC sign-off :milestone, M1, 2026-04-10, 0d Code freeze :milestone, M2, 2026-05-08, 0d Go live :milestone, M3, 2026-05-12, 0d section eng Discovery + RFC :done, PLAT-201, 2026-04-06, 5d Auth service :done, PLAT-202, 2026-04-13, 7d API gateway :active, PLAT-205, 2026-04-27, 6d Cutover :crit, PLAT-210, 2026-05-11, 2d section data Schema review :done, DATA-118, 2026-04-13, 3d Migration scripts :active, DATA-122, 2026-04-20, 5d Backfill + verify :DATA-125, 2026-04-27, 4d section devops Terraform modules :done, OPS-077, 2026-04-06, 6d Staging stack :active, OPS-081, 2026-04-20, 5d Prod stack + DNS :OPS-084, 2026-05-04, 4d section qa Test plan :done, QA-040, 2026-04-13, 4d Load rig :active, QA-046, 2026-04-27, 4d Smoke + soak :crit, QA-051, 2026-05-04, 5d

whygroup by owner gives swimlanes and a tiny status_tag helper maps each row to mermaid's done / active tags. Milestones live in their own lane so the timeline keeps a clean top edge. Pipe stdout into a .md file and any GitHub issue, wiki, or Notion page renders the chart.

ix

mermaid funnel for sales

"Stage counts —> flowchart with conversion rates."

let funnel = [
  { stage: "leads",      n: 12500 },
  { stage: "mqls",       n:  3100 },
  { stage: "sqls",       n:  1240 },
  { stage: "opps",       n:   410 },
  { stage: "closed_won", n:   118 },
]

show "```mermaid"
show "flowchart TD"

# one node per stage, label = name + count
for each i in 1..length(funnel)
  let s = funnel[i]
  show f"  S{i}[\"{s.stage}<br/>{s.n}\"]"
end

# edges labeled with stage-to-stage conversion %
let last = length(funnel) - 1
for each i in 1..last
  let cur = funnel[i]
  let nxt = funnel[i + 1]
  let pct = math.round(nxt.n / cur.n * 1000) / 10
  show f"  S{i} -->|{pct}%| S{i + 1}"
end

show "```"
renders to
flowchart TD S1["leads<br/>12500"] S2["mqls<br/>3100"] S3["sqls<br/>1240"] S4["opps<br/>410"] S5["closed_won<br/>118"] S1 -->|24.8%| S2 S2 -->|40%| S3 S3 -->|33.1%| S4 S4 -->|28.8%| S5

why1-based indexing makes i + 1 the natural "next stage" reference. math.round(x * 1000) / 10 is the canonical one-decimal-percent trick — cr8 has no printf, but rounding to a tenth is one expression.

x

mermaid pie for channel mix

"Marketing spend by channel —> pie chart."

let spend = [
  { channel: "paid_search", usd: 45000 },
  { channel: "social",      usd: 22000 },
  { channel: "email",       usd:  8500 },
  { channel: "events",      usd: 31000 },
  { channel: "content",     usd: 14000 },
]

show "```mermaid"
show "pie title Q2 marketing spend (USD)"
for each row in spend
  show f"  \"{row.channel}\" : {row.usd}"
end
show "```"
renders to
pie title Q2 marketing spend (USD) "paid_search" : 45000 "social" : 22000 "email" : 8500 "events" : 31000 "content" : 14000

whyThe simplest mermaid type — one record per slice, no math. Same shape works for budget allocation, headcount, deal-stage value, anything where you want a one-line summary visual.

xi

return a value from if

"Conditional assignment without a temporary variable."

# if/then/else is also an expression in cr8.
let label = if score is at least 90 then "A"
            else if score is at least 80 then "B"
            else "C" end

show f"grade: {label}"

whyNo need for a var + reassign dance. The end closes the expression form just like the statement form.

read next principles ↗ cr8 vs python ↗ agents (integrator playbook) ↗ play ↗ LLMS.md (full reference) ↗