diff --git a/lib/skill.md b/lib/skill.md new file mode 100644 index 00000000..21bed939 --- /dev/null +++ b/lib/skill.md @@ -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 +``` diff --git a/src/chad-native.ts b/src/chad-native.ts index 30fd1746..96f76c7b 100644 --- a/src/chad-native.ts +++ b/src/chad-native.ts @@ -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: { @@ -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"); @@ -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") { @@ -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(""); diff --git a/src/chad-node.ts b/src/chad-node.ts index e1311230..708f23b8 100644 --- a/src/chad-node.ts +++ b/src/chad-node.ts @@ -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"); @@ -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") { diff --git a/src/codegen/stdlib/init-templates.ts b/src/codegen/stdlib/init-templates.ts index f4b1ac19..6ec3a24b 100644 --- a/src/codegen/stdlib/init-templates.ts +++ b/src/codegen/stdlib/init-templates.ts @@ -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", @@ -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}`); }