Help us improve
Share bugs, ideas, or general feedback.
From greycat
Generates and maintains .gcl files for GreyCat projects. Provides GCL syntax, node persistence patterns, typed CSV/JSON I/O, time series, geo coordinates, API exposure, and MCP tagging. For creating, editing, or debugging GreyCat code.
npx claudepluginhub datathings/marketplace --plugin greycatHow this skill is triggered — by the user, by Claude, or both
Slash command
/greycat:greycatThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Unified language + database (temporal/graph/vector) + web server + MCP. Built for billion-scale digital twins.
References GreyCat C API for native functions, tensors, objects, memory/crypto/I/O; GCL std library (core/runtime/io/util modules); plugin patterns like hooks, linking, thread safety.
Orchestrates Neo4j project from zero to running app in 8 stages: prerequisites, provision (Aura/local/Docker), model schema, load synthetic/CSV data, explore, query, build notebook/app.
Builds production-ready GraphQL servers with schema design, DataLoader resolvers, WebSocket subscriptions, and field-level authorization for Node.js and Python.
Share bugs, ideas, or general feedback.
Unified language + database (temporal/graph/vector) + web server + MCP. Built for billion-scale digital twins.
Verify with which greycat or greycat --version. If not found, confirm with user before installing:
Linux/Mac/FreeBSD: curl -fsSL https://get.greycat.io/install.sh | bash -s dev
Windows: iwr https://get.greycat.io/install_dev.ps1 -useb | iex
| Command | Description | Key Options |
|---|---|---|
greycat build | Compile project | --log, --cache |
greycat serve | Start server (HTTP + MCP) | --port=8080, --workers=N, --user=1 (dev only) |
greycat run | Execute main() or function | greycat run myFunction |
greycat test | Run @test functions | Exit 0 on success |
greycat install | Download dependencies | From project.gcl @library |
greycat codegen | Generate typed headers | TS, Python, C, Rust |
greycat defrag | Compact storage | Safe anytime |
greycat-lang lint --fix | Auto-fix errors | Run after code changes |
greycat-lang fmt | Format files | In-place |
greycat-lang server | Start LSP | --stdio for transport |
Environment: All --options have GREYCAT_* env equivalents. Use .env next to project.gcl.
Dev mode: --user=1 bypasses auth (NEVER in production).
Use /greycat:command-name in Claude Code:
| Command | Purpose | When |
|---|---|---|
/greycat:init | Initialize CLAUDE.md | New projects |
/greycat:tutorial | Interactive learning | Onboarding, learning |
/greycat:scaffold | Generate models, services, APIs, tests | Starting features |
/greycat:migrate | Schema evolution, imports, storage | Schema changes, bulk ops |
/greycat:upgrade | Update libraries | Monthly maintenance |
/greycat:backend | Backend review (dead code, anti-patterns) | Before releases |
/greycat:optimize | Auto-fix performance issues | Quick checks |
/greycat:apicheck | Review @expose endpoints | After endpoints added |
/greycat:coverage | Test coverage + suggestions | After sprints |
/greycat:frontend | Frontend review | Frontend features |
/greycat:docs | Generate README, API docs | Before releases |
/greycat:typecheck | Advanced type safety | After type changes |
After every code change, check for errors using the LSP or lint:
.gcl files. Check diagnostics after each write/edit. The LSP provides inline errors, hover info, and go-to-definition.greycat-lang lint (fallback) — use when LSP diagnostics are unavailable or for batch checking:greycat-lang lint -p project.gcl # check all project files
greycat-lang lint --fix -p project.gcl # auto-fix what it can
Repeat until zero errors.
Always open project.gcl first before any other .gcl file. The LSP needs it to initialize the project context — opening other files first will not trigger analysis.
project.gcl - Entry point, libs, permissions, roles, main(), init()src/<feature>/<feature>.gcl - Data models + global indicessrc/<feature>/<feature>_api.gcl - @expose functions and associated @volatile typessrc/<feature>/<feature>_reader.gcl - Readers (CSV, JSON, Parquet, etc.)src/<feature>/<feature>_writer.gcl - Writerstest/<feature>_test.gcl - TestsSmall features: src/<feature>.gcl — one file is fine.
See references/frontend.md for full setup.
project.gcl:
@library("std", "<version>"); // required — fetch latest: curl https://get.greycat.io/files/core/stable/latest
@library("explorer", "<version>"); // administration app served at /explorer
@include("src");
@include("test");
fn main() {}
Fetching latest library versions: curl https://get.greycat.io/files/<lib>/stable/latest — returns <MAJOR>.<MINOR>/<MAJOR>.<MINOR>.<PATCH>-<BRANCH> (use the part after /). Exception: std uses core as the URL path (all others match: ai, explorer, algebra, etc.).
Conventions: snake_case files, PascalCase types, _prefix unused, *_test.gcl tests
Primitives: int (64-bit, 1_000_000), float (3.14), bool, char, String ("${name}")
Casting — as int vs floor():
x as int — ROUNDS (nearest integer): 0.5 as int → 1, 2.4 as int → 2floor(x) as int — FLOORS (truncates toward −∞): 0.5 → 0, 2.9 → 2CRITICAL: For indices/buckets from float division, use floor(x) as int, NOT x as int.
String→number: parseNumber(s) returns any. Cast: parseNumber(s) as float or parseNumber(s) as int.
Time: time (μs epoch), duration (1_us, 500_ms, 5_s, 30_min, 7_hour, 2_day), Date (UI, needs timezone). Convert: Date::parse("2025-01-15 08:00", "%Y-%m-%d %H:%M").to_time(TimeZone::UTC). Use TimeZone:: enum values (e.g. TimeZone::"Europe/Luxembourg"), never raw strings.
Geo: geo{lat, lng} (positional, no field names). Access via methods: location.lat(), location.lng(), location.distance(other). Shapes: GeoBox, GeoCircle, GeoPoly (.contains(geo))
var list = Array<String>{}; var map = Map<String, int>{}; // use {}, NOT ::new()
@volatile type ApiResponse { data: String; } // non-persisted
NO TERNARY — use if/else: if (valid) { result = "yes"; } else { result = "no"; }
Non-null by default. Use ? for nullable:
var city: City?; // nullable
city?.name?.size(); // optional chaining
city?.name ?? "Unknown"; // nullish coalescing
data.get("key")!!; // non-null assertion (use sparingly)
Control flow narrowing — after a null guard, the type is narrowed automatically:
var n = index.get(id); // returns node<T>?
if (n == null) {
return null;
}
// n is now node<T> — NO !! needed
n.resolve(); // valid
n->name; // valid
CRITICAL: Do NOT use !! after if (x == null) { return; } — the LSP will warn about unnecessary non-null assertions. Same applies inside if (x != null) { ... } blocks.
Paren expr for cast + coalescing: (answer as String?) ?? "default" NOT answer as String? ?? "default"
Nodes are 64b references to persistent data.
type Country { name: String; code: int; }
var n = node<Country>{ Country { name: "LU", code: 352 } };
Node access — three distinct operations:
*n; // dereference: returns the inner T value
n->name; // field access: shorthand for (*n).name
n.resolve(); // node method: returns T? (nullable)
CRITICAL — -> calls methods on the inner type, . calls methods on node itself:
n->name; // accesses Country.name (field on inner type)
n.resolve(); // calls node.resolve() (method on node, returns T?)
n->resolve(); // WRONG: calls Country.resolve() which doesn't exist
Use node refs to share data: type City { country: node<Country>; } — light 64b ref vs full copy.
Single ownership: objects belong to ONE node. For multi-index, store node<T> refs:
var by_id = nodeList<node<Item>> {};
var by_name = nodeIndex<String, node<Item>> {};
var item = node<Item> { Item {} };
by_id.set(1, item); by_name.set("x", item); // both share same node
Transactions: atomic per function, rollback on error.
More patterns → references/nodes.md
| Key | In-Memory | Persisted |
|---|---|---|
int | Array<T> | nodeList<node<T>> |
K | Map<K, V> | nodeIndex<K, node<V>> |
time | Map<time, V> | nodeTime<node<T>> |
geo | Map<geo, V> | nodeGeo<node<T>> |
Other: Stack<T>, Queue<T>, Set<T>
var ni = nodeIndex<String, node<X>> {};
ni.set("key", val); ni.get("key"); // set/get, NOT add
var nt = nodeTime<float> {};
nt.setAt(t1, 20.5);
for (t, v in nt[from..to]) {}
var nl = nodeList<node<X>> {};
for (i, v in nl[0..100]) {}
Sampling: nodeTime::sample([series], start, end, 1000, SamplingMode::adaptative, null, null)
Sort: cities.sort_by(City::population, SortOrder::desc);
Initialize all non-nullable fields, including node collections inside type constructors:
var b = Box {}; // WRONG: non-nullable fields unset
var b = Box { x: 42 }; // RIGHT
var n = node<String> {}; // WRONG
var n = node<String> { "text" }; // RIGHT
var n = node<String?> {}; // RIGHT (nullable generic)
// Node collections inside types need explicit {} in constructors:
type City { name: String; streets: nodeIndex<String, node<Street>>; }
var c = node<City> { City { name: "Paris" } }; // WRONG
var c = node<City> { City { name: "Paris", streets: nodeIndex<String, node<Street>> {} } }; // RIGHT
Root-level variables must be nodes — graph entrypoints. Auto-initialized (no {} needed):
var count: node<int?>; // nullable generic for node
var by_id: nodeList<float>;
var cities: nodeIndex<String, node<City>>;
Models — global indices + types:
var cities_by_name: nodeIndex<String, node<City>>;
type City { name: String; country: node<Country>; }
API — return Array<XxxView> with @volatile, never nodeList:
@volatile
type CityView { name: String; country_name: String; }
@expose
fn getCities(): Array<CityView> { ... }
MCP exposure (sparingly — only high-value APIs):
/// Search cities by name
/// @param query City name or partial match
@expose
@tag("mcp")
fn searchCities(query: String): Array<CityView> { ... }
fn add(x: int): int { return x + 2; }
fn noReturn() { } // no void keyword
var lambda = fn(x: int): int { x * 2 };
for (k, v in map) { }
for (i, v in nullable?) { } // use ? for nullable iterables
Function parameters: use function keyword, calls return any? — always cast:
fn applyAll(arr: Array<any>, f: function) {
for (var i = 0; i < arr.size(); i++) { f(arr[i]); }
}
abstract type Building { address: String; fn calculateTax(): float; }
type House extends Building { fn calculateTax(): float { return value * 0.01; } }
More → references/modelling.md
info("msg ${var}"); warn("msg"); error("msg");
try { op(); } catch (ex) { error("${ex}"); }
var jobs = Array<Job<ResultType>> {};
for (item in items) { jobs.add(Job<ResultType> { function: processFn, arguments: [item] }); }
await(jobs, MergeStrategy::strict);
for (job in jobs) { results.add(job.result()); }
More → references/concurrency.md
Run greycat test --quiet. Single test: greycat test module_name::test_fn_name. Single module: greycat test module_name.
More → references/testing.md
Reserved keywords: limit, node, type, var, fn — do NOT use as variable/param names.
| Wrong | Correct |
|---|---|
Array<T>::new() | Array<T>{} |
(*node)->field | node->field |
n->resolve() | *n or n.resolve() |
x!! after null guard return | x (control flow narrows) |
@permission("api") fn getX() | @expose @permission("api") fn getX() |
nodeList<City> | nodeList<node<City>> for complex types |
nodeIndex.add(k, v) | nodeIndex.set(k, v) |
for(i, v in nullable_list) | for(i, v in nullable_list?) |
fn doX(): void | fn doX() |
fn somefn(f: fn(T): R) | fn somefn(f: function) |
(x / y) as int for floor | floor(x / y) as int |
geo { lat: x, lng: y } | geo{x, y} (positional, no spaces/names) |
date.to_time("UTC") | date.to_time(TimeZone::UTC) (enum) |
date.toTime(tz) | date.to_time(tz) (snake_case) |
City { name: "X" } (missing nodeIndex) | City { name: "X", streets: nodeIndex<..> {} } |
str.toLowerCase() | str.lowercase() (also uppercase()) |
str.split(";") | str.split(';') (takes char, not String) |
str[i] for char access | Not supported — no character indexing on strings |
DurationUnit::us | DurationUnit::microseconds |
Assert::notNull(x) | Assert::isNotNull(x) or Assert::isTrue(x != null) |
Math::random() | Random{}.uniform(min, max) |
Tuple return (A, B) | Not supported — use separate helper functions |
fn foo(): void | fn foo() — no void keyword |
CsvFormat { separator: "," } | CsvFormat { separator: ',' } (char, not String) |
Type named User, Job, Task | Conflicts with runtime:: built-ins — rename (e.g. Profile) |
Native (primitive) types (geo, time, duration, etc.) have no fields — use methods for access, never field syntax. Construction varies by type: geo uses positional geo{lat, lng}, time uses literals (5_time) or static methods (time::now(), time::new(epoch, unit), time::parse(str, fmt)), duration uses literals (1_us, 500_ms) or static methods (duration::new(v, unit)).
Adding a non-nullable field to an existing type breaks the ABI. Fix: make new fields nullable (int?).
type Foo { name: String; new_field: int?; } // nullable = safe ABI update
references/frontend.md — @greycat/web SDK, JSX, web components, codegen, auth.
@library("ai", "<version>"); // fetch latest: curl https://get.greycat.io/files/ai/stable/latest
fn main() {
var model = Model::load("llama", "./model.gguf", ModelParams { n_gpu_layers: -1 });
var result = model.chat([ChatMessage { role: "user", content: "Hello!" }], null, null);
}
references/libraries.md — available libraries with @library() declarations.
Use the LSP (hover, completion, go-to-def) for type signatures and API details.
Versions & downloads: https://get.greycat.io | Docs: https://doc.greycat.io/ | CLI: references/cli.md