📚 Sesi Language — Complete Study Guide
1. Identity & Philosophy
Sesi is a clean, minimal, side-effect-oriented scripting language. It is:
- NOT Python, TypeScript, JavaScript, YAML, or any other language — its syntax is unique
- Top-level execution only — no
main()wrappers - Built for clarity and reusability, with built-in AI primitives, SVG drawing, audio synthesis, networking, and a document database
- Dynamically typed with optional type annotations
- File extension:
.sesi - Runs via CLI:
npm run sesi <file>.sesiornode bin/sesi.js <file>.sesi
---
2. Variables
Declaration — always let, never const
let name = "Sesi"
let retries: number = 3
let version = 1.9.0
let active = true
let missing // null (uninitialized)
Reassignment — no let keyword on re-assign
let count = 0
count = count + 1 // count is now 1
Types
| Type | Alias | Example | |
|---|---|---|---|
number |
num |
42, 3.14 |
|
string |
str |
"hello" |
|
bool |
— | true, false |
|
null |
— | null |
|
array |
— | [1, 2, 3] |
|
object |
— | {key: "value"} |
|
any |
— | any value, no type check |
Type annotations are optional and belong after the variable name:
let total: number = 42
let label: string = "draft"
let ready: bool = true
Type Conversion
let n = num("42") // "42" -> 42
let s = str(99) // 99 -> "99"
let b = bool(0) // 0 -> false
Never use parseInt, Number(), or JS-style coercions.
Optional & Union Types (in fn signatures)
fn greet(name: str, title: str?) { ... } // title may be null (optional type)
fn find(id: num) -> object? { ... } // optional return type
fn show(val: number | string) { ... } // union type
Optional Chaining
// Optional chaining: obj?.prop, arr?.[index]
let email = user?.profile?.email
let first = items?.[0]
Built-in Global Variables & Shorthands
// CLI: sesi script.sesi Alice 30
let name = args[0] // "Alice"
let age = args[1] // "30"
// Environment variable lookup shorthand ($VAR)
show $USER
let port = $PORT || 8080
let home = $HOME
Collection Mutation
let scores = [10, 20, 30]
scores[0] = 99 // [99, 20, 30]
let user = {name: "Ada", role: "admin"}
user.role = "developer"
---
3. Functions
Declaration
fn greet(name: string) { show "Hello," name }
greet("Ada") // Hello, Ada
Grammar: 'async'? 'fn' identifier '(' parameters ')' '->' type? block
Documentation Comments
Write a comment immediately above each public or nontrivial declaration. Sesi uses these comments in hover details, preserves function documentation across exports and imports, and can expose handler documentation through modules such as std/api.
There is no required documentation schema—a clear description by itself is useful. For consistent, discoverable API documentation, prefer a short description followed by Parameters and Returns sections:
/**
Adds two numbers.
Parameters:
- `a`: The first number.
- `b`: The second number.
Returns:
- The sum of `a` and `b`.
Example:
```sesi
add(2, 3) // 5
```
Notes:
- This function assumes both `a` and `b` are numbers.
*/
fn add(a: number, b: number) -> number { return a + b }
Documentation comments also work on make and let declarations. They are especially valuable for let values containing arrays or objects: describe the purpose of the collection, the shape and ordering of array items, and the important object keys and defaults.
/** Collects jobs waiting to be processed. */
make JobQueue {
/**
* Runtime settings shared by API requests.
*
* Fields:
* - `hosts`: Ordered fallback API hosts.
* - `retries`: Maximum attempts per request.
*/
let settings = {
"hosts": ["https://primary.example", "https://backup.example"],
"retries": 3
}
// Pending job objects in insertion order.
let jobs = []
}
Typed Parameters, Defaults, Return Values
fn add(a: number, b: number) -> number { return a + b }
fn greet(name: string = "World") { show "Hello," name }
Inside a
fnblock,returnsends a value back to the caller. At the top level, it stops the current script or module immediately; a supplied value has no caller to receive it.
Functions as First-Class Values
fn isEven(x) { return x % 2 == 0 }
fn square(x) { return x * x }
fn add(a, b) { return a + b }
let evens = filter([1, 2, 3, 4], isEven) // [2, 4]
let sq = map([1, 2, 3], square)
let total = reduce([1, 2, 3], add)
Pipe Operator |
Passes the left value as the first argument to the right function:
fn add(a, b) { return a + b }
fn mul(a, b) { return a * b }
let result = 10 | add(5) | mul(2) // 30
Async Functions
async fn load(path: string) { return read_file(path) }
let content = await load("data.txt")
Closures
Functions close over live bindings — changes to outer variables are visible inside:
let base = 100
fn addBase(n) { return n + base }
show addBase(5) // 105
base = 200
show addBase(5) // 205
Exporting Functions
// math.sesi
export fn add(a, b) { return a + b }
export let PI = 3.14159
---
4. Conditionals
No parentheses required around conditions.
if score >= 90 {
show "excellent"
} else if score >= 70 {
show "passing"
} else {
show "needs work"
}
One-liners
if ready { show "go" }
if !ready { show "wait" }
Truthiness Table
| Value | Truthy? | |
|---|---|---|
true |
✅ | |
| Non-zero number | ✅ | |
| Non-empty string | ✅ | |
| Non-empty array | ✅ | |
| Non-empty object | ✅ | |
false |
❌ | |
0 |
❌ | |
"" |
❌ | |
null |
❌ |
Operators
| Operator | Meaning | |||
|---|---|---|---|---|
== |
Equal | |||
!= |
Not equal | |||
< |
Less than | |||
> |
Greater than | |||
<= |
Less than or equal | |||
>= |
Greater than or equal | |||
&& |
And (short-circuits) | |||
\ |
\ | |
Or (short-circuits) | |
! |
Not |
---
5. Loops
while — condition-based
let i = 0
while i < 5 {
show i
i = i + 1
}
// One-liner
while x > 0 { x = x - 1 }
for ... in — iterate array
let names = ["Ada", "Grace", "Linus"]
for name in names { show "Hello," name }
// Object keys
for key in keys(config) { show key ":" config[key] }
for ... = ... to ... — numeric range (excludes upper bound)
for i = 0 to 5 { show i }
// 0 1 2 3 4
break and continue
for item in items {
if item == "stop" { break } // exit loop
if item == "skip" { continue } // next iteration
show item
}
Accumulator Pattern
let total = 0
for n in numbers { total = total + n }
let results = []
for file in files {
if contains(file, ".sesi") { push(results, file) }
}
---
6. Prompt Blocks
Sesi's unique string-composition primitive. Replaces template literals.
let name = "Ada"
let ver = "1.9.0"
prompt header {"Welcome to Sesi "ver". Hello, "name}
// header = "Welcome to Sesi 1.9.0. Hello, Ada"
show header
write_file("out.txt", header)
Rules
⚠️ NO raw newlines between elements inside
{ }(i.e. outside of string literals) — they are treated as statement separators and will cause a syntax error. Write prompt blocks on a single line.
>
To include actual newlines in the output, place them literally inside the string quotes:
prompt report {"Student: "name"
Score: "score"
Grade: A"}
The newline is a real line break inside the string literal — not \n, not any escape sequence.
Composing Prompts
prompt fullName {first last}
prompt badge {"[Developer] "fullName}
Prefer prompt blocks over + inside show
// ✅ Idiomatic
show "Hello," name "version" version
// ❌ Avoid
show "Hello, " + name + " version " + str(version)
---
7. Modules
Exporting
export fn add(a, b) { return a + b }
export let VERSION = "1.9.0"
Importing — import (named)
import { add, PI } from "math"
Importing — allow (Sesi's preferred syntax)
// Named bindings
allow "math" in with { add, multiply }
show add(3, 4)
// Namespace object
allow "math" in as Math
show Math.add(3, 4)
Module Resolution Order
- Script's own directory
- Current working directory
SESI_PATHenv var~/.sesi/lib(global)sesi_modules(local modules directory)
Standard Library Modules
| Module | Common Alias | Description | |
|---|---|---|---|
std/draw |
Draw |
SVG vector drawing | |
std/db |
{db_open} |
Embedded document DB | |
std/audio |
Audio |
Sound synthesis/playback | |
std/theory |
Music |
Music theory helpers | |
std/math |
Math |
Math operations | |
std/time |
Time |
Time/date functions |
... for more, visit
getting-started/
JSON conversion is built in directly through from_json and to_json; the former std/json module has been removed.
---
8. Error Handling
try {
let data = read_file("config.json")
let obj = from_json(data)
show obj.key
} catch (err) {
show "Error:" err
}
Always wrap file I/O and network calls in
try/catch— this is a Sesi best practice.
---
9. std/draw — SVG and Pixel Drawing
allow "std/draw" in as Draw
Shapes
Draw.rect(x, y, width, height, fill, options?)
Draw.circle(x, y, radius, fill, options?)
Draw.line(x1, y1, x2, y2, stroke, options?)
Draw.text(x, y, content, size, fill, options?)
Draw.ellipse(cx, cy, rx, ry, fill, options?)
Draw.polygon(points, fill, options?)
Draw.path(d, fill, options?)
Gradients
Draw.gradient("linear", "myGrad", [
{offset: "0%", color: "#ff0000"},
{offset: "100%", color: "#0000ff"}
], {x1: "0%", y1: "0%", x2: "100%", y2: "0%"})
// Reference in fill
Draw.rect(0, 0, 400, 300, "url(#myGrad)")
Draw.gradient("radial", "glowGrad", [
{offset: "0%", color: "#ffffff", opacity: 1.0},
{offset: "100%", color: "#000000", opacity: 0.0}
], {cx: "50%", cy: "50%", r: "50%"})
CSS Animations
Draw.style("
@keyframes pulse {
0%, 100% { opacity: 0.8; }
50% { opacity: 1.0; }
}
.pulsing { animation: pulse 2s infinite ease-in-out; }
")
Draw.circle(200, 200, 80, "#ff0000", {class: "pulsing"})
Raw SVG Injection
Draw.raw("<filter id=\"glow\"><feGaussianBlur stdDeviation=\"5\" result=\"blur\" /><feMerge><feMergeNode in=\"blur\" /><feMergeNode in=\"SourceGraphic\" /></feMerge></filter>")
Rendering & Saving
let svg = Draw.render(400, 300) // returns SVG string, buffer stays
Draw.save_svg("output.svg", 400, 300)
Draw.clear() // reset buffer
Raster Pixels
Draw.pixel(x, y, color)
Draw.pixel_grid(grid, palette, scale?, x?, y?)
Draw.pixel_rect(x, y, w, h, color, filled?)
Draw.pixel_circle(cx, cy, r, color, filled?)
Draw.pixel_line(x1, y1, x2, y2, color)
Draw.pixel_ellipse(cx, cy, rx, ry, color, filled?)
Draw.pixel_polygon(points, color, filled?)
Draw.pixel_triangle(x1, y1, x2, y2, x3, y3, color, filled?)
Draw.pixel_star(cx, cy, spikes, outerRadius, innerRadius, color, filled?)
Draw.pixel_ring(cx, cy, radius, thickness, color)
Draw.pixel_arc(cx, cy, radius, startAngle, endAngle, color)
Draw.pixel_bezier(x1, y1, cx, cy, x2, y2, color)
Draw.pixel_path(d, color, filled?)
Draw.pixel_text(x, y, text, color, scale?)
Draw.save_png(path, width, height, background?)
Pixel calls write to a raster buffer, not the SVG. pixel_grid accepts array rows or compact string rows and expands each cell by scale. save_png encodes the buffer as a true RGBA PNG; the background defaults to transparent.
Layer Order
Shapes are rendered in draw-call order — earlier calls appear behind later ones.
---
10. Model Calls (AI)
Basic call
let response = model("gemini-3.5-flash-lite") {"Summarize this:" text}
show response
The prompt block
{...}uses the same sequential string composition rules asprompt.
Config block
// Config block comes between model name and prompt block
let result = model("gemini-3.8-flash") {thinkingLevel: "medium", max_tokens: 500} {"Analyze this:" doc}
Config keys are unquoted identifiers (schema objects).
Config Keys
| Key | Type | Notes | ||
|---|---|---|---|---|
thinkingLevel |
string |
"minimal", "low", "medium", "high" |
||
max_tokens |
number |
Cap response length | ||
images |
string \ |
array |
Vision input file path(s) | |
stream |
bool \ |
fn |
Stream to stdout or callback | |
cache |
bool |
false = bypass Sesi Logic Caching |
||
search |
(bare key) | Enable live web search grounding | ||
tools |
array \ |
bool |
Registered names/schemas, or true for all |
|
max_tool_calls |
number |
Automatic-call limit; defaults to 8 |
// Vision
let desc = model("gemini-3.8-flash") {images: "photo.png"} {"Describe this image."}
// Streaming
let r = model("gemini-3.5-flash-lite") {stream: true} {"Write a poem."}
// No cache + search
let news = model("gemini-3.5-flash-lite") {search, cache: false} {"Latest AI news."}
Automatic Function Calling
Register ordinary Sesi functions, then expose selected names to a model. Sesi derives the provider schema from the function parameters, executes requested calls, and resumes the model with each result until it returns a final answer.
fn calculateTax(amount: number, rate: number) -> number {return amount * rate}
define_tool("calculateTax", calculateTax, "Calculate tax for an amount and rate")
let answer = model("gemini-3-flash-preview") {tools: list_tools(), max_tool_calls: 4} {"What is 8% tax on $125?"}
show answer
Use tools: ["calculateTax"] to expose only selected registered tools, or tools: true to expose all of them. Sensitive system functions such as exec, spawn, python, and ffmpeg remain blocked from automated model execution.
Image Generation
let img = image("gemini-3.1-flash-image-lite") {ratio: "16:9", size: "1K"} {"A cyberpunk city at night"}
write_image("banner.png", img)
---
11. Network
Built-in — no imports required.
// GET
let body = web_get("https://api.example.com/data")
let body = web_get("https://api.example.com/data", {Authorization: "Bearer token"})
// POST
let res = web_send("https://api.example.com/submit", to_json({key: "val"}))
let res = web_send(url, body, {"Content-Type": "application/json"})
// Parse JSON response
let data = from_json(body)
show data.title
HTTP Server
fn handler(req) {
if req.path == "/health" { return {status: 200, body: "ok"} }
return {status: 404, body: "Not found"}
}
let http = listen(8080, handler)
http.close()
WebSocket Server
fn onMsg(client, msg) {
client.send("Echo: " + msg)
}
let ws = api(8989, onMsg)
ws.close()
---
12. Database (std/db)
allow "std/db" in with {db_open}
let db = db_open("store.db") // plain
let db = db_open("store.db", "passphrase") // encrypted (AES-256-CBC)
let users = db.collection("users")
// CRUD
users.insert({name: "Ada", role: "admin"})
let all = users.find()
let admins = users.find({role: "admin"})
let count = users.update({name: "Ada"}, {role: "lead"})
let del = users.delete({active: false})
Documents auto-get an _id if not provided.
---
13. Audio (std/audio & std/theory)
allow "std/audio" in as Audio
allow "std/theory" in as Music
Playback & Synthesis
Audio.beep(440, 200) // Hz, ms
Audio.play("C4", 500) // note name, ms
Audio.save("tone.wav", "A4", 2000, "sine", {attack: 50, release: 500})
Audio.midi("song.mid", track) // export MIDI
Waveform types
"sine", "square", "saw", "triangle", "noise", "kick", "snare", "hat", "clap"
Sequences & Mixing
let song = [
{note: "C4", ms: 500, vol: 0.8},
{note: "E4", ms: 500, pan: -0.5}
]
Audio.sequence("song.wav", song, "triangle")
Audio.mix("mix.wav", [melody_track, bass_track], "sine", {saturate: 1.2})
SoundFont Instruments
let piano = Audio.sf2("GeneralUser-GS.sf2", {instrument: 0, gain: 1.5})
let notes = [piano("C4", 500), piano("E4", 500)]
Audio.mix("song.wav", [notes], "sine")
Music Theory
let scale = Music.scale("C4", "major")
let chord = Music.chord("A3", "m7")
let moved = Music.transpose(chord, 7) // +7 semitones
let ms = Music.bar(8, 120) // 8 bars at 120bpm = 16000ms
let ms2 = Music.duration(1, 30) // 1m30s = 90000ms
---
14. Core Built-in Functions
These are always available — no imports needed:
Arrays
| Function | Description | |
|---|---|---|
push(arr, val) |
Append to array (mutates in place) | |
pop(arr) |
Remove and return last element | |
len(arr) |
Length of array or string | |
range(n) |
[0, 1, ..., n-1] |
|
map(arr, fn) |
Apply fn to each element → new array | |
filter(arr, fn) |
Keep elements where fn returns true | |
reduce(arr, fn) |
Accumulate array to single value | |
find(arr, fn) |
First matching element, or null | |
contains(str, sub) |
String/array containment check | |
keys(obj) |
Array of object keys | |
slice(arr, s, e) |
Sub-array from index s to e | |
join(arr, sep) |
Join array elements into string | |
split(str, sep) |
Split string into array |
Strings
| Function | Description | |
|---|---|---|
to_upper(str) |
Uppercase | |
to_lower(str) |
Lowercase | |
trim(str) |
Strip whitespace | |
swap(str, a, b) |
Replace a with b | |
regex(pattern, text, options?) |
Match, test, replace, or split with a regular expression | |
str(val) |
Convert any value to string |
JSON
| Function | Description | |
|---|---|---|
to_json(obj) |
Serialize object → JSON string | |
from_json(str) |
Parse JSON string → object/array |
Speech
| Function | Description | |
|---|---|---|
speech(text, voice?, gemini?) |
Speak text with the system voice or Gemini | |
from_speech(path, language?, gemini?) |
Transcribe with Whisper or Gemini |
from_speech() needs Whisper and a local model.
Always use
to_json()for serialization. Never usestringify().
Token Usage & Cost
| Function | Description | |
|---|---|---|
tokenize(text, model?) |
Return local plain-text token IDs; also supports "simple" word splitting |
|
count_tokens(text, model?) |
Count through the selected OpenAI, Gemini, or local tokenizer | |
estimate_tokens(text, model?) |
Estimate locally without an API request | |
estimate_cost(model, input, output?) |
Estimate paid-tier text-token cost | |
model_usage() |
Inspect actual usage from the latest model call |
let planned = estimate_cost("gemini-3.8-flash", prompt, 1000)
let answer = model("gemini-3.8-flash") {max_tokens: 1000} {prompt}
let actual = model_usage()
show actual.total_tokens actual.total_cost_usd
These are deliberately separate: tokenize() is local and returns IDs, count_tokens() asks the selected provider for an exact request count, and estimate_tokens() is the explicit offline approximation.
Math & Randomness
random() and floor(value) are globally available. Import std/math for the complete stable JavaScript Math surface supported by Node.js 20+:
allow "std/math" in as Math
show floor(3.9) // 3; global
show Math.atan2(1, 0) // 1.5707963267948966
show Math.hypot(3, 4) // 5
show Math.min(8, 3, 5) // 3
Constants: E, LN10, LN2, LOG10E, LOG2E, PI, SQRT1_2, SQRT2.
Functions: abs, acos, acosh, asin, asinh, atan, atanh, atan2, cbrt, ceil, clz32, cos, cosh, exp, expm1, floor, fround, hypot, imul, log, log1p, log2, log10, max, min, pow, random, round, sign, sin, sinh, sqrt, tan, tanh, trunc.
File I/O
| Function | Description | |
|---|---|---|
read_file(path) |
Read file → string | |
write_file(path, str) |
Write string to file | |
write_image(path, data) |
Write image data to file | |
list_dir(path) |
List directory → array of filenames | |
make_dir(path) |
Create directory → bool | |
rename(old, new) |
Rename or move file/directory | |
archive(src, dest?) |
Backup/copy file/directory | |
get_ext(path) |
Get a lowercase file extension | |
exists(path) |
Check whether a path exists | |
zip(src, dest?, op?) |
Create, list, or extract archives | |
trash(path, auto?) |
Delete (trash or permanent remove) |
Misc
| Function | Description | |
|---|---|---|
input(prompt) |
Read a line from stdin | |
show ... |
Print space-separated values to stdout | |
swap(str, a, b) |
String swap/replacement utility | |
define_tool(n,f,d) |
Register a function as a named AI tool | |
list_tools() |
List all registered tools | |
sesi(cmd, options?) |
Run a sesi script | |
exec(cmd) / run(cmd) |
Run a system shell command (blocked in Safe Mode) | |
spawn(path) |
Run a background Sesi script (blocked in Safe Mode) | |
python(code, args) |
Run inline Python code (blocked in Safe Mode) | |
js(code, args) |
Run inline JavaScript code (blocked in Safe Mode) | |
gif(input, output, options?) |
Create an animated GIF with FFmpeg (blocked in Safe Mode) | |
video(model) {config} {prompt} |
Generate Base64 video with Gemini Omni Flash or Veo | |
video(input, output, options?) |
Create or transcode video with FFmpeg (blocked in Safe Mode) | |
ffmpeg(args, options?) |
Run structured FFmpeg arguments (blocked in Safe Mode) | |
html(body, opts) |
Build a complete HTML document string |
## FOR MORE, PLEASE VISIT "BUILTINS.md" EITHER IN
docs/ORnode_modules/@misterscan/sesi/docs/
---
15. Quirks & Gotchas (Critical)
1. No raw newlines between elements inside prompt {} or model {}
Raw newlines outside of string literals are statement separators — they break the block.
// ✅ Correct — all elements on one line
prompt title {"Hello," name "— version" version}
// ✅ Also correct — newline is INSIDE the string literal
prompt report {"Name: "name"
Score: "score}
// ❌ WRONG — raw newline between elements (outside string literals)
prompt title {
"Hello," name
}
// Same applies to model `{}` blocks and other similar constructs
// ✅ Correct — all elements on one line
model("gemini-3.1-flash-lite") {"What is the latest in tech? Return a list with the top 5 trends."}
// ✅ Also correct — newline is INSIDE the string literal
model("gemini-3.1-flash-lite") {"What is the latest in tech?
Return a list with the top 5 trends."}
// ❌ WRONG — raw newline between elements (outside string literals)
model("gemini-3.1-flash-lite") {
"What is the latest in tech?
Return a list with the top 5 trends.
"
// ❌ Also WRONG — raw newline between elements (outside string literals) and manual string concatenation
model("gemini-3.1-flash-lite") {
"What is the latest in tech?" +
"Return a list with the top 5 trends."
}
}
2. Object keys: quoted vs unquoted
// Object literals: quoted
let obj = {"name": "Ada", "age": 42}
// Object literals: unquoted
let obj2 = {name: "Ada", age: 42}
// Config/schema blocks: unquoted identifiers
let r = model("gemini-3.6-flash") {thinkingLevel: "low", max_tokens: 100} {"Hello"}
3. No const or var; top-level return is allowed
// ✅
let x = 10
fn double(n) { return n * 2 }
if x < 0 { return } // stop the script early
// ❌
const x = 10 // forbidden; use let
var y = 20 // forbidden; use let
4. show concatenation style
// ✅ Idiomatic — space-separated values
show "Hello," name "your score is" score
// ❌ Avoid
show "Hello, " + name + " your score is " + str(score)
5. Block condensing is valid
while x { x = x - 1 } // valid one-liner
if ready { show "go" } // valid one-liner
6. % operator for modulo
if i % 2 == 0 { continue }
7. Type aliases in signatures
num = number, str = string — both valid in fn parameter lists.
---
16. CLI Commands
npm run sesi <file>.sesi # Run a script
npm run sesi:local # Run a script with local permissions
npm run sesi <file>.sesi arg1 # Run with args (available as args[] inside the script)
npm run eval "sesi code" # Inline eval (syntax testing)
npm run help # Show CLI help
npm run lint # Run linter on workspace
npm run lint <file>.sesi # Run linter on a specific file
npm run encrypt # Encrypt a .sesi file
npm run decrypt # Decrypt a .sesi file
Visit CLI.md for more details.
Aliases via node bin/sesi.js:
-e "code"→ inline eval-l file.sesi→ run file-h→ help-enc/-dec→ encrypt/decrypt
---
17. Idiomatic Patterns from Real Scripts
Starfield / random particle generation
let i = 0
while i < 180 {
let cx = random() * 800
let cy = random() * 800
let r = random() * 1.6 + 0.4
Draw.circle(cx, cy, r, "#ffffff", {opacity: random() * 0.7 + 0.3})
i = i + 1
}
Conditional color assignment
let color = "#00ffff"
if random() > 0.5 { color = "#ff00ff" }
Polygon point string construction
let pts = str(x1) + "," + str(y1) + " " + str(x2) + "," + str(y2)
Draw.polygon(pts, "url(#myGrad)")
Accumulating loop with counter variable
let lineY = 418
for i = 0 to 9 {
Draw.line(292, lineY, 360, lineY, "#f43f5e", {"stroke-width": 4.5})
lineY = lineY + 12
}
Scoped function inside another function (valid)
fn draw_building(x, y, w, h, has_antenna) {
fn draw_antenna(ax, ay) {
Draw.line(ax, ay, ax, ay - 40, "#768da3", {"stroke-width": 2})
Draw.circle(ax, ay - 40, 4, "#ffffff", {filter: "url(#glow)"})
}
if has_antenna { draw_antenna(x + w / 2, y) }
}
Test runner pattern
allow "bin/test-runner" in with { assert_equals, run_test_suite }
let results = [
assert_equals(add(2, 3), 5, "add two positives"),
assert_equals(add(-1, 1), 0, "add to zero")
]
run_test_suite("Math Tests", results)
---
18. Agent Debug Protocol (Mandatory Workflow)
- Draft in file — write the
.sesiscript in your editor - Eval risky snippets —
npm run eval "sesi code"to validate isolated blocks - Fix in file only — never use
sed,awk, or shell manipulation on.sesifiles - Run the full script —
npm run sesi <file>.sesionly after eval passes - File-aware help —
node bin/sesi.js -h <file>.sesi "question"when stuck
ABSOLUTE RULE: Never edit
.sesifiles via terminal shell tools. Always use the IDE editor directly.
---
19. Quick Cheat Sheet
// Variables
let x = 42
let msg = "hello"
let items = [1, 2, 3]
let cfg = {key: "val"}
// Functions
fn add(a: num, b: num) -> num { return a + b }
fn greet(name: str = "World") { show "Hello," name }
// Control flow
if x > 0 { show "pos" } else { show "neg" }
for item in items { show item }
for i = 0 to 5 { show i }
while x > 0 { x = x - 1 }
// Prompt blocks
prompt msg {"Value is:" x "and name is" name}
// Modules
allow "std/draw" in as Draw
allow "std/db" in with {db_open}
allow "std/game" in as Game
import { fn1, fn2 } from "mymodule"
// Error handling
try { let data = read_file("f.json") } catch (err) { show "Error:" err }
// AI
let r = model("gemini-3.8-flash") {thinkingLevel: "low"} {"Question:" q}
let img = image("gemini-3.1-flash-image-lite") {ratio: "1:1"} {"A forest"}
write_image("out.png", img)
// Network
let body = web_get("https://api.example.com")
let data = from_json(body)
let res = web_send("https://api.example.com", to_json({k: "v"}))
// JSON
let json_str = to_json({name: "Ada"})
let obj = from_json(json_str)
// SVG
allow "std/draw" in as Draw
Draw.rect(0, 0, 400, 300, "#1a1a2e")
Draw.circle(200, 150, 80, "#e94560")
Draw.text(100, 160, "Hello", 28, "white")
Draw.save_svg("out.svg", 400, 300)
3D desktop games
Install @misterscan/sesi-game, run with sesi -l, and use live native handles:
allow "std/game" in as Game
let game = Game.create({title: "Guide Game"})
let scene = game.create_scene("main")
let camera = scene.orbit_camera("camera", {radius: 10})
let light = scene.hemispheric_light("light")
let cube = scene.box("cube")
fn update(dt) { cube.rotation.y = cube.rotation.y + dt }
game.on_update(update)
game.run()
Game scenes use left-handed coordinates: Y is up, +X is right, and +Z is forward. Relative generated and registered asset paths both resolve from the current working directory.
Read getting-started/GAME.md for the complete scenes, assets, physics, input, GUI, audio, storage, permissions, and build API.