smithery.ai

syntax

When writing new CSL code you must reference these docs to understand the syntax of .csl files.

First seen Apr 14, 2026

Installation

$ npx skills add https://smithery.ai

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery.ai · top by installs.

npx skills add https://smithery.ai

Browse all from smithery.ai

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 6,644 B
  • docs SUMMARY.md 109 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 1 installs

SKILL.md

Declarations

my_variable: int = 42; // Explicit type
my_variable := 42; // Type inferred
my_variable: int; // Zero-initialized

Integer literals coerce to float, but not the reverse.

Struct fields cannot have inline defaults.

Constants

Define const with :: Must be compile-time constant.

Global variable initializers must be compile-time constants. Zero-initialized file-scope vars are allowed for game-wide handles/registries, then assign them in aostart or aobeforesceneload for runtime init. Do not use globals for per-player state; store that on the player/component.

PI is already defined as a global.

Types

Primitive Types

  • Signed integers: s8, s16, s32, s64
  • Unsigned integers: u8, u16, u32, u64
  • Booleans: bool
  • Floats: f32, f64
  • Vector types: v2, v3, v4 (float fields .x, .y, .z, .w; constructed with v2{10, 20})
  • string, typeid, any

Strings and Template Strings

"..." strings support backslash escapes (\n, \t, \\, ...). Backtick strings are raw (no backslash escapes, newlines allowed) and support {expr} interpolation:

name := "Ada";
count := 0;
msg := `Hi {name}, you have {count + 1} new messages`; // any expression works inside {}
  • Interpolating templates compile to a format_string call, so they need import "core:basic" and are not compile-time constants.
  • A backtick string with no {expr} stays a plain raw string constant.
  • Literal braces: {{ and }}. A single } in text is a compile error.
  • Backtick strings cannot nest inside {} (use "..." strings there).

Structs

Structs are value types (shallow-copied on assignment/pass).

Food_Definition :: struct {
    name: string;
    food_value: int;
}

Classes: reference types allocated with new

Foo :: class {
    value: int;
    position: v2;
}

foo := new(Foo);
foo: Foo = new();
foo: Foo = new(Foo);

Inheritance

Dog :: class : Animal {
    breed: string;
}

Procedures

add :: proc(a: int, b: int) -> int {
    return a + b;
}

Methods

Dog :: class {
    name: string;

    bark :: method() {
        log_info(`{name} says bark!`); // implicit this.name
    }
}

dog := new(Dog);
dog.bark();

Arrays

  • Fixed: [N]T initialized with {...}
  • Slice: []T a view into array data (common for parameters)
  • Dynamic: [..]T resizable list (.count, .capacity); implicitly converts to []T
fixed: [4]int = {1, 2, 3, 4};
spawn_points: [3]v2 = {{0, 0}, {5, 0}, {0, 5}};
dyn: [..]int;
view: []int = dyn;

hit := Damage_Desc{amount=10, knockback={2, 1}}; // named fields use = not :

Dynamic Arrays

numbers: [..]int;
numbers.append(10);
numbers.pop();
numbers.clear();
numbers.reserve(64);

numbers.unordered_remove_by_value(10);
numbers.ordered_remove_by_value(999, .ALL); 
numbers.unordered_remove_by_index(0);
numbers.ordered_remove_by_index(0);

Control Flow

Conditions normally omit parentheses. Enum values use . prefix in switch:

Compound literals in control-flow conditions

A compound literal immediately before a control-flow body is ambiguous. This is the exception to the normal no-parentheses style: disambiguate with .{}, Type.{}, or parentheses.

if value == .{} {}
if value == v2.{} {}
if (value == v2{}) {}
switch tier {
    case .COMMON:    return {0.7, 0.7, 0.7, 1.0};
    case .RARE:      return {0.3, 0.5, 1.0, 1.0};
    default:         return {1.0, 1.0, 1.0, 1.0};
}

Cases support multiple values (comma-separated) and ranges (.., inclusive):

switch level {
    case 1, 2, 3: tier = .BEGINNER;
    case 4..10: tier = .INTERMEDIATE;
    case 11..20, 25, 30..50: tier = .ADVANCED;
    default: tier = .UNKNOWN;
}

Multi-statement bodies use braces

switch tier {
    case .COMMON, .UNCOMMON: {
        color = {0.7, 0.7, 0.7, 1.0};
        label = "Common";
    }
    default: {
        color = {1.0, 1.0, 1.0, 1.0};
        label = "Unknown";
    }
}

Do not write C-style fallthrough logic

for also handles custom iterators: for player: componentiterator(MyPlayer) { } Numeric loops use for i: 0..3 { } (inclusive) or for i: 0..<3 { } (half-open), never for i in ....

Custom iterator-based for loops require a next :: method() -> bool and a current field.

Item_Tier :: enum {
    COMMON;
    RARE;
}

if !#alive(target) return;

UI.push_screen_draw_context();
defer UI.pop_draw_context();

#alive(expr) checks whether a class reference is still valid and MUST be used before accessing to prevent crashes. defer runs a statement when the current scope exits.

Type Casting

Use expr.(T) syntax: b := 123.4.(int);

Parameter Passing: ref

Mark both the parameter and callsite with ref. When forwarding a ref param use ref again:

update_health :: proc(health: ref int, damage: int) {
    health -= damage;
}

hp := 100;
update_health(ref hp, 25);

Polymorphic Procedures

$T on a parameter deduces the type from the callsite. $T is only used when defining polymorphic procs — callers always pass concrete types:

min :: proc(a: $T, b: T) -> T {
    if a < b return a;
    return b;
}

result := min(3, 5); // T is deduced as int

T is a type only inside a polymorphic declaration that binds $T; it is not a generic placeholder elsewhere. In concrete code use the actual type name, such as component_iterator(Enemy) or [..]Enemy.

Function Pointers and Callbacks

CSL has no closures. Proc literals cannot capture enclosing parameters or locals. Pair a callback field with a userdata: Object field. Any class instance can be stored as Object and cast back:

on_death_userdata: Object;
on_death: proc(player: Player, userdata: Object);

// Setting the callback:
player.on_death_userdata = this;
player.on_death = proc(player: Player, userdata: Object) {
    tracker := userdata.(Death_Tracker);
    tracker.death_count += 1;
};

Using Keyword - access fields without a selector:

using position: v3;
x = 123; // Instead of position.x

Runtime Type Checking

Use .#type to get the runtime type of a class instance:

if effect.#type == Slow_Effect {
    slow := effect.(Slow_Effect);
}

Multiple Return Values

get_thing :: proc() -> Thing, bool {
    return g_thing, true;
}

thing, ok := get_thing();
if thing, ok := get_thing(); ok { }