Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions lib/skill.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
---
name: chadscript
description: Use when writing ChadScript applications, compiling TypeScript to native binaries, or working with the chad CLI. Triggers on ChadScript projects (chadscript.d.ts present), chad CLI usage, or native compilation tasks.
---

# ChadScript

ChadScript compiles TypeScript to native binaries via LLVM IR. It produces fast, single-file executables with no runtime dependency on Node.js.

## CLI

```bash
chad build app.ts # compile to .build/app
chad build app.ts -o myapp # compile with custom output
chad run app.ts # compile and run
chad run app.ts -- arg1 # pass args to the binary
chad watch app.ts # recompile+run on file changes
chad init # scaffold a new project
chad ir app.ts # emit LLVM IR only
```

## Project Setup

Run `chad init` to generate `chadscript.d.ts` (type definitions), `tsconfig.json`, and `hello.ts`.

The `chadscript.d.ts` file provides type definitions for ChadScript-specific APIs. Keep it in your project root.

## Supported TypeScript Features

ChadScript supports a practical subset of TypeScript:

- Classes with inheritance, interfaces, generics (type-erased)
- Arrays (`number[]`, `string[]`, `object[]`), Maps, Sets
- String/array methods: `map`, `filter`, `find`, `reduce`, `push`, `pop`, `split`, `trim`, `replace`, `indexOf`, `includes`, `slice`, `substring`, `startsWith`, `endsWith`, `padStart`, `padEnd`, etc.
- Template literals, destructuring, spread operator
- `for...of`, `for` loops, `while`, `if/else`, ternary, switch
- `async/await` with `fetch()`, `setTimeout`, `setInterval`
- Closures (capture by value — mutations after capture won't be seen)
- Enums (numeric and string)
- Type assertions (`as`), optional chaining (`?.`), nullish coalescing (`??`)

## Built-in Modules

```typescript
import * as fs from "fs"; // readFileSync, writeFileSync, existsSync, etc.
import * as path from "path"; // join, resolve, dirname, basename, extname
import * as os from "os"; // platform, arch, cpus, totalmem, homedir
import * as child_process from "child_process"; // execSync
```

Also available globally: `console`, `process`, `JSON`, `Math`, `Date`, `setTimeout`, `setInterval`.

## Embedding Files (Single-Binary Deploys)

Embed files at compile time — they're baked into the binary:

```typescript
ChadScript.embedFile("./config.json");
ChadScript.embedDir("./public");

const config = ChadScript.getEmbeddedFile("config.json");
const binary = ChadScript.getEmbeddedFileAsUint8Array("image.png");
```

## HTTP Server

```typescript
function handler(req: HttpRequest): HttpResponse {
return { status: 200, body: "hello", headers: "" };
}
httpServe(3000, handler);
```

Combine with `ChadScript.embedDir("./public")` and `ChadScript.serveEmbedded(req.path)` for static file serving from a single binary.

## Key Differences from Node.js TypeScript

- **No `node_modules` imports** — only built-in modules and local files
- **No `any` type** — all values must have a concrete type
- **Closures capture by value** — mutating a variable after it's captured in a closure won't update the closure's copy
- **`process.exit()` is required** at the end of synchronous programs (no implicit exit)
- **Generics use type erasure** — `T` becomes `i8*` (opaque pointer), numeric type params not supported
- **Union types** limited to members with the same LLVM representation

## Cross-Compilation

```bash
chad target add linux-x64
chad build app.ts --target linux-x64
```
15 changes: 15 additions & 0 deletions src/chad-native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
const dtsContent = ChadScript.embedFile("../chadscript.d.ts");
registerStdlib("argparse.ts", ChadScript.embedFile("../lib/argparse.ts"));
registerStdlib("http.ts", ChadScript.embedFile("../lib/http.ts"));
const skillContent = ChadScript.embedFile("../lib/skill.md");
import { ArgumentParser } from "chadscript/argparse";

declare const fs: {
Expand Down Expand Up @@ -72,6 +73,7 @@ parser.addSubcommandInGroup("target", "Manage cross-compilation target SDKs", "A
parser.addSubcommandInGroup("ast-dump", "Dump parsed AST as JSON", "Advanced");

parser.addFlag("version", "", "Show version");
parser.addFlag("skill", "", "Print Claude Code skill to stdout");
parser.addScopedOption("output", "o", "Specify output file", "", "build,run,ir");
parser.addScopedFlag("verbose", "v", "Show compilation steps", "build,run,ir");
parser.addScopedFlag("debug-info", "g", "Emit DWARF debug info (skips stripping)", "build,run");
Expand Down Expand Up @@ -108,6 +110,11 @@ if (parser.getFlag("version")) {
process.exit(0);
}

if (parser.getFlag("skill")) {
console.log(skillContent);
process.exit(0);
}

const command = parser.getSubcommand();

if (command === "init") {
Expand All @@ -132,6 +139,14 @@ if (command === "init") {
fs.writeFileSync("hello.ts", 'console.log("Hello from ChadScript!");\nprocess.exit(0);\n');
console.log(" created hello.ts");
}
const skillPath = ".claude/skills/chadscript/SKILL.md";
if (fs.existsSync(skillPath)) {
console.log(" skip " + skillPath + " (already exists)");
} else {
child_process.execSync("mkdir -p .claude/skills/chadscript");
fs.writeFileSync(skillPath, skillContent);
console.log(" created " + skillPath);
}
console.log("");
console.log("Ready!");
console.log("");
Expand Down
7 changes: 7 additions & 0 deletions src/chad-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ parser.addSubcommandInGroup("target", "Manage cross-compilation target SDKs", "A
parser.addSubcommandInGroup("ast-dump", "Dump parsed AST as JSON", "Advanced");

parser.addFlag("version", "", "Show version");
parser.addFlag("skill", "", "Print Claude Code skill to stdout");
parser.addScopedOption("output", "o", "Specify output file", "", "build,run,ir");
parser.addScopedFlag("verbose", "v", "Show compilation steps", "build,run,ir,watch");
parser.addScopedFlag("debug", "", "Show internal debugging information", "build,run,ir");
Expand Down Expand Up @@ -97,6 +98,12 @@ if (parser.getFlag("version")) {
process.exit(0);
}

if (parser.getFlag("skill")) {
const skillPath = path.join(import.meta.dirname || process.cwd(), "../lib/skill.md");
process.stdout.write(fs.readFileSync(skillPath, "utf8"));
process.exit(0);
}

const command = parser.getSubcommand();

if (command === "init") {
Expand Down
11 changes: 10 additions & 1 deletion src/codegen/stdlib/init-templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ import { fileURLToPath } from "url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));

function getDtsContent(): string {
// Read the canonical chadscript.d.ts from the repo root
return fs.readFileSync(path.join(__dirname, "../../../chadscript.d.ts"), "utf8");
}

function getSkillContent(): string {
return fs.readFileSync(path.join(__dirname, "../../../lib/skill.md"), "utf8");
}

const TSCONFIG_CONTENT = `{
"compilerOptions": {
"target": "ES2020",
Expand All @@ -25,16 +28,22 @@ const HELLO_CONTENT = `console.log("Hello from ChadScript!");
`;

export function runInit(): void {
const skillDir = path.join(".claude", "skills", "chadscript");
const files: Array<{ name: string; content: string }> = [
{ name: "chadscript.d.ts", content: getDtsContent() },
{ name: "tsconfig.json", content: TSCONFIG_CONTENT },
{ name: "hello.ts", content: HELLO_CONTENT },
{ name: path.join(skillDir, "SKILL.md"), content: getSkillContent() },
];

for (const file of files) {
if (fs.existsSync(file.name)) {
console.log(` skip ${file.name} (already exists)`);
} else {
const dir = path.dirname(file.name);
if (dir !== "." && !fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(file.name, file.content);
console.log(` created ${file.name}`);
}
Expand Down
Loading