ailang

star 3

Write, read, and compile AiLang (`.ail`) source with the self-hosted `ailc` compiler. Use whenever the user asks for AiLang code, references AiLang syntax, mentions an `.ail` file, asks about the `ailc` compiler, or works inside an AiLang project. AiLang is a compiled, statically-typed language with a deliberately minimal-token syntax — 2-char keywords (`fn`/`lp`/`mt`/`rt`/`el`/`en`/`st`), optional type annotations (default `i64`), implicit `main`, implicit return. It compiles to C and links via `clang -O2`. Supports structs, classes (single inheritance + virtual methods), enums/recursive ADTs, real generics (generic structs `Box<T>` + enums `Option<T>` + multi-param `<A,B>` + `tr` trait bounds), operator overloading, closures with capture, `!T`/`?` error propagation, string interpolation `"${e}"`, UFCS, module namespacing (`im "…" as m`), C++ library interop via `csrc`, a multi-error type checker with *"did you mean?"* suggestions, and a 16-module stdlib (sockets/HTTP/TLS/Postgres/Redis/WebSocket/JSON/CSV/ti

Ray-T-r By Ray-T-r schedule Updated 6/8/2026

name: ailang description: Write, read, and compile AiLang (.ail) source with the self-hosted ailc compiler. Use whenever the user asks for AiLang code, references AiLang syntax, mentions an .ail file, asks about the ailc compiler, or works inside an AiLang project. AiLang is a compiled, statically-typed language with a deliberately minimal-token syntax — 2-char keywords (fn/lp/mt/rt/el/en/st), optional type annotations (default i64), implicit main, implicit return. It compiles to C and links via clang -O2. Supports structs, classes (single inheritance + virtual methods), enums/recursive ADTs, real generics (generic structs Box<T> + enums Option<T> + multi-param <A,B> + tr trait bounds), operator overloading, closures with capture, !T/? error propagation, string interpolation "${e}", UFCS, module namespacing (im "…" as m), C++ library interop via csrc, a multi-error type checker with "did you mean?" suggestions, and a 16-module stdlib (sockets/HTTP/TLS/Postgres/Redis/WebSocket/JSON/CSV/time/str/math/threads/seq/web/jwt/mysql).

AiLang Quick Reference (self-hosted ailc)

AiLang's compiler is written in AiLang itself (ailc). It lexes, parses, type-tracks, and lowers .ail → C, then drives clang -O2 to a native binary. The source syntax is optimized for low token count when generated by an LLM; the binary runs at C speed because it is C, generated.

CLI — this is different from older docs

ailc [--keep-c|-k] <input.ail> [output-binary]
  • ailc only PRODUCES a binary — it does not run it. To run: ailc foo.ail && ./foo.
  • Positional only. Arg 1 = input .ail; optional arg 2 = output path. Default output = input with .ail stripped (fib.ailfib).
  • --keep-c / -k keeps the generated <output>.c (deleted by default after a successful compile).
  • There is NO run, compile, --emit-c, --backend, or any subcommand. (Old docs for the Rust ailangc run/compile are obsolete — that's a different, archived compiler.)
  • im "..." imports resolve relative to the source file; std/ modules resolve via $AILANG_STD (set by the installer — see "Standard library" below). ailc then shells out to clang, auto-adding -lgc and (when used) OpenSSL / libpq / -lm.
echo 'println("hi")' > hi.ail
ailc hi.ail && ./hi          # hi
ailc hi.ail /tmp/hi && /tmp/hi
ailc -k hi.ail && cat hi.c   # inspect generated C

On Windows

ailc runs natively and writes a .exe: ailc hi.ail hi produces hi.exe, run it with .\hi.exe (not ./hi). The .exe is self-contained (depends only on KERNEL32/msvcrt). Networking/regex programs (sockets/HTTP/TLS/Postgres/Redis/regex_*) are POSIX-only — build and run those under WSL, not native ailc.

Mental model

  • Default type is i64. Unannotated params and locals are i64.
  • All keywords are short (fn, lp, mt, rt, el, en, st, mu, im, ex). Do not substitute for/while/match/return/else/enum/struct/let/var.
  • Top-level statements form an implicit main — don't write fn main for a script.
  • The trailing expression of a function/block body is its return value — no rt needed at the end.
  • if and mt and {...} blocks are expressions — they yield a value.

Keywords

kw meaning kw meaning
fn function / lambda mt match
rt return (early only) st struct declaration
if / el if / else (el if chains) en enum / ADT declaration
lp loop (while + for-in + range + k,v) im import a file
br / ct break / continue ex extern C function
mu mutable binding cinc include a C header
in iterator separator in lp true false bool literals
cl class (single inheritance) vt virtual-method marker
csrc compile + link a C++ shim super parent-method call (in a method)
tr trait declaration (generic bound) <T> <A,B> generic type params

println / print are builtins, not keywords. Full-word forms do not exist — use the short keyword: cl (not class), st (not struct), en (not enum), lp (not for/while), el (not else), rt (not return), mt (not match). Also no def / let / var.

Operators

declare     :=                 introduce a NEW binding
assign      =                  reassign an existing `mu` binding
compound    += -= *= /= %=     (binding must be mu)
arithmetic  + - * / %          (- x is unary negate)
concat      +  (both str)  or  ++   (explicit string concat)
compare     == != < <= > >=
logical     && || !
bitwise     & | ^ << >>        (prefix & is address-of)
pipe        |>                 x |> f  ⇒ f(x);  x |> f(b) ⇒ f(x, b)
coalesce    ??                 m[k] ?? default
error prop  expr?              postfix — propagate err inside an !T fn
range       .. ..=             ONLY inside `lp i in lo..hi` (exclusive / inclusive)
interp      "${expr}"          string interpolation

No ternary cond ? a : b — use an if expression. ? is only postfix error-propagation.

Bindings

x := 10           // immutable (cannot reassign)
mu n := 5         // mutable
n = 7             // ok — n is mu
n += 1            // compound needs mu too

Empty literals need a type annotation so element types are known:

mu xs:[i64] := []
mu m:{str:i64} := {}

Functions, generics, closures

fn add(a, b) a + b                       // expression body; params default i64
fn fib(n) {                              // block body
  if n < 2 rt n                          // braceless single-stmt if + early return
  fib(n-1) + fib(n-2)                    // trailing expr = return value
}
fn greet(name:str) -> str "Hello, " + name + "!"   // annotate non-i64

fn id<T>(x:T) -> T x                     // real generic, monomorphized to a real C fn per call
fn pick<A,B>(a:A, b:B) -> A a            // multi-param — A and B inferred independently
fn dump<T: Show>(x:T) -> str x.fmt()     // constrained — T must satisfy trait Show (see Traits)
fn map2<T,U>(xs:[T], f:fn(T)->U) -> [U] { map(xs, fn(x) f(x)) }  // generic HOF — takes a closure

inc := fn(x) x + 1                          // lambda (closure) — fn(params) body
println(inc(41))                            // 42 — stored lambda, direct call: fine
fn apply(f:fn(i64)->i64, a:i64) -> i64 { f(a) }  // fn-type param: annotate -> ret
println(apply(inc, 41))                     // 42 — stored lambda → user fn: fine
threshold := 3
println(filter([1,2,3,4,5], fn(x) x > threshold))  // [4, 5] — capture by value

Lambda syntax is fn(x) body or fn(x) { ... }. Not |x| ..., (x) => ..., or lambda x:.

Generic functions monomorphize into real C functions (one per type instantiation), so a generic fn may take a closure parameter (fn map2<T,U>(xs:[T], f:fn(T)->U)) and have a full multi-statement body with loops, locals, and early return. std/seq.ail is a combinator library built this way. (When you pass a lambda to such a fn over non-i64 elements, annotate the lambda's param to match: keep(words, fn(s:str) starts_with(s,"a")).)

Two real constraints of the current self-hosted compiler:

  • A fn that takes a fn(...)->R parameter and returns the result of calling it must annotate its return type (-> i64) or use an explicit rt — implicit-return inference fails there and defaults to void.
  • The builtins map/filter/reduce want the lambda inline (map(xs, fn(x) x*2)). A lambda stored in a variable works for direct calls and for your own fn-type params, but not as a map/filter/reduce argument.

Control flow

// lp has FOUR forms, one keyword:
lp i in 1..10 { print(i) }      // for-in range (exclusive; ..= inclusive)
lp x in nums { total += x }     // for-in collection
lp (k, v) in m { ... }          // map iteration, tuple-destructured
mu n := 5
lp n > 0 { n -= 1 }             // while
lp { ...; if done br }          // infinite loop

// if / el — also usable as an expression
grade := if s >= 90 { "A" } el if s >= 80 { "B" } el { "C" }

Structs

st Point { x:i64; y:i64 }              // fields separated by ; or ,
p := Point(3, 4)                        // positional construction
q := Point{ x: 0, y: 1 }                // named (order-independent)
println(p.x)                            // field access

Classes (single inheritance + explicit virtual)

cl Shape {
  tag:i64
  fn describe(self) -> i64 { self.tag }            // method: implicit self (*Shape)
  vt fn area(self) -> i64 { 0 }                    // `vt` = virtual (vtable dispatch)
}
cl Circle : Shape {                                // `: Base` = single inheritance
  r:i64
  vt fn area(self) -> i64 { 3*self.r*self.r }      // override
  vt fn name(self) -> str { "c/" ++ super.name() } // override + super call
}
c := Circle(0, 5)        // ctor: inherited fields first, then own → (tag, r)
println(c.area())        // 75  — UFCS call; virtual → Circle::area
println(c.describe())    // 0   — inherited static method
  • Methods take an implicit self (typed *ClassName); call with UFCS obj.m(args).
  • fn = static dispatch (by the receiver's static type); vt fn = virtual (runtime dispatch via a vtable). A same-named vt fn in a subclass overrides it; super.m() calls the parent's impl.
  • A class IS a struct under the hood — Name(...) construction, println(obj), [Name] arrays, {str:Name} maps and !Name all work for free.
  • Single inheritance only. Lowers to plain C (vtables = function-pointer tables) → works on macOS, Linux, and Windows.

Operator overloading (structural). A class that defines a conventionally-named method gets the operator — no trait/keyword needed:

cl Vec2 { x:i64  y:i64
  fn add(self, o:Vec2) -> Vec2 { Vec2(self.x+o.x, self.y+o.y) }   // enables  a + b
  fn eq(self, o:Vec2) -> bool { self.x==o.x && self.y==o.y }      // enables  a == b
}
c := a + b        // → a.add(b)

Map: + - * / %add sub mul div mod; == != < > <= >=eq ne lt gt le ge. Dispatch is on the LEFT operand's class; bind intermediates (c := a+b) before chaining .m().

Traits & generic bounds (structural — no impl)

tr Show { fn fmt(self) -> str; }                 // a bundle of required method names
cl Dog { nm:str  fn fmt(self) -> str { "dog:" + self.nm } }   // satisfies Show by HAVING fmt
fn dump<T: Show>(x:T) -> str { x.fmt() }         // bound: T must provide Show's methods
println(dump(Dog("rex")))                        // dog:rex

A type satisfies a trait just by defining its methods (Go-interface style). The checker enforces the bound at the call site: dump(5)type 'i64' does not satisfy bound 'Show': missing method 'fmt'.

Generic data types (monomorphized per use)

st Box<T> { val: T }                             // generic struct
st Pair<A,B> { a: A  b: B }                       // multi-param
en Option<T> { Some(v:T), None }                  // generic enum
en Result<T> { Ok(v:T), Err(e:str) }

b := Box(5)            // → Box_i64 (T inferred from the ctor arg)
p := Pair(3, "hi")     // → Pair_i64_str  (order matters: Pair_i64_str ≠ Pair_str_i64)

fn lookup(k:i64) -> Option<i64> { if k>0 { Some(k*10) } el { None } }
fn opt_get(o:Option<i64>) -> i64 { mt o { Some(v) => v; None => 0-1 } }
  • Some(x) / Ok(x) infer the type param from the payload. A payload-less / non-T variant (None, Err) infers it from the enclosing fn's declared return type — so return None from a -> Option<i64> fn; f(None) at a call site (no context) is not supported.
  • Use an explicit annotation (Box<i64> in a param/field/return) for instantiations over a non-scalar type (e.g. Box<Vec2>).

Enums / ADTs (recursive OK — self-references are heap-boxed)

en Color { Red, Green, Blue }           // nullary variants = bare names
c := Blue

en Expr {                               // recursive ADT
  Num(v:i64),
  Add(l:Expr, r:Expr),
  Neg(x:Expr),
}
e := Add(Num(2), Neg(Num(3)))           // variant = call-style constructor

fn eval(x:Expr) -> i64 {
  mt x {                                 // match — `;` separates arms, `=>` per arm
    Num(v)   => v;
    Add(l,r) => eval(l) + eval(r);
    Neg(y)   => 0 - eval(y);
  }
}

mt is an expression; variant patterns bind positionally. Also supported: a _ wildcard arm, per-arm guards (Circle(r) if r > 10 => …), and one-level nested destructuring (Some(Pair(a,b)) => …). The checker verifies exhaustiveness (guard-aware — a guarded arm doesn't count as covering), variant validity, and binding arity, reported at the .ail line.

Types

  • Primitives: i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 bool str bytes void. Caveat: integer widths are cosmetic — most lower to 64-bit; f32/f64 are both double. Don't rely on wraparound.
  • str is an immutable string; bytes is a binary buffer (may contain NUL).
  • Composite: [T] array, {K:V} map (open-addressing hash), *T pointer, !T result, (A, B, ...) tuple, fn(A,B)->R closure type.
*T pointers:  fn bump(c:*Counter) { c.n = c.n + 1 }   // p.f auto-derefs
              bump(&ctr)                                // &x = address-of
tuples:       a, b := divmod(17, 5)                     // multi-return + destructure

Error handling: !T + ?

fn parse(s:str) -> !i64 {
  if regex_match("^-?[0-9]+$", s) rt ok(str_to_int(s))
  err_i64("not a number: " + s)         // err_<type>(msg): err_i64/err_str/err_f64/...
}

fn sum2(a:str, b:str) -> !i64 {
  x := parse(a)?                          // ? propagates the err to this fn's return
  y := parse(b)?
  ok(x + y)
}

r := sum2("3", "4")
if is_ok(r) println(unwrap(r)) el println(err_msg(r))

? only works inside a fn whose return type is !T. The implicit top-level main is not !T — wrap fallible code in a fn run() -> !i64 { ... }. No generic err() — the constructor name encodes the type.

String interpolation, UFCS, C interop

name := "Ada"; n := 6
println("lang=${name} sq=${n * n}")     // ${expr} holes; required braces

s.f(a)                                   // UFCS: sugar for f(s, a)
s.cstr                                   // property sugar for cstr(s)

cinc "math.h"                            // pull a C header into scope
ex fn sqrt(x:f64) -> f64                 // then bind its symbols
ex fn printf(fmt:str, ...) -> i32        // variadic extern
ex "z" fn zlibVersion() -> str           // "lib" → adds -lz

csrc "shim.cpp"                          // C++ interop (POSIX only): compile a C++
ex fn Acc_new() -> i64                    //   shim with clang++ & link it; bind its
ex fn Acc_free(h:i64)                     //   extern "C" fns. C++ objects = i64 handles

csrc links a C++ shim: expose extern "C" functions, declare them with ex fn, pass opaque C++ objects across as i64 handles (reinterpret_cast). macOS/Linux only — on Windows a csrc program errors clearly. Two forms — external file csrc "shim.cpp", or inline in one file with a backtick block (the C++ is extracted, compiled with clang++, and cleaned up):

csrc `
#include <set>
extern "C" { int64_t set_new(){ return (int64_t)new std::set<int64_t>(); } }
`
ex fn set_new() -> i64

Builtins (always in scope — no im)

Name Notes
print(x) / println(x) type-dispatched; arrays of scalars and scalar maps print directly; bools print true/false in every form — the true/false literal, a comparison (== < …), a logical (&& || !), an overloaded ==, a bool variable, or a bool-returning call
len(x) str / bytes / [T] / {K:V}
has(m, k) map membership
push/pop/sort/reverse/slice return a fresh array
keys(m) / values(m)
map(arr,f) / filter(arr,pred) / reduce(arr,init,f) pass the lambda inline (map(xs, fn(x) x*2)) — a stored-in-a-variable lambda is not accepted here
ok(v) / err_i64/err_str/err_f64/err_bool / unwrap/is_ok/is_err/err_msg !T result
str_to_int/int_to_str/str_to_float/float_to_str conversions
regex_match(pat,s) / regex_find(pat,s) POSIX extended
to_str(x) used by ${...} interpolation
time / io: now_ms(), mono_ms(), time_iso(ms), sleep_ms(ms), flush(), read_line(), read_stdin(), get_env(name) time_iso takes a ms timestamp — current ISO time is time_iso(now_ms()), not time_iso(). read_line() reads one line (returns "" at EOF and for a blank line); read_stdin() reads all of stdin (use it for multi-line/JSON input). flush() flushes stdout: lp { print(time_iso(now_ms()) + "\r"); flush(); sleep_ms(1000) }
socket/net builtins: tcp_*, sock_*, tls_*, pg_*, sha1, ... baked into codegen — no extern decls needed; POSIX-only (need WSL on Windows)

Don't name your own functions after a builtin (e.g. unwrap, split, len, keys, to_str) — the builtin wins and your fn is silently misrouted. Pick a distinct name (opt_get, not unwrap).

Standard library (im "std/<name>.ail")

module purpose key functions
std/math.ail libm + helpers sqrt/pow/sin/cos/log/floor/ceil(f64), min/max/ipow/gcd, rand/srand
std/str.ail string utils eq(a,b), parse_int(s), strcmp, atoi
std/time.ail timing tick(), elapsed_ms(t), since(t), sleep_s(s), now_iso()
std/sock.ail TCP must_listen(host,port,banner), sock_send_str_all(fd,s), env_int(name,def)
std/http.ail HTTP/1.1 http_recv_request(fd), http_method/http_path/http_header(req[,name]), http_text/http_json/http_html(status,body)
std/json.ail JSON (flat + nested) flat: parse_flat_obj_str/parse_flat_obj_int; nested: json_parse(s) -> Json, json_str(j), accessors obj_get(j,k)/arr_at(j,i)/arr_len/as_int/as_float/as_str/as_bool/is_null/json_keys
std/csv.ail CSV reader/writer csv_parse(text) -> [Row] (quoted fields, CRLF/LF), csv_emit(rows), csv_field(f), row_map(header,r) -> {str:str} (a Row wraps cells:[str])
std/tls.ail TLS I/O tls_send_str_all(ssl,s), tls_send_all(ssl,bytes)
std/pg.ail Postgres pg_must_connect(dsn), pg_one(conn,sql), pg_first_col(conn,sql) -> [str], pg_print_table(res)
std/redis.ail Redis redis_connect(host,port), redis_get/redis_set, redis_incr, redis_del, redis_ping
std/ws.ail WebSocket ws_handshake_response(key), ws_send_text(fd,p), ws_recv_text(fd), b64_encode(bytes)
std/thread.ail OS threads (pthread, POSIX) spawn(fn()->i64)/wait(h)/wait_all(hs), mutex()/lock/unlock, channel(cap)/send/recv/close (bounded blocking)
std/seq.ail generic combinators (|>-friendly) any/all/count/find_index/take/drop/keep/map_to/flat_map/fold/sort_by/for_each/zip_with — each takes a passed closure; annotate the lambda param when elements aren't i64
std/web.ail Express-style web framework (POSIX) web_new(), web_get/web_post/web_put/web_delete(&app, pat, fn(r:Req)->str), web_use(&app, mw) middleware, :id path params via req_param(r,"id"), web_handle(&app, raw)->resp (socket-free, testable), web_listen(&app, host, port) (live server). Handlers are closures in the routes table.
std/jwt.ail JWT HS256 (POSIX) jwt_sign(payload_json, secret)->str, jwt_verify(token, secret)->bool, jwt_payload(token)->str, jwt_claim(token, key)->str; b64url_encode/b64url_decode_str. Real interoperable tokens (byte-identical to PyJWT).
std/mysql.ail MySQL/MariaDB (libmysqlclient, POSIX) mysql_must_connect(host,user,pass,db,port), mysql_one(c,sql)->str, mysql_rows(c,sql)->[MRow], mysql_exec/mysql_escape/mysql_close. Opt-in (only programs that use it link -lmysqlclient); needs the client lib + a server. (Postgres: std/pg.ail.)

std/math.ail and std/sock.ail are auto-imported. The net/TLS/PG/Redis/thread builtins are baked into codegen, so the modules are thin convenience wrappers.

Resolving std/. im "std/…" is searched in three places, in order: (1) beside your source file, (2) $AILANG_STD/std/…, (3) beside the ailc binary. The installer sets AILANG_STD — and it must point at the directory that contains std/ (e.g. the repo root), not at std/ itself (a common mistake: AILANG_STD=…/std makes it look for …/std/std/math.ail). So once AILANG_STD is set, im "std/math.ail" resolves from any directory; otherwise put a std/ next to your .ail or next to ailc. An import that resolves nowhere is a hard error (it no longer silently drops).

Namespaced imports. im "path" as m aliases a module; call its fns qualified as m.fn(...). The module's functions are isolated under the alias, so two modules can define the same name without colliding (im "a.ail" as a + im "b.ail" as ba.run() / b.run()). Plain im "path" still splices unqualified.

Top gotchas (what an LLM gets wrong)

  1. No run/compile subcommand. ailc src.ail [out], then run the binary yourself. The old ailangc run syntax is dead.
  2. el not else; rt not return; lp not for/while; en not enum; st not struct.
  3. Reassignment needs mu. x := v is const; use mu x := v then x = v.
  4. String interpolation is "${expr}" — braces required. Not {}, %s, or $var.
  5. No ternary — use if c { a } el { b } as an expression.
  6. Errors are !T + ok()/err_<type>()/?, not exceptions. ? only inside an !T fn.
  7. Enum variants are call-style (Add(l,r), bare Red) and matched with mt x { V(b) => ...; } using ; separators.
  8. Lambdas are fn(x) body — no |x| / => / -> arrow forms.
  9. Implicit main — don't wrap a top-level script in fn main.
  10. Integer widths are cosmetic (stored 64-bit). The type checker is conservative but real — it reports confident mistakes at the .ail line:col (type/!T mismatches, mt exhaustiveness/variants/arity, call & generic arity, <T: Trait> bounds, generic-instance mismatches), all errors in one run, with "did you mean?" spelling suggestions. It's not a full type system, so some mistakes still surface as C-compiler errors.
  11. map/filter/reduce need an inline lambdamap(xs, fn(x) x*2), not a lambda stored in a variable. (std/seq.ail's keep/map_to/fold accept a passed/stored closure where the builtins won't.) And a non-generic fn that returns the result of calling a fn(...)->R parameter must annotate its return type (-> i64) or use explicit rt.

Worked examples

// hello — implicit main
println("hello, AiLang")
// fizzbuzz — lp range + mt tuple patterns
lp i in 1..16 {
  mt (i%3, i%5) {
    (0,0) => println("FizzBuzz");
    (0,_) => println("Fizz");
    (_,0) => println("Buzz");
    _     => println(i);
  }
}
// recursive fib
fn fib(n) {
  if n < 2 rt n
  fib(n-1) + fib(n-2)
}
println(fib(30))                          // 832040
// arrays, maps, higher-order
nums := [5, 2, 8, 1, 9]
println(len(nums))                        // 5
println(reduce(nums, 0, fn(a, b) a + b))  // 25
mu counts:{str:i64} := {}
lp w in ["a", "b", "a"] { counts[w] = counts[w] + 1 }
println(counts["a"])                      // 2
// recursive ADT + match expression
en Tree { Leaf(v:i64), Node(l:Tree, r:Tree) }
fn sum(t:Tree) -> i64 {
  mt t {
    Leaf(v)   => v;
    Node(l,r) => sum(l) + sum(r);
  }
}
println(sum(Node(Leaf(1), Node(Leaf(2), Leaf(3)))))   // 6
// !T result + ? propagation
fn half(n) -> !i64 {
  if n % 2 == 0 rt ok(n / 2)
  err_i64("odd: ${n}")
}
fn run() -> !i64 {
  a := half(8)?
  b := half(a)?
  ok(a + b)
}
r := run()
if is_ok(r) println(unwrap(r)) el println(err_msg(r))   // 6
// tiny HTTP server (std/sock auto-imported; im http)
im "std/http.ail"
fd := must_listen("127.0.0.1", 8080, "listening on :8080")
lp {
  cli := tcp_accept(fd)
  req := http_recv_request(cli)
  sock_send_str_all(cli, http_text(200, "you asked for ${http_path(req)}\n"))
  sock_close(cli)
}

When unsure, mimic the shape of programs in examples-selfhost/*.ail rather than translating literally from another language.

Install via CLI
npx skills add https://github.com/Ray-T-r/AiLang --skill ailang
Repository Details
star Stars 3
call_split Forks 1
navigation Branch main
article Path SKILL.md
More from Creator