tjs-lang 0.5.4 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CLAUDE.md CHANGED
@@ -4,14 +4,13 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
4
4
 
5
5
  ## Project Overview
6
6
 
7
- **tjs-lang** (npm: `tjs-lang`) is a type-safe virtual machine (~33KB) for safe execution of untrusted code in any JavaScript environment. It compiles logic chains and AI agents to JSON-serializable ASTs that run sandboxed with fuel (gas) limits.
7
+ **tjs-lang** (npm: `tjs-lang`) is a typed JavaScript platform a language, runtime, and toolchain that transpiles TypeScript and TJS to JavaScript with runtime type validation, inline WASM, monadic errors, and safe eval. It also includes AJS, a gas-metered VM for executing untrusted agent code in any JavaScript environment.
8
8
 
9
- Key concept: Code travels to data (rather than shipping data to code). Agents are defined as data, not deployed code.
9
+ **Three pillars:**
10
10
 
11
- **Two languages in one platform:**
12
-
13
- - **TJS** — TypeScript-like syntax with runtime type validation for writing your platform
14
- - **AJS** — Agent language that compiles to JSON AST for safe, sandboxed execution
11
+ - **TJS** TypeScript-like syntax where types are examples that survive to runtime as contracts, documentation, and tests. Transpiles TS → TJS → JS in a single fast pass.
12
+ - **AJS** — Agent language that compiles to JSON AST for safe, sandboxed execution with fuel limits and injected capabilities. Code travels to data.
13
+ - **Toolchain** — Compresses transpilation, linting, testing, and documentation generation into one pass. Includes inline WASM with SIMD, polymorphic dispatch, local class extensions, and a browser-based playground.
15
14
 
16
15
  ## Common Commands
17
16
 
@@ -70,7 +69,7 @@ npm run functions:serve # Local functions emulator
70
69
  ### Key Source Files
71
70
 
72
71
  - `src/index.ts` - Main entry, re-exports everything
73
- - `src/vm/runtime.ts` - All atom implementations, expression evaluation, fuel charging (~2900 lines, security-critical)
72
+ - `src/vm/runtime.ts` - All atom implementations, expression evaluation, fuel charging (~3000 lines, security-critical)
74
73
  - `src/vm/vm.ts` - AgentVM class (~226 lines)
75
74
  - `src/vm/atoms/batteries.ts` - Battery atoms (vector search, LLM, store operations)
76
75
  - `src/builder.ts` - TypedBuilder fluent API (~19KB)
@@ -153,8 +152,8 @@ function greet(name: '') -> '' {
153
152
  }
154
153
  `
155
154
 
156
- const jsCode = tjs(tjsSource)
157
- // Generates JavaScript with __tjs metadata for runtime validation
155
+ const jsResult = tjs(tjsSource)
156
+ // jsResult.code contains JavaScript with __tjs metadata for runtime validation
158
157
  ```
159
158
 
160
159
  **Full Chain Example:**
@@ -223,10 +222,12 @@ AJS expressions behave differently from JavaScript in several important ways:
223
222
  - Unit tests alongside source files (`*.test.ts`)
224
223
  - Integration tests in `src/use-cases/` (RAG, orchestration, malicious actors)
225
224
  - Security tests in `src/use-cases/malicious-actor.test.ts`
226
- - Language tests in `src/lang/lang.test.ts` (~46KB comprehensive)
225
+ - Language tests split across 14 files in `src/lang/` (lang.test.ts, features.test.ts, codegen.test.ts, parser.test.ts, from-ts.test.ts, wasm.test.ts, etc.)
227
226
 
228
227
  Coverage targets: 98% lines on `src/vm/runtime.ts` (security-critical), 80%+ overall.
229
228
 
229
+ **Bug fix rule:** Always create a reproduction test case before fixing a bug.
230
+
230
231
  ## Key Patterns
231
232
 
232
233
  ### Adding a New Atom
@@ -692,9 +693,28 @@ The `docs/` directory contains real documentation (markdown), not build artifact
692
693
  - `DOCS-TJS.md` — TJS language guide
693
694
  - `DOCS-AJS.md` — AJS runtime guide
694
695
  - `CONTEXT.md` — Architecture deep dive
695
- - `AGENTS.md` — Agent workflow instructions (issue tracking with `bd`, mandatory push-before-done)
696
+ - `AGENTS.md` — Agent workflow instructions (issue tracking with `bd`, mandatory push-before-done). **Critical**: work is NOT complete until `git push` succeeds; use `bd ready` to find work, `bd close <id>` to complete
696
697
  - `PLAN.md` — Roadmap
697
698
 
698
- ### Known Gotcha: Self-Contained Output
699
+ ### Known Gotcha: `tjs()` Returns an Object, Not a String
700
+
701
+ `tjs(source)` returns `{ code, types, metadata, testResults, ... }`. Use `.code` to get the transpiled JavaScript string. This is a common mistake.
702
+
703
+ ### Known Gotcha: Running Emitted TJS Code in Tests
704
+
705
+ Transpiled TJS code requires `globalThis.__tjs` to be set up with `createRuntime()` before execution:
706
+
707
+ ```typescript
708
+ import { createRuntime } from '../lang/runtime'
709
+
710
+ const saved = globalThis.__tjs
711
+ globalThis.__tjs = createRuntime()
712
+ try {
713
+ const fn = new Function(result.code + '\nreturn fnName')()
714
+ // ... test fn
715
+ } finally {
716
+ globalThis.__tjs = saved
717
+ }
718
+ ```
699
719
 
700
- Transpiled TJS code currently requires `globalThis.__tjs` to be set up with `createRuntime()` before execution. A `{ standalone: true }` option to inline the ~1KB runtime is planned but not yet implemented.
720
+ A `{ standalone: true }` option to inline the ~1KB runtime is planned but not yet implemented.
package/README.md CHANGED
@@ -272,10 +272,10 @@ The cost of "safe eval"—compare to a 200MB Docker image:
272
272
 
273
273
  | Bundle | Size | Gzipped |
274
274
  | ------------------------- | ------ | --------- |
275
- | VM only | 42 KB | **14 KB** |
276
- | + Batteries (LLM, vector) | 56 KB | 19 KB |
277
- | + Transpiler | 89 KB | 27 KB |
278
- | Full (with TS support) | 180 KB | 56 KB |
275
+ | VM only | 218 KB | **66 KB** |
276
+ | + Batteries (LLM, vector) | 14 KB | 5 KB |
277
+ | + Transpiler | 5 KB | 2 KB |
278
+ | Full (with TS support) | 230 KB | 71 KB |
279
279
 
280
280
  **Dependencies:** `acorn` (JS parser), `tosijs-schema` (validation). Both have zero transitive dependencies.
281
281
 
package/bin/dev.ts CHANGED
@@ -181,6 +181,7 @@ const server = Bun.serve({
181
181
  headers: {
182
182
  'Content-Type': contentTypes[ext || 'html'] || 'text/plain',
183
183
  'Access-Control-Allow-Origin': '*',
184
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
184
185
  },
185
186
  })
186
187
  }
@@ -189,7 +190,10 @@ const server = Bun.serve({
189
190
  const indexFile = Bun.file(join(DOCS_DIR, 'index.html'))
190
191
  if (await indexFile.exists()) {
191
192
  return new Response(indexFile, {
192
- headers: { 'Content-Type': 'text/html' },
193
+ headers: {
194
+ 'Content-Type': 'text/html',
195
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
196
+ },
193
197
  })
194
198
  }
195
199
 
package/demo/docs.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "section": "home",
7
7
  "order": 0,
8
8
  "navTitle": "Home",
9
- "text": "<!--{\"section\": \"home\", \"order\": 0, \"navTitle\": \"Home\"}-->\n\n# TJS Platform\n\n![tjs-lang logo](/tjs-lang.svg)\n\n[playground](https://tjs-platform.web.app) | [github](https://github.com/tonioloewald/tjs-lang#readme) | [npm](https://www.npmjs.com/package/tjs-lang) | [discord](https://discord.gg/ramJ9rgky5)\n\n## What is TJS?\n\n**TJS is a language.** It's what JavaScript always promised, but never quite delivered. Indeed it's what Apple's Dylan promised and never delivered. Instead of \"a lot of the power of Lisp\", _all_ the power of Lisp. Instead of C-like Syntax, actual JavaScript syntax. Instead of easy to learn but with weird corner cases, dangerous gotchas, and problems at scale, we fix the corner cases, remove the gotchas, and provide the tools that let you scale.\n\n**TJS is also a runtime.** A runtime that remembers your function declarations and can check whether parameter types are what they ought to be. It can guarantee safety by default, and speed when it's needed (including inline WASM).\n\n**AJS is another language.** It's a language for safe evaluation with injected capabilities and a gas limit. It's the language tjs allows you to Eval and use to create a SafeFunction. It also has its own VM and runtime to allow you to build **universal endpoints**. It's a language that's easy for agents to write and comprehend. It can be converted into an AST and run remotely.\n\n**TJS is also a toolchain.** It transpiles itself into JavaScript. It transpiles TypeScript into itself and then into JS. It turns function definitions into runtime contracts, documentation, and simple tests. It uses types both as contracts and examples. It allows inline tests of private module internals that disappear at runtime. It compresses transpilation, linting, testing, and documentation generation into a single fast pass. As for bundling? It allows it but it targets an unbundled web.\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│ TJS Platform │\n├─────────────────────┬─────────────────────┬─────────────────────────────┤\n│ Language │ Runtime │ Safe Execution │\n├─────────────────────┼─────────────────────┼─────────────────────────────┤\n│ TypeScript │ __tjs metadata │ AJS Agent │\n│ ↓ │ ↓ │ ↓ │\n│ TJS │ Runtime Validation │ JSON AST │\n│ ↓ │ ↓ │ ↓ │\n│ JavaScript │ Auto Documentation │ Gas-Limited VM │\n│ │ │ ↓ │\n│ │ │ Injected Capabilities │\n└─────────────────────┴─────────────────────┴─────────────────────────────┘\n```\n\n## The Problem\n\n**TypeScript is fragile.** It pretends to be a superset of JavaScript, but it isn't. It pretends to be typesafe, but it isn't. Its Turing-complete type system is harder to reason about than the code it supposedly documents—and then it all disappears at runtime.\n\nTypeScript is also _difficult to transpile_. Your browser can run entire [full virtual machines](https://infinitemac.org/) in JavaScript, but most TypeScript playgrounds either fake transpilation by stripping type declarations or use a server backend to do the real work.\n\n**JavaScript is dangerous.** `eval()` and `Function()` are so powerful they're forbidden almost everywhere—blocked by CSP in most production environments. The industry's answer? The **Container Fallacy**: shipping a 200MB Linux OS just to run a 1KB function safely. We ship buildings to deliver letters.\n\n**Security is a mess.** Every layer validates. Gateway validates. Auth validates. Business logic validates. Database validates. We spend 90% of our time building pipelines to move data to code, re-checking it at every hop.\n\n## What If?\n\nWhat if your language were:\n\n- **Honest** — types that actually exist at runtime, not fiction that evaporates\n- **Safe** — a gas-metered VM where infinite loops are impossible, no container required\n- **Mobile** — logic that travels to data, not oceans of data dragged to logic\n- **Unified** — one source of truth, not TypeScript interfaces _plus_ Zod schemas _plus_ JSDoc\n\nThat's what TJS Platform provides: **TJS** for writing your infrastructure, and **AJS** for shipping logic that runs anywhere.\n\n## TJS — Types That Survive\n\nWrite typed JavaScript where the type _is_ an example. No split-brain validation.\n\n```typescript\n// TJS: The type is an example AND a test\nfunction greet(name: 'World') -> 'Hello, World!' {\n return `Hello, ${name}!`\n}\n// At transpile time: greet('World') is called and checked against 'Hello, World!'\n\n// Runtime: The type becomes a contract\nconsole.log(greet.__tjs.params) // { name: { type: 'string', example: 'World', required: true } }\n\n// Safety: Errors are values, not crashes\nconst result = greet(123) // MonadicError: Expected string for 'greet.name', got number\n```\n\n**Why it matters:**\n\n- **One source of truth** — no more TS interfaces + Zod schemas + JSDoc comments\n- **Types as examples** — `name: 'Alice'` means \"required string, like 'Alice'\"\n- **Runtime metadata** — `__tjs` enables reflection, autocomplete, documentation from live objects\n- **Monadic errors** — type failures return values, never throw\n- **Zero build step** — transpiles in the browser, no webpack/Vite/Babel\n- **The compiler _is_ the client** — TJS transpiles itself _and_ TypeScript entirely client-side\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│ COMPILE TIME │\n│ │\n│ function greet(name: 'World') ──→ Parse ──→ Extract type │\n│ name = string │\n└─────────────────────────────────────────────────────────────────┘\n ↓\n┌─────────────────────────────────────────────────────────────────┐\n│ RUNTIME │\n│ │\n│ greet.__tjs = { params: { name: { type: 'string' } } } │\n│ │\n│ greet(123) ──→ Type Check ──┬──→ Pass ──→ Execute │\n│ └──→ Fail ──→ MonadicError │\n│ (no throw) │\n└─────────────────────────────────────────────────────────────────┘\n```\n\n## AJS — Code That Travels\n\nWrite logic that compiles to JSON and runs in a gas-limited sandbox. Send agents to data instead of shipping data to code.\n\n```typescript\nconst agent = ajs`\n function research(topic: 'AI') {\n let data = httpFetch({ url: '/search?q=' + topic })\n let summary = llmPredict({ prompt: 'Summarize: ' + data })\n return { topic, summary }\n }\n`\n\n// Run it safely—no Docker required\nconst result = await vm.run(\n agent,\n { topic: 'Agents' },\n {\n fuel: 500, // Strict CPU budget\n capabilities: { fetch: http }, // Allow ONLY http, block everything else\n }\n)\n```\n\n**Why it matters:**\n\n- **Safe eval** — run untrusted code without containers\n- **Code is JSON** — store in databases, diff, version, transmit\n- **Fuel metering** — every operation costs gas, infinite loops impossible\n- **Capability-based** — zero I/O by default, grant only what's needed\n- **LLM-native** — simple enough for small models to generate correctly\n\n## The Architecture Shift\n\n```\n❌ OLD WAY: Data-to-Code\n┌────────┐ request ┌────────┐ fetch 100 rows ┌──────────┐\n│ Client │ ────────────→ │ Server │ ───────────────→ │ Database │\n│ │ ←──────────── │ │ ←─────────────── │ │\n└────────┘ 5 rows └────────┘ 100 rows └──────────┘\n (after filtering 95 away)\n```\n\n_High latency. High bandwidth. Validate at every layer._\n\n```\n✅ TJS WAY: Code-to-Data\n┌────────┐ send agent ┌────────┐ run at data ┌──────────┐\n│ Client │ ────────────→ │ Edge │ ───────────────→ │ Database │\n│ │ ←──────────── │ │ ←─────────────── │ │\n└────────┘ 5 rows └────────┘ 5 rows └──────────┘\n (agent filtered at source)\n```\n\n_Low latency. Zero waste. Validate once._\n\nThe agent carries its own validation. The server grants capabilities. Caching happens automatically because the query _is_ the code.\n\n## Safe Eval\n\nThe holy grail: `eval()` that's actually safe.\n\n```typescript\nimport { Eval } from 'tjs-lang/eval'\n\n// Whitelist-wrapped fetch - untrusted code only reaches your domains\nconst safeFetch = (url: string) => {\n const allowed = ['api.example.com', 'cdn.example.com']\n const host = new URL(url).host\n if (!allowed.includes(host)) {\n return { error: 'Domain not allowed' }\n }\n return fetch(url)\n}\n\nconst { result, fuelUsed } = await Eval({\n code: `\n let data = fetch('https://api.example.com/products')\n return data.filter(x => x.price < budget)\n `,\n context: { budget: 100 },\n fuel: 1000,\n capabilities: { fetch: safeFetch }, // Only whitelisted domains\n})\n```\n\nThe untrusted code thinks it has `fetch`, but it only has _your_ `fetch`. No CSP violations. No infinite loops. No access to anything you didn't explicitly grant.\n\n```\n ┌─────────────────┐\n │ Untrusted Code │\n └────────┬────────┘\n │\n ┌───────────┬───────┴───────┬───────────┐\n ↓ ↓ ↓ ↓\n fetch() fs.read() loop console\n │ │ │ │\n ↓ ↓ ↓ ↓\n ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐\n │Granted? │ │Granted? │ │Fuel left│ │Granted? │\n └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘\n Y │ N N │ Y │ N N │\n ↓ ↓ ↓ ↓ ↓ ↓\n ┌────────┐ ┌───────┐ Continue Halt Block\n │ Your │ │ Block │ │\n │safeFetch│ └───────┘ ↓\n └────────┘\n```\n\n## Quick Start\n\n```bash\nnpm install tjs-lang\n```\n\n### Run an Agent\n\n```typescript\nimport { ajs, AgentVM } from 'tjs-lang'\n\nconst agent = ajs`\n function double(value: 21) {\n return { result: value * 2 }\n }\n`\n\nconst vm = new AgentVM()\nconst { result } = await vm.run(agent, { value: 21 })\nconsole.log(result) // { result: 42 }\n```\n\n### Write Typed Code\n\n```typescript\nimport { tjs } from 'tjs-lang'\n\nconst { code, metadata } = tjs`\n function add(a: 0, b: 0) -> 0 {\n return a + b\n }\n`\n// code: JavaScript with __tjs metadata attached\n// metadata: { add: { params: { a: { type: 'number', example: 0 }, b: { type: 'number', example: 0 } }, returns: { type: 'number' } } }\n```\n\n### Try the Playground\n\nSince TJS compiles itself, the playground is the full engine running entirely in your browser.\n\n**[tjs-platform.web.app](https://tjs-platform.web.app)**\n\n## At a Glance\n\n| | TypeScript | TJS | AJS |\n| --------------- | ------------------------------- | ------------------------------------------------ | ----------------- |\n| **Purpose** | Write your platform | Write your platform | Write your agents |\n| **Trust level** | Your code | Your code | Anyone's code |\n| **Compiles to** | JavaScript + `.d.ts` | JavaScript (with runtime checks + introspection) | JSON AST |\n| **Runs in** | Browser, Node, Bun | Browser, Node, Bun | Sandboxed VM |\n| **Types** | Static only (erased at runtime) | Examples → runtime validation | Schemas for I/O |\n| **Errors** | Exceptions | Monadic (values, not exceptions) | Monadic |\n| **Build step** | `tsc` → JS + `.d.ts` | Runs tests, builds docs, produces JS | None |\n\n> **Note:** TJS can transpile TypeScript into JS (via TJS) using `tjs convert`, giving your existing TS code\n> runtime type checks and introspection. You can even add inline tests using `/*test ...*/` comments\n> that run automatically during the build.\n\n## Bundle Size\n\nThe cost of \"safe eval\"—compare to a 200MB Docker image:\n\n| Bundle | Size | Gzipped |\n| ------------------------- | ------ | --------- |\n| VM only | 42 KB | **14 KB** |\n| + Batteries (LLM, vector) | 56 KB | 19 KB |\n| + Transpiler | 89 KB | 27 KB |\n| Full (with TS support) | 180 KB | 56 KB |\n\n**Dependencies:** `acorn` (JS parser), `tosijs-schema` (validation). Both have zero transitive dependencies.\n\n## Documentation\n\n- **[TJS Language Guide](DOCS-TJS.md)** — Types, syntax, runtime\n- **[AJS Runtime Guide](DOCS-AJS.md)** — VM, atoms, capabilities\n- **[Architecture Deep Dive](CONTEXT.md)** — How it all fits together\n- **[Playground](https://tjs-platform.web.app)** — Try it now\n\n## Installation\n\n```bash\n# npm\nnpm install tjs-lang\n\n# bun\nbun add tjs-lang\n\n# pnpm\npnpm add tjs-lang\n```\n\n## License\n\nApache 2.0\n"
9
+ "text": "<!--{\"section\": \"home\", \"order\": 0, \"navTitle\": \"Home\"}-->\n\n# TJS Platform\n\n![tjs-lang logo](/tjs-lang.svg)\n\n[playground](https://tjs-platform.web.app) | [github](https://github.com/tonioloewald/tjs-lang#readme) | [npm](https://www.npmjs.com/package/tjs-lang) | [discord](https://discord.gg/ramJ9rgky5)\n\n## What is TJS?\n\n**TJS is a language.** It's what JavaScript always promised, but never quite delivered. Indeed it's what Apple's Dylan promised and never delivered. Instead of \"a lot of the power of Lisp\", _all_ the power of Lisp. Instead of C-like Syntax, actual JavaScript syntax. Instead of easy to learn but with weird corner cases, dangerous gotchas, and problems at scale, we fix the corner cases, remove the gotchas, and provide the tools that let you scale.\n\n**TJS is also a runtime.** A runtime that remembers your function declarations and can check whether parameter types are what they ought to be. It can guarantee safety by default, and speed when it's needed (including inline WASM).\n\n**AJS is another language.** It's a language for safe evaluation with injected capabilities and a gas limit. It's the language tjs allows you to Eval and use to create a SafeFunction. It also has its own VM and runtime to allow you to build **universal endpoints**. It's a language that's easy for agents to write and comprehend. It can be converted into an AST and run remotely.\n\n**TJS is also a toolchain.** It transpiles itself into JavaScript. It transpiles TypeScript into itself and then into JS. It turns function definitions into runtime contracts, documentation, and simple tests. It uses types both as contracts and examples. It allows inline tests of private module internals that disappear at runtime. It compresses transpilation, linting, testing, and documentation generation into a single fast pass. As for bundling? It allows it but it targets an unbundled web.\n\n```\n┌─────────────────────────────────────────────────────────────────────────┐\n│ TJS Platform │\n├─────────────────────┬─────────────────────┬─────────────────────────────┤\n│ Language │ Runtime │ Safe Execution │\n├─────────────────────┼─────────────────────┼─────────────────────────────┤\n│ TypeScript │ __tjs metadata │ AJS Agent │\n│ ↓ │ ↓ │ ↓ │\n│ TJS │ Runtime Validation │ JSON AST │\n│ ↓ │ ↓ │ ↓ │\n│ JavaScript │ Auto Documentation │ Gas-Limited VM │\n│ │ │ ↓ │\n│ │ │ Injected Capabilities │\n└─────────────────────┴─────────────────────┴─────────────────────────────┘\n```\n\n## The Problem\n\n**TypeScript is fragile.** It pretends to be a superset of JavaScript, but it isn't. It pretends to be typesafe, but it isn't. Its Turing-complete type system is harder to reason about than the code it supposedly documents—and then it all disappears at runtime.\n\nTypeScript is also _difficult to transpile_. Your browser can run entire [full virtual machines](https://infinitemac.org/) in JavaScript, but most TypeScript playgrounds either fake transpilation by stripping type declarations or use a server backend to do the real work.\n\n**JavaScript is dangerous.** `eval()` and `Function()` are so powerful they're forbidden almost everywhere—blocked by CSP in most production environments. The industry's answer? The **Container Fallacy**: shipping a 200MB Linux OS just to run a 1KB function safely. We ship buildings to deliver letters.\n\n**Security is a mess.** Every layer validates. Gateway validates. Auth validates. Business logic validates. Database validates. We spend 90% of our time building pipelines to move data to code, re-checking it at every hop.\n\n## What If?\n\nWhat if your language were:\n\n- **Honest** — types that actually exist at runtime, not fiction that evaporates\n- **Safe** — a gas-metered VM where infinite loops are impossible, no container required\n- **Mobile** — logic that travels to data, not oceans of data dragged to logic\n- **Unified** — one source of truth, not TypeScript interfaces _plus_ Zod schemas _plus_ JSDoc\n\nThat's what TJS Platform provides: **TJS** for writing your infrastructure, and **AJS** for shipping logic that runs anywhere.\n\n## TJS — Types That Survive\n\nWrite typed JavaScript where the type _is_ an example. No split-brain validation.\n\n```typescript\n// TJS: The type is an example AND a test\nfunction greet(name: 'World') -> 'Hello, World!' {\n return `Hello, ${name}!`\n}\n// At transpile time: greet('World') is called and checked against 'Hello, World!'\n\n// Runtime: The type becomes a contract\nconsole.log(greet.__tjs.params) // { name: { type: 'string', example: 'World', required: true } }\n\n// Safety: Errors are values, not crashes\nconst result = greet(123) // MonadicError: Expected string for 'greet.name', got number\n```\n\n**Why it matters:**\n\n- **One source of truth** — no more TS interfaces + Zod schemas + JSDoc comments\n- **Types as examples** — `name: 'Alice'` means \"required string, like 'Alice'\"\n- **Runtime metadata** — `__tjs` enables reflection, autocomplete, documentation from live objects\n- **Monadic errors** — type failures return values, never throw\n- **Zero build step** — transpiles in the browser, no webpack/Vite/Babel\n- **The compiler _is_ the client** — TJS transpiles itself _and_ TypeScript entirely client-side\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│ COMPILE TIME │\n│ │\n│ function greet(name: 'World') ──→ Parse ──→ Extract type │\n│ name = string │\n└─────────────────────────────────────────────────────────────────┘\n ↓\n┌─────────────────────────────────────────────────────────────────┐\n│ RUNTIME │\n│ │\n│ greet.__tjs = { params: { name: { type: 'string' } } } │\n│ │\n│ greet(123) ──→ Type Check ──┬──→ Pass ──→ Execute │\n│ └──→ Fail ──→ MonadicError │\n│ (no throw) │\n└─────────────────────────────────────────────────────────────────┘\n```\n\n## AJS — Code That Travels\n\nWrite logic that compiles to JSON and runs in a gas-limited sandbox. Send agents to data instead of shipping data to code.\n\n```typescript\nconst agent = ajs`\n function research(topic: 'AI') {\n let data = httpFetch({ url: '/search?q=' + topic })\n let summary = llmPredict({ prompt: 'Summarize: ' + data })\n return { topic, summary }\n }\n`\n\n// Run it safely—no Docker required\nconst result = await vm.run(\n agent,\n { topic: 'Agents' },\n {\n fuel: 500, // Strict CPU budget\n capabilities: { fetch: http }, // Allow ONLY http, block everything else\n }\n)\n```\n\n**Why it matters:**\n\n- **Safe eval** — run untrusted code without containers\n- **Code is JSON** — store in databases, diff, version, transmit\n- **Fuel metering** — every operation costs gas, infinite loops impossible\n- **Capability-based** — zero I/O by default, grant only what's needed\n- **LLM-native** — simple enough for small models to generate correctly\n\n## The Architecture Shift\n\n```\n❌ OLD WAY: Data-to-Code\n┌────────┐ request ┌────────┐ fetch 100 rows ┌──────────┐\n│ Client │ ────────────→ │ Server │ ───────────────→ │ Database │\n│ │ ←──────────── │ │ ←─────────────── │ │\n└────────┘ 5 rows └────────┘ 100 rows └──────────┘\n (after filtering 95 away)\n```\n\n_High latency. High bandwidth. Validate at every layer._\n\n```\n✅ TJS WAY: Code-to-Data\n┌────────┐ send agent ┌────────┐ run at data ┌──────────┐\n│ Client │ ────────────→ │ Edge │ ───────────────→ │ Database │\n│ │ ←──────────── │ │ ←─────────────── │ │\n└────────┘ 5 rows └────────┘ 5 rows └──────────┘\n (agent filtered at source)\n```\n\n_Low latency. Zero waste. Validate once._\n\nThe agent carries its own validation. The server grants capabilities. Caching happens automatically because the query _is_ the code.\n\n## Safe Eval\n\nThe holy grail: `eval()` that's actually safe.\n\n```typescript\nimport { Eval } from 'tjs-lang/eval'\n\n// Whitelist-wrapped fetch - untrusted code only reaches your domains\nconst safeFetch = (url: string) => {\n const allowed = ['api.example.com', 'cdn.example.com']\n const host = new URL(url).host\n if (!allowed.includes(host)) {\n return { error: 'Domain not allowed' }\n }\n return fetch(url)\n}\n\nconst { result, fuelUsed } = await Eval({\n code: `\n let data = fetch('https://api.example.com/products')\n return data.filter(x => x.price < budget)\n `,\n context: { budget: 100 },\n fuel: 1000,\n capabilities: { fetch: safeFetch }, // Only whitelisted domains\n})\n```\n\nThe untrusted code thinks it has `fetch`, but it only has _your_ `fetch`. No CSP violations. No infinite loops. No access to anything you didn't explicitly grant.\n\n```\n ┌─────────────────┐\n │ Untrusted Code │\n └────────┬────────┘\n │\n ┌───────────┬───────┴───────┬───────────┐\n ↓ ↓ ↓ ↓\n fetch() fs.read() loop console\n │ │ │ │\n ↓ ↓ ↓ ↓\n ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐\n │Granted? │ │Granted? │ │Fuel left│ │Granted? │\n └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘\n Y │ N N │ Y │ N N │\n ↓ ↓ ↓ ↓ ↓ ↓\n ┌────────┐ ┌───────┐ Continue Halt Block\n │ Your │ │ Block │ │\n │safeFetch│ └───────┘ ↓\n └────────┘\n```\n\n## Quick Start\n\n```bash\nnpm install tjs-lang\n```\n\n### Run an Agent\n\n```typescript\nimport { ajs, AgentVM } from 'tjs-lang'\n\nconst agent = ajs`\n function double(value: 21) {\n return { result: value * 2 }\n }\n`\n\nconst vm = new AgentVM()\nconst { result } = await vm.run(agent, { value: 21 })\nconsole.log(result) // { result: 42 }\n```\n\n### Write Typed Code\n\n```typescript\nimport { tjs } from 'tjs-lang'\n\nconst { code, metadata } = tjs`\n function add(a: 0, b: 0) -> 0 {\n return a + b\n }\n`\n// code: JavaScript with __tjs metadata attached\n// metadata: { add: { params: { a: { type: 'number', example: 0 }, b: { type: 'number', example: 0 } }, returns: { type: 'number' } } }\n```\n\n### Try the Playground\n\nSince TJS compiles itself, the playground is the full engine running entirely in your browser.\n\n**[tjs-platform.web.app](https://tjs-platform.web.app)**\n\n## At a Glance\n\n| | TypeScript | TJS | AJS |\n| --------------- | ------------------------------- | ------------------------------------------------ | ----------------- |\n| **Purpose** | Write your platform | Write your platform | Write your agents |\n| **Trust level** | Your code | Your code | Anyone's code |\n| **Compiles to** | JavaScript + `.d.ts` | JavaScript (with runtime checks + introspection) | JSON AST |\n| **Runs in** | Browser, Node, Bun | Browser, Node, Bun | Sandboxed VM |\n| **Types** | Static only (erased at runtime) | Examples → runtime validation | Schemas for I/O |\n| **Errors** | Exceptions | Monadic (values, not exceptions) | Monadic |\n| **Build step** | `tsc` → JS + `.d.ts` | Runs tests, builds docs, produces JS | None |\n\n> **Note:** TJS can transpile TypeScript into JS (via TJS) using `tjs convert`, giving your existing TS code\n> runtime type checks and introspection. You can even add inline tests using `/*test ...*/` comments\n> that run automatically during the build.\n\n## Bundle Size\n\nThe cost of \"safe eval\"—compare to a 200MB Docker image:\n\n| Bundle | Size | Gzipped |\n| ------------------------- | ------ | --------- |\n| VM only | 218 KB | **66 KB** |\n| + Batteries (LLM, vector) | 14 KB | 5 KB |\n| + Transpiler | 5 KB | 2 KB |\n| Full (with TS support) | 230 KB | 71 KB |\n\n**Dependencies:** `acorn` (JS parser), `tosijs-schema` (validation). Both have zero transitive dependencies.\n\n## Documentation\n\n- **[TJS Language Guide](DOCS-TJS.md)** — Types, syntax, runtime\n- **[AJS Runtime Guide](DOCS-AJS.md)** — VM, atoms, capabilities\n- **[Architecture Deep Dive](CONTEXT.md)** — How it all fits together\n- **[Playground](https://tjs-platform.web.app)** — Try it now\n\n## Installation\n\n```bash\n# npm\nnpm install tjs-lang\n\n# bun\nbun add tjs-lang\n\n# pnpm\npnpm add tjs-lang\n```\n\n## License\n\nApache 2.0\n"
10
10
  },
11
11
  {
12
12
  "title": "Applied Laziness: The \"Zero-Infrastructure\" Stack",
@@ -655,7 +655,7 @@
655
655
  "title": "CLAUDE.md",
656
656
  "filename": "CLAUDE.md",
657
657
  "path": "CLAUDE.md",
658
- "text": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Project Overview\n\n**tjs-lang** (npm: `tjs-lang`) is a type-safe virtual machine (~33KB) for safe execution of untrusted code in any JavaScript environment. It compiles logic chains and AI agents to JSON-serializable ASTs that run sandboxed with fuel (gas) limits.\n\nKey concept: Code travels to data (rather than shipping data to code). Agents are defined as data, not deployed code.\n\n**Two languages in one platform:**\n\n- **TJS** — TypeScript-like syntax with runtime type validation for writing your platform\n- **AJS** — Agent language that compiles to JSON AST for safe, sandboxed execution\n\n## Common Commands\n\n```bash\n# Development\nnpm run format # ESLint fix + Prettier\nnpm run test:fast # Core tests (skips LLM & benchmarks)\nnpm run make # Full build (clean, format, grammars, tsc, esbuild)\nnpm run dev # Development server with file watcher\nnpm run start # Build demo + start dev server\nnpm run latest # Clean reinstall (rm node_modules + bun install)\n\n# Testing (framework: bun:test — describe/it/expect)\nbun test # Full test suite\nbun test src/path/to/file.test.ts # Single test file\nbun test --test-name-pattern \"pattern\" # Run tests matching pattern\nSKIP_LLM_TESTS=1 bun test # Skip LLM integration tests\nbun test --coverage # With coverage report\n\n# Efficient test debugging - capture once, query multiple times\nbun test 2>&1 | tee /tmp/test-results.txt | tail -20 # Run and save\ngrep -E \"^\\(fail\\)\" /tmp/test-results.txt # List failures\ngrep -A10 \"test name\" /tmp/test-results.txt # See specific error\n\n# CLI tools\nbun src/cli/tjs.ts check <file> # Parse and type check TJS file\nbun src/cli/tjs.ts run <file> # Transpile and execute\nbun src/cli/tjs.ts types <file> # Output type metadata as JSON\nbun src/cli/tjs.ts emit <file> # Output transpiled JavaScript\nbun src/cli/tjs.ts convert <file> # Convert TypeScript to TJS (--emit-tjs) or JS\nbun src/cli/tjs.ts test <file> # Run inline tests in a TJS file\n\n# Type checking & other\nnpm run typecheck # tsc --noEmit (type check without emitting)\nnpm run test:llm # LM Studio integration tests\nnpm run bench # Vector search benchmarks\nnpm run docs # Generate documentation\n\n# Build standalone CLI binaries\nnpm run build:cli # Compiles tjs + tjsx to dist/\n\n# Deployment (Firebase)\nnpm run deploy # Build demo + deploy functions + hosting\nnpm run deploy:hosting # Hosting only (serves from .demo/)\nnpm run functions:deploy # Cloud functions only\nnpm run functions:serve # Local functions emulator\n```\n\n## Architecture\n\n### Two-Layer Design\n\n1. **Builder Layer** (`src/builder.ts`): Fluent API that constructs AST nodes. Contains no execution logic.\n2. **Runtime Layer** (`src/vm/runtime.ts`): Executes AST nodes. Contains all atom implementations (~2900 lines, security-critical).\n\n### Key Source Files\n\n- `src/index.ts` - Main entry, re-exports everything\n- `src/vm/runtime.ts` - All atom implementations, expression evaluation, fuel charging (~2900 lines, security-critical)\n- `src/vm/vm.ts` - AgentVM class (~226 lines)\n- `src/vm/atoms/batteries.ts` - Battery atoms (vector search, LLM, store operations)\n- `src/builder.ts` - TypedBuilder fluent API (~19KB)\n- `src/lang/parser.ts` - TJS parser with colon shorthand, unsafe markers, return type extraction\n- `src/lang/emitters/ast.ts` - Emits Agent99 AST from parsed source\n- `src/lang/emitters/js.ts` - Emits JavaScript with `__tjs` metadata\n- `src/lang/emitters/from-ts.ts` - TypeScript to TJS/JS transpiler with class metadata extraction\n- `src/lang/inference.ts` - Type inference from example values\n- `src/lang/linter.ts` - Static analysis (unused vars, unreachable code, no-explicit-new)\n- `src/lang/runtime.ts` - TJS runtime (monadic errors, type checking, wrapClass)\n- `src/lang/wasm.ts` - WASM compiler (opcodes, disassembler, bytecode generation)\n- `src/types/` - Type system definitions (Type.ts, Generic.ts)\n- `src/transpiler/` - AJS transpiler (source → AST)\n- `src/batteries/` - LM Studio integration (lazy init, model audit, vector search)\n- `src/store/` - Store implementations for persistence\n- `src/rbac/` - Role-based access control\n- `src/use-cases/` - Integration tests and real-world examples (28 test files)\n- `src/cli/tjs.ts` - CLI tool for check/run/types/emit/convert/test commands\n- `src/cli/tjsx.ts` - JSX/component runner\n- `src/cli/playground.ts` - Local playground server\n- `src/cli/create-app.ts` - Project scaffolding tool\n\n### Core APIs\n\n```typescript\n// Language\najs`...` // Parse AJS to AST\ntjs`...` // Parse TypeScript variant with type metadata\ntranspile(source, options) // Full transpilation with signature extraction\ncreateAgent(source, vm) // Creates callable agent\n\n// VM\nconst vm = new AgentVM(customAtoms)\nawait vm.run(ast, args, { fuel, capabilities, timeoutMs, trace })\n\n// Builder\nAgent.take(schema).varSet(...).httpFetch(...).return(schema)\nvm.Agent // Builder with custom atoms included\n```\n\n### Package Entry Points\n\n```typescript\nimport { Agent, AgentVM, ajs, tjs } from 'tjs-lang' // Main entry\nimport { Eval, SafeFunction } from 'tjs-lang/eval' // Safe eval utilities\nimport { tjs, transpile } from 'tjs-lang/lang' // Language tools only\nimport { fromTS } from 'tjs-lang/lang/from-ts' // TypeScript transpilation\n```\n\n### Transpiler Chain (TS → TJS → JS)\n\nTJS supports transpiling TypeScript to JavaScript with runtime type validation. The pipeline has two distinct, independently testable steps:\n\n**Step 1: TypeScript → TJS** (`fromTS`)\n\n```typescript\nimport { fromTS } from 'tosijs/lang/from-ts'\n\nconst tsSource = `\nfunction greet(name: string): string {\n return \\`Hello, \\${name}!\\`\n}\n`\n\nconst result = fromTS(tsSource, { emitTJS: true })\n// result.code contains TJS:\n// function greet(name: '') -> '' {\n// return \\`Hello, \\${name}!\\`\n// }\n```\n\n**Step 2: TJS → JavaScript** (`tjs`)\n\n```typescript\nimport { tjs } from 'tosijs/lang'\n\nconst tjsSource = `\nfunction greet(name: '') -> '' {\n return \\`Hello, \\${name}!\\`\n}\n`\n\nconst jsCode = tjs(tjsSource)\n// Generates JavaScript with __tjs metadata for runtime validation\n```\n\n**Full Chain Example:**\n\n```typescript\nimport { fromTS } from 'tosijs/lang/from-ts'\nimport { tjs } from 'tosijs/lang'\n\n// TypeScript source with type annotations\nconst tsSource = `\nfunction add(a: number, b: number): number {\n return a + b\n}\n`\n\n// Step 1: TS → TJS\nconst tjsResult = fromTS(tsSource, { emitTJS: true })\n\n// Step 2: TJS → JS (with runtime validation)\nconst jsCode = tjs(tjsResult.code)\n\n// Execute the result\nconst fn = new Function('__tjs', jsCode + '; return add')(__tjs_runtime)\nfn(1, 2) // Returns 3\nfn('a', 'b') // Returns { error: 'type mismatch', ... }\n```\n\n**Design Notes:**\n\n- The two steps are intentionally separate for tree-shaking (TS support is optional)\n- `fromTS` lives in a separate entry point (`tosijs/lang/from-ts`)\n- Import only what you need to keep bundle size minimal\n- Each step is independently testable (see `src/lang/codegen.test.ts`)\n\n### Security Model\n\n- **Capability-based**: VM has zero IO by default; inject `fetch`, `store`, `llm` via capabilities\n- **Fuel metering**: Every atom has a cost; execution stops when fuel exhausted\n- **Timeout enforcement**: Default `fuel × 10ms`; explicit `timeoutMs` overrides\n- **Monadic errors**: Errors wrapped in `AgentError` (VM) / `MonadicError` (TJS), not thrown (prevents exception exploits). Use `isMonadicError()` to check — `isError()` is deprecated\n- **Expression sandboxing**: ExprNode AST evaluation, blocked prototype access\n\n### Expression Evaluation\n\nExpressions use AST nodes (`$expr`), not strings:\n\n```typescript\n{ $expr: 'binary', op: '+', left: {...}, right: {...} }\n{ $expr: 'ident', name: 'varName' }\n{ $expr: 'member', object: {...}, property: 'foo' }\n```\n\nEach node costs 0.01 fuel. Forbidden: function calls, `new`, `this`, `__proto__`, `constructor`.\n\n## AJS Expression Gotchas\n\nAJS expressions behave differently from JavaScript in several important ways:\n\n- **Null member access is safe by default**: `null.foo.bar` returns `undefined` silently (uses `?.` semantics internally). This differs from JavaScript which would throw `TypeError`.\n- **No computed member access with variables**: `items[i]` fails at transpile time with \"Computed member access with variables not yet supported\". Literal indices work (`items[0]`, `obj[\"key\"]`). Workaround: use `.map`/`.reduce` atoms instead.\n- **Unknown atom errors**: When an atom doesn't exist, the error is `\"Unknown Atom: <name>\"` with no listing of available atoms.\n- **TJS parameter syntax is NOT TypeScript**: `function foo(x: 'default')` means \"required param, example value 'default'\" — not a TypeScript string literal type. LLMs consistently generate `function foo(x: string)` which is wrong. The colon value is an _example_, not a _type annotation_.\n\n## Testing Strategy\n\n- Unit tests alongside source files (`*.test.ts`)\n- Integration tests in `src/use-cases/` (RAG, orchestration, malicious actors)\n- Security tests in `src/use-cases/malicious-actor.test.ts`\n- Language tests in `src/lang/lang.test.ts` (~46KB comprehensive)\n\nCoverage targets: 98% lines on `src/vm/runtime.ts` (security-critical), 80%+ overall.\n\n## Key Patterns\n\n### Adding a New Atom\n\n1. Define with `defineAtom(opCode, inputSchema, outputSchema, implementation, { cost, timeoutMs, docs })`\n2. Add to `src/vm/atoms/` and export from `src/vm/atoms/index.ts`\n3. Add tests\n4. Run `npm run test:fast`\n\n**Atom implementation notes:**\n\n- `cost` can be static number or dynamic: `(input, ctx) => number`\n- `timeoutMs` defaults to 1000ms; use `0` for no timeout (e.g., `seq`)\n- Atoms are always async; fuel deduction is automatic in the `exec` wrapper\n\n### Debugging Agents\n\nEnable tracing: `vm.run(ast, args, { trace: true })` returns `TraceEvent[]` with execution path, fuel consumption, and state changes.\n\n### Custom Atoms Must\n\n- Be non-blocking (no synchronous CPU-heavy work)\n- Respect `ctx.signal` for cancellation\n- Access IO only via `ctx.capabilities`\n\n### Value Resolution\n\nThe `resolveValue()` function handles multiple input patterns:\n\n- `{ $kind: 'arg', path: 'varName' }` → lookup in `ctx.args`\n- `{ $expr: ... }` → evaluate ExprNode via `evaluateExpr()`\n- String with dots `'obj.foo.bar'` → traverse state with forbidden property checks\n- Bare strings → lookup in state, else return literal\n\n### Monadic Error Flow\n\nWhen `ctx.error` is set, subsequent atoms in a `seq` skip execution. Errors are wrapped in `AgentError`, not thrown. This prevents exception-based exploits.\n\n### TJS Parser Syntax Extensions\n\nTJS extends JavaScript with type annotations that survive to runtime.\n\n#### Classes (Callable Without `new`)\n\nTJS classes are wrapped to be callable without the `new` keyword:\n\n```typescript\nclass Point {\n constructor(public x: number, public y: number) {}\n}\n\n// Both work identically:\nconst p1 = Point(10, 20) // TJS way - clean\nconst p2 = new Point(10, 20) // Still works, but linter warns\n\n// The linter flags explicit `new` usage:\n// Warning: Unnecessary 'new' keyword. In TJS, classes are callable without 'new'\n```\n\nThe `wrapClass()` function in the runtime uses a Proxy to intercept calls and auto-construct.\n\n#### Function Parameters\n\n```typescript\n// Required param with example value (colon shorthand)\nfunction greet(name: 'Alice') { } // name is required, type inferred as string\n\n// Numeric type narrowing (all valid JS syntax)\nfunction calc(rate: 3.14) { } // number (float) -- has decimal point\nfunction calc(count: 42) { } // integer -- whole number\nfunction calc(index: +0) { } // non-negative integer -- + prefix\n\n// Optional param with default\nfunction greet(name = 'Alice') { } // name is optional, defaults to 'Alice'\n\n// Object parameter with shape\nfunction createUser(user: { name: '', age: 0 }) { }\n\n// Nullable type\nfunction find(id: 0 | null) { } // integer or null\n\n// Optional TS-style\nfunction greet(name?: '') { } // same as name = ''\n```\n\n#### Return Types\n\n```typescript\n// Return type annotation (arrow syntax)\nfunction add(a: 0, b: 0) -> 0 { return a + b }\n\n// Object return type\nfunction getUser(id: 0) -> { name: '', age: 0 } { ... }\n```\n\n#### Safety Markers\n\n```typescript\n// Unsafe function (skips runtime validation)\nfunction fastAdd(! a: 0, b: 0) { return a + b }\n\n// Safe function (explicit validation)\nfunction safeAdd(? a: 0, b: 0) { return a + b }\n\n// Unsafe block\nunsafe {\n // All calls in here skip validation\n fastPath(data)\n}\n```\n\n#### Type Declarations\n\n```typescript\n// Simple type from example\nType Name 'Alice'\n\n// Type with description and example\nType User {\n description: 'a user object'\n example: { name: '', age: 0 }\n}\n\n// Type with predicate (auto-generates type guard from example)\nType EvenNumber {\n description: 'an even number'\n example: 2\n predicate(x) { return x % 2 === 0 }\n}\n```\n\n#### Generic Declarations\n\n```typescript\n// Simple generic\nGeneric Box<T> {\n description: 'a boxed value'\n predicate(x, T) {\n return typeof x === 'object' && x !== null && 'value' in x && T(x.value)\n }\n}\n\n// Generic with default type parameter\nGeneric Container<T, U = ''> {\n description: 'container with label'\n predicate(obj, T, U) {\n return T(obj.item) && U(obj.label)\n }\n}\n```\n\n#### Bare Assignments\n\n```typescript\n// Uppercase identifiers auto-get const\nFoo = Type('test', 'example') // becomes: const Foo = Type(...)\nMyConfig = { debug: true } // becomes: const MyConfig = { ... }\n```\n\n#### Module Safety Directive\n\n```typescript\n// At top of file - sets default validation level\nsafety none // No validation (metadata only)\nsafety inputs // Validate function inputs (default)\nsafety all // Validate everything (debug mode)\n```\n\n#### TJS Mode Directives\n\nJavaScript semantics are the default. TJS improvements are opt-in via file-level directives:\n\n```typescript\nTjsStrict // Enables ALL modes below at once\n\nTjsEquals // == and != use structural equality (Is/IsNot)\nTjsClass // Classes callable without new, explicit new is banned\nTjsDate // Date is banned, use Timestamp/LegalDate instead\nTjsNoeval // eval() and new Function() are banned\nTjsStandard // Newlines as statement terminators (prevents ASI footguns)\nTjsSafeEval // Include Eval/SafeFunction in runtime for dynamic code\n```\n\nMultiple directives can be combined. Place them at the top of the file before any code.\n\n#### Equality Operators\n\nWith `TjsEquals` (or `TjsStrict`), TJS redefines equality to be structural, fixing JavaScript's confusing `==` vs `===` semantics.\n\n| Operator | Meaning | Example |\n| ----------- | -------------------------------- | --------------------------- |\n| `==` | Structural equality | `{a:1} == {a:1}` is `true` |\n| `!=` | Structural inequality | `{a:1} != {a:2}` is `true` |\n| `===` | Identity (same reference) | `obj === obj` is `true` |\n| `!==` | Not same reference | `{a:1} !== {a:1}` is `true` |\n| `a Is b` | Structural equality (explicit) | Same as `==` |\n| `a IsNot b` | Structural inequality (explicit) | Same as `!=` |\n\n```typescript\n// Structural equality - compares values deeply\nconst a = { x: 1, y: [2, 3] }\nconst b = { x: 1, y: [2, 3] }\na == b // true (same structure)\na === b // false (different objects)\n\n// Works with arrays too\n[1, 2, 3] == [1, 2, 3] // true\n\n// Infix operators for readability\nuser Is expectedUser\nresult IsNot errorValue\n```\n\n**Implementation Notes:**\n\n- **AJS (VM)**: The VM's expression evaluator (`src/vm/runtime.ts`) uses `isStructurallyEqual()` for `==`/`!=`\n- **TJS (browser/Node)**: Source transformation converts `==` to `Is()` and `!=` to `IsNot()` calls\n- **`===` and `!==`**: Always preserved as identity checks, never transformed\n- The `Is()` and `IsNot()` functions are available in `src/lang/runtime.ts` and exposed globally\n\n**Custom Equality Protocol:**\n\n- `[tjsEquals]` symbol (`Symbol.for('tjs.equals')`) — highest priority, ideal for Proxies\n- `.Equals` method — backward-compatible, works on any object/class\n- Priority: symbol → `.Equals` → structural comparison\n- `tjsEquals` is exported from `src/lang/runtime.ts` and available as `__tjs.tjsEquals`\n\n#### Polymorphic Functions\n\nMultiple function declarations with the same name are merged into a dispatcher:\n\n```typescript\nfunction area(radius: 3.14) {\n return Math.PI * radius * radius\n}\nfunction area(w: 0.0, h: 0.0) {\n return w * h\n}\n\narea(5) // dispatches to variant 1 (one number)\narea(3, 4) // dispatches to variant 2 (two numbers)\n```\n\nDispatch order: arity first, then type specificity, then declaration order. Ambiguous signatures (same types at same arity) are caught at transpile time.\n\n#### Polymorphic Constructors\n\nClasses can have multiple constructor signatures (requires `TjsClass` directive):\n\n```typescript\nTjsClass\n\nclass Point {\n constructor(x: 0.0, y: 0.0) {\n this.x = x\n this.y = y\n }\n constructor(coords: { x: 0.0; y: 0.0 }) {\n this.x = coords.x\n this.y = coords.y\n }\n}\n\nPoint(3, 4) // variant 1\nPoint({ x: 10, y: 20 }) // variant 2 (both produce correct instanceof)\n```\n\nThe first constructor becomes the real JS constructor; additional variants become factory functions using `Object.create`.\n\n#### Local Class Extensions\n\nAdd methods to built-in types without prototype pollution:\n\n```typescript\nextend String {\n capitalize() { return this[0].toUpperCase() + this.slice(1) }\n}\n\nextend Array {\n last() { return this[this.length - 1] }\n}\n\n'hello'.capitalize() // 'Hello' — rewritten to __ext_String.capitalize.call('hello')\n[1, 2, 3].last() // 3\n```\n\n- Methods are rewritten to `.call()` at transpile time for known-type receivers (zero overhead)\n- Runtime fallback via `registerExtension()`/`resolveExtension()` for unknown types\n- Arrow functions rejected (need `this` binding)\n- Multiple `extend` blocks for same type merge left-to-right\n- File-local only — no cross-module leaking\n\n## WASM Blocks\n\nTJS supports inline WebAssembly for performance-critical code. WASM blocks are compiled at transpile time and embedded as base64 in the output.\n\n### Syntax\n\n```typescript\nconst add = wasm (a: i32, b: i32) -> i32 {\n local.get $a\n local.get $b\n i32.add\n}\n```\n\n### Features\n\n- **Transpile-time compilation**: WASM bytecode is generated during transpilation, not at runtime\n- **WAT comments**: Human-readable WebAssembly Text format is included as comments above the base64\n- **Type-safe**: Parameters and return types are validated\n- **Self-contained**: Compiled WASM is embedded in output JS, no separate .wasm files needed\n\n### Output Example\n\nThe transpiler generates code like:\n\n```javascript\n/*\n * WASM Block: add\n * WAT (WebAssembly Text):\n * (func $add (param $a i32) (param $b i32) (result i32)\n * local.get 0\n * local.get 1\n * i32.add\n * )\n */\nconst add = await (async () => {\n const bytes = Uint8Array.from(atob('AGFzbQEAAAA...'), (c) => c.charCodeAt(0))\n const { instance } = await WebAssembly.instantiate(bytes)\n return instance.exports.fn\n})()\n```\n\n### SIMD Intrinsics (f32x4)\n\nWASM blocks support explicit SIMD via `f32x4_*` intrinsics:\n\n```typescript\nconst scale = wasm (arr: Float32Array, len: 0, factor: 0.0) -> 0 {\n let s = f32x4_splat(factor)\n for (let i = 0; i < len; i += 4) {\n let off = i * 4\n let v = f32x4_load(arr, off)\n f32x4_store(arr, off, f32x4_mul(v, s))\n }\n} fallback {\n for (let i = 0; i < len; i++) arr[i] *= factor\n}\n```\n\nAvailable: `f32x4_load`, `f32x4_store`, `f32x4_splat`, `f32x4_extract_lane`, `f32x4_replace_lane`, `f32x4_add`, `f32x4_sub`, `f32x4_mul`, `f32x4_div`, `f32x4_neg`, `f32x4_sqrt`.\n\n### Zero-Copy Arrays: `wasmBuffer()`\n\n`wasmBuffer(Constructor, length)` allocates typed arrays directly in WASM linear memory. When passed to a `wasm {}` block, these arrays are zero-copy — no marshalling overhead.\n\n```typescript\n// Allocate in WASM memory (zero-copy when passed to wasm blocks)\nconst xs = wasmBuffer(Float32Array, 50000)\n\n// Works like a normal Float32Array from JS\nxs[0] = 3.14\nfor (let i = 0; i < xs.length; i++) xs[i] = Math.random()\n\n// Zero-copy in WASM blocks — data is already in WASM memory\nfunction process(! xs: Float32Array, len: 0, delta: 0.0) {\n wasm {\n let vd = f32x4_splat(delta)\n for (let i = 0; i < len; i += 4) {\n let off = i * 4\n f32x4_store(xs, off, f32x4_add(f32x4_load(xs, off), vd))\n }\n } fallback {\n for (let i = 0; i < len; i++) xs[i] += delta\n }\n}\n\n// After WASM runs, JS sees mutations immediately (same memory)\n```\n\n- Regular `Float32Array` args are copied in before and out after each WASM call\n- `wasmBuffer` arrays skip both copies (detected via `buffer === wasmMemory.buffer`)\n- Uses a bump allocator — allocations persist for program lifetime (no deallocation)\n- All WASM blocks in a file share one `WebAssembly.Memory` (64MB / 1024 pages)\n- Supports `Float32Array`, `Float64Array`, `Int32Array`, `Uint8Array`\n\n### Current Limitations\n\n- No imports/exports beyond the function itself\n- `wasmBuffer` allocations are permanent (bump allocator, no free)\n\n## Dependencies\n\nRuntime (shipped): `acorn` (JS parser, ~30KB), `tosijs-schema` (validation, ~5KB). Both have zero transitive dependencies.\n\n## Forbidden Properties (Security)\n\nThe following property names are blocked in expression evaluation to prevent prototype pollution:\n\n- `__proto__`, `constructor`, `prototype`\n\nThese are hardcoded in `runtime.ts` and checked during member access in `evaluateExpr()`.\n\n## Batteries System\n\nThe batteries (`src/batteries/`) provide zero-config local AI development:\n\n- **Lazy initialization**: First import audits LM Studio models (cached 24 hours)\n- **HTTPS detection**: Blocks local LLM calls from HTTPS contexts (security)\n- **Capabilities interface**: `fetch`, `store` (KV + vector), `llmBattery` (predict/embed)\n\nRegister battery atoms: `new AgentVM(batteryAtoms)` then pass `{ capabilities: batteries }` to `run()`.\n\n### Capability Key Naming\n\nThe base `Capabilities` interface (`runtime.ts`) uses `llm` with `{ predict, embed? }`, but the battery atoms access capabilities via different keys:\n\n| Capability key | Used by | Contains |\n| -------------- | -------------------------------------------------------- | -------------------------------------------- |\n| `llmBattery` | `llmPredictBattery`, `llmVision` | Full `LLMCapability` (`predict` + `embed`) |\n| `vector` | `storeVectorize` | Just `{ embed }` (extracted from llmBattery) |\n| `store` | `storeSearch`, `storeCreateCollection`, `storeVectorAdd` | KV + vector store ops |\n\nBoth `llmBattery` and `vector` can be `undefined`/`null` if LM Studio isn't available or HTTPS is detected.\n\n### Battery Atom Return Types\n\n- **`llmPredictBattery`**: Returns OpenAI message format `{ role?, content?, tool_calls? }` — NOT a plain string\n- **`storeVectorize`**: Returns `number[]` (embedding vector)\n- **`storeSearch`**: Returns `any[]` (matched documents)\n\n## Development Configuration\n\n### Bun Plugin\n\n`bunfig.toml` preloads `src/bun-plugin/tjs-plugin.ts` which enables importing `.tjs` files directly in bun. It also aliases `tjs-lang` to `./src/index.ts` for local development (monorepo-style resolution).\n\n### Code Style\n\n- **Prettier**: Single quotes, no semicolons, 2-space indentation, 80 char width, es5 trailing commas\n- Prefix unused variables with `_` (enforced by ESLint: `argsIgnorePattern: '^_'`)\n- `any` types are allowed (`@typescript-eslint/no-explicit-any: 0`)\n- Module type is ESM (`\"type\": \"module\"` in package.json)\n- Build output goes to `dist/` (declaration files only via `tsconfig.build.json`, bundles via `scripts/build.ts`)\n- Run `npm run format` before committing (ESLint fix + Prettier)\n\n### Firebase Deployment\n\nThe playground is hosted on Firebase (`tjs-platform.web.app`). Demo build output goes to `.demo/` (gitignored) which is the Firebase hosting root. Cloud Functions live in `functions/` with their own build process (`functions/src/*.tjs` → transpile → bundle). Firebase config: `firebase.json`, `.firebaserc`, `firestore.rules`.\n\nThe `docs/` directory contains real documentation (markdown), not build artifacts. See `docs/README.md` for the documentation index.\n\n### Additional Directories\n\n- `tjs-src/` — TJS runtime written in TJS itself (self-hosting)\n- `guides/` — Usage patterns, benchmarks, examples (`patterns.md`, `benchmarks.md`, `tjs-examples.md`)\n- `examples/` — Standalone TJS example files (`hello.tjs`, `datetime.tjs`, `generic-demo.tjs`)\n- `editors/` — Syntax highlighting for Monaco, CodeMirror, Ace, VSCode\n\n### Additional Documentation\n\n- `DOCS-TJS.md` — TJS language guide\n- `DOCS-AJS.md` — AJS runtime guide\n- `CONTEXT.md` — Architecture deep dive\n- `AGENTS.md` — Agent workflow instructions (issue tracking with `bd`, mandatory push-before-done)\n- `PLAN.md` — Roadmap\n\n### Known Gotcha: Self-Contained Output\n\nTranspiled TJS code currently requires `globalThis.__tjs` to be set up with `createRuntime()` before execution. A `{ standalone: true }` option to inline the ~1KB runtime is planned but not yet implemented.\n"
658
+ "text": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Project Overview\n\n**tjs-lang** (npm: `tjs-lang`) is a typed JavaScript platform — a language, runtime, and toolchain that transpiles TypeScript and TJS to JavaScript with runtime type validation, inline WASM, monadic errors, and safe eval. It also includes AJS, a gas-metered VM for executing untrusted agent code in any JavaScript environment.\n\n**Three pillars:**\n\n- **TJS** — TypeScript-like syntax where types are examples that survive to runtime as contracts, documentation, and tests. Transpiles TS → TJS → JS in a single fast pass.\n- **AJS** — Agent language that compiles to JSON AST for safe, sandboxed execution with fuel limits and injected capabilities. Code travels to data.\n- **Toolchain** — Compresses transpilation, linting, testing, and documentation generation into one pass. Includes inline WASM with SIMD, polymorphic dispatch, local class extensions, and a browser-based playground.\n\n## Common Commands\n\n```bash\n# Development\nnpm run format # ESLint fix + Prettier\nnpm run test:fast # Core tests (skips LLM & benchmarks)\nnpm run make # Full build (clean, format, grammars, tsc, esbuild)\nnpm run dev # Development server with file watcher\nnpm run start # Build demo + start dev server\nnpm run latest # Clean reinstall (rm node_modules + bun install)\n\n# Testing (framework: bun:test — describe/it/expect)\nbun test # Full test suite\nbun test src/path/to/file.test.ts # Single test file\nbun test --test-name-pattern \"pattern\" # Run tests matching pattern\nSKIP_LLM_TESTS=1 bun test # Skip LLM integration tests\nbun test --coverage # With coverage report\n\n# Efficient test debugging - capture once, query multiple times\nbun test 2>&1 | tee /tmp/test-results.txt | tail -20 # Run and save\ngrep -E \"^\\(fail\\)\" /tmp/test-results.txt # List failures\ngrep -A10 \"test name\" /tmp/test-results.txt # See specific error\n\n# CLI tools\nbun src/cli/tjs.ts check <file> # Parse and type check TJS file\nbun src/cli/tjs.ts run <file> # Transpile and execute\nbun src/cli/tjs.ts types <file> # Output type metadata as JSON\nbun src/cli/tjs.ts emit <file> # Output transpiled JavaScript\nbun src/cli/tjs.ts convert <file> # Convert TypeScript to TJS (--emit-tjs) or JS\nbun src/cli/tjs.ts test <file> # Run inline tests in a TJS file\n\n# Type checking & other\nnpm run typecheck # tsc --noEmit (type check without emitting)\nnpm run test:llm # LM Studio integration tests\nnpm run bench # Vector search benchmarks\nnpm run docs # Generate documentation\n\n# Build standalone CLI binaries\nnpm run build:cli # Compiles tjs + tjsx to dist/\n\n# Deployment (Firebase)\nnpm run deploy # Build demo + deploy functions + hosting\nnpm run deploy:hosting # Hosting only (serves from .demo/)\nnpm run functions:deploy # Cloud functions only\nnpm run functions:serve # Local functions emulator\n```\n\n## Architecture\n\n### Two-Layer Design\n\n1. **Builder Layer** (`src/builder.ts`): Fluent API that constructs AST nodes. Contains no execution logic.\n2. **Runtime Layer** (`src/vm/runtime.ts`): Executes AST nodes. Contains all atom implementations (~2900 lines, security-critical).\n\n### Key Source Files\n\n- `src/index.ts` - Main entry, re-exports everything\n- `src/vm/runtime.ts` - All atom implementations, expression evaluation, fuel charging (~3000 lines, security-critical)\n- `src/vm/vm.ts` - AgentVM class (~226 lines)\n- `src/vm/atoms/batteries.ts` - Battery atoms (vector search, LLM, store operations)\n- `src/builder.ts` - TypedBuilder fluent API (~19KB)\n- `src/lang/parser.ts` - TJS parser with colon shorthand, unsafe markers, return type extraction\n- `src/lang/emitters/ast.ts` - Emits Agent99 AST from parsed source\n- `src/lang/emitters/js.ts` - Emits JavaScript with `__tjs` metadata\n- `src/lang/emitters/from-ts.ts` - TypeScript to TJS/JS transpiler with class metadata extraction\n- `src/lang/inference.ts` - Type inference from example values\n- `src/lang/linter.ts` - Static analysis (unused vars, unreachable code, no-explicit-new)\n- `src/lang/runtime.ts` - TJS runtime (monadic errors, type checking, wrapClass)\n- `src/lang/wasm.ts` - WASM compiler (opcodes, disassembler, bytecode generation)\n- `src/types/` - Type system definitions (Type.ts, Generic.ts)\n- `src/transpiler/` - AJS transpiler (source → AST)\n- `src/batteries/` - LM Studio integration (lazy init, model audit, vector search)\n- `src/store/` - Store implementations for persistence\n- `src/rbac/` - Role-based access control\n- `src/use-cases/` - Integration tests and real-world examples (28 test files)\n- `src/cli/tjs.ts` - CLI tool for check/run/types/emit/convert/test commands\n- `src/cli/tjsx.ts` - JSX/component runner\n- `src/cli/playground.ts` - Local playground server\n- `src/cli/create-app.ts` - Project scaffolding tool\n\n### Core APIs\n\n```typescript\n// Language\najs`...` // Parse AJS to AST\ntjs`...` // Parse TypeScript variant with type metadata\ntranspile(source, options) // Full transpilation with signature extraction\ncreateAgent(source, vm) // Creates callable agent\n\n// VM\nconst vm = new AgentVM(customAtoms)\nawait vm.run(ast, args, { fuel, capabilities, timeoutMs, trace })\n\n// Builder\nAgent.take(schema).varSet(...).httpFetch(...).return(schema)\nvm.Agent // Builder with custom atoms included\n```\n\n### Package Entry Points\n\n```typescript\nimport { Agent, AgentVM, ajs, tjs } from 'tjs-lang' // Main entry\nimport { Eval, SafeFunction } from 'tjs-lang/eval' // Safe eval utilities\nimport { tjs, transpile } from 'tjs-lang/lang' // Language tools only\nimport { fromTS } from 'tjs-lang/lang/from-ts' // TypeScript transpilation\n```\n\n### Transpiler Chain (TS → TJS → JS)\n\nTJS supports transpiling TypeScript to JavaScript with runtime type validation. The pipeline has two distinct, independently testable steps:\n\n**Step 1: TypeScript → TJS** (`fromTS`)\n\n```typescript\nimport { fromTS } from 'tosijs/lang/from-ts'\n\nconst tsSource = `\nfunction greet(name: string): string {\n return \\`Hello, \\${name}!\\`\n}\n`\n\nconst result = fromTS(tsSource, { emitTJS: true })\n// result.code contains TJS:\n// function greet(name: '') -> '' {\n// return \\`Hello, \\${name}!\\`\n// }\n```\n\n**Step 2: TJS → JavaScript** (`tjs`)\n\n```typescript\nimport { tjs } from 'tosijs/lang'\n\nconst tjsSource = `\nfunction greet(name: '') -> '' {\n return \\`Hello, \\${name}!\\`\n}\n`\n\nconst jsResult = tjs(tjsSource)\n// jsResult.code contains JavaScript with __tjs metadata for runtime validation\n```\n\n**Full Chain Example:**\n\n```typescript\nimport { fromTS } from 'tosijs/lang/from-ts'\nimport { tjs } from 'tosijs/lang'\n\n// TypeScript source with type annotations\nconst tsSource = `\nfunction add(a: number, b: number): number {\n return a + b\n}\n`\n\n// Step 1: TS → TJS\nconst tjsResult = fromTS(tsSource, { emitTJS: true })\n\n// Step 2: TJS → JS (with runtime validation)\nconst jsCode = tjs(tjsResult.code)\n\n// Execute the result\nconst fn = new Function('__tjs', jsCode + '; return add')(__tjs_runtime)\nfn(1, 2) // Returns 3\nfn('a', 'b') // Returns { error: 'type mismatch', ... }\n```\n\n**Design Notes:**\n\n- The two steps are intentionally separate for tree-shaking (TS support is optional)\n- `fromTS` lives in a separate entry point (`tosijs/lang/from-ts`)\n- Import only what you need to keep bundle size minimal\n- Each step is independently testable (see `src/lang/codegen.test.ts`)\n\n### Security Model\n\n- **Capability-based**: VM has zero IO by default; inject `fetch`, `store`, `llm` via capabilities\n- **Fuel metering**: Every atom has a cost; execution stops when fuel exhausted\n- **Timeout enforcement**: Default `fuel × 10ms`; explicit `timeoutMs` overrides\n- **Monadic errors**: Errors wrapped in `AgentError` (VM) / `MonadicError` (TJS), not thrown (prevents exception exploits). Use `isMonadicError()` to check — `isError()` is deprecated\n- **Expression sandboxing**: ExprNode AST evaluation, blocked prototype access\n\n### Expression Evaluation\n\nExpressions use AST nodes (`$expr`), not strings:\n\n```typescript\n{ $expr: 'binary', op: '+', left: {...}, right: {...} }\n{ $expr: 'ident', name: 'varName' }\n{ $expr: 'member', object: {...}, property: 'foo' }\n```\n\nEach node costs 0.01 fuel. Forbidden: function calls, `new`, `this`, `__proto__`, `constructor`.\n\n## AJS Expression Gotchas\n\nAJS expressions behave differently from JavaScript in several important ways:\n\n- **Null member access is safe by default**: `null.foo.bar` returns `undefined` silently (uses `?.` semantics internally). This differs from JavaScript which would throw `TypeError`.\n- **No computed member access with variables**: `items[i]` fails at transpile time with \"Computed member access with variables not yet supported\". Literal indices work (`items[0]`, `obj[\"key\"]`). Workaround: use `.map`/`.reduce` atoms instead.\n- **Unknown atom errors**: When an atom doesn't exist, the error is `\"Unknown Atom: <name>\"` with no listing of available atoms.\n- **TJS parameter syntax is NOT TypeScript**: `function foo(x: 'default')` means \"required param, example value 'default'\" — not a TypeScript string literal type. LLMs consistently generate `function foo(x: string)` which is wrong. The colon value is an _example_, not a _type annotation_.\n\n## Testing Strategy\n\n- Unit tests alongside source files (`*.test.ts`)\n- Integration tests in `src/use-cases/` (RAG, orchestration, malicious actors)\n- Security tests in `src/use-cases/malicious-actor.test.ts`\n- Language tests split across 14 files in `src/lang/` (lang.test.ts, features.test.ts, codegen.test.ts, parser.test.ts, from-ts.test.ts, wasm.test.ts, etc.)\n\nCoverage targets: 98% lines on `src/vm/runtime.ts` (security-critical), 80%+ overall.\n\n**Bug fix rule:** Always create a reproduction test case before fixing a bug.\n\n## Key Patterns\n\n### Adding a New Atom\n\n1. Define with `defineAtom(opCode, inputSchema, outputSchema, implementation, { cost, timeoutMs, docs })`\n2. Add to `src/vm/atoms/` and export from `src/vm/atoms/index.ts`\n3. Add tests\n4. Run `npm run test:fast`\n\n**Atom implementation notes:**\n\n- `cost` can be static number or dynamic: `(input, ctx) => number`\n- `timeoutMs` defaults to 1000ms; use `0` for no timeout (e.g., `seq`)\n- Atoms are always async; fuel deduction is automatic in the `exec` wrapper\n\n### Debugging Agents\n\nEnable tracing: `vm.run(ast, args, { trace: true })` returns `TraceEvent[]` with execution path, fuel consumption, and state changes.\n\n### Custom Atoms Must\n\n- Be non-blocking (no synchronous CPU-heavy work)\n- Respect `ctx.signal` for cancellation\n- Access IO only via `ctx.capabilities`\n\n### Value Resolution\n\nThe `resolveValue()` function handles multiple input patterns:\n\n- `{ $kind: 'arg', path: 'varName' }` → lookup in `ctx.args`\n- `{ $expr: ... }` → evaluate ExprNode via `evaluateExpr()`\n- String with dots `'obj.foo.bar'` → traverse state with forbidden property checks\n- Bare strings → lookup in state, else return literal\n\n### Monadic Error Flow\n\nWhen `ctx.error` is set, subsequent atoms in a `seq` skip execution. Errors are wrapped in `AgentError`, not thrown. This prevents exception-based exploits.\n\n### TJS Parser Syntax Extensions\n\nTJS extends JavaScript with type annotations that survive to runtime.\n\n#### Classes (Callable Without `new`)\n\nTJS classes are wrapped to be callable without the `new` keyword:\n\n```typescript\nclass Point {\n constructor(public x: number, public y: number) {}\n}\n\n// Both work identically:\nconst p1 = Point(10, 20) // TJS way - clean\nconst p2 = new Point(10, 20) // Still works, but linter warns\n\n// The linter flags explicit `new` usage:\n// Warning: Unnecessary 'new' keyword. In TJS, classes are callable without 'new'\n```\n\nThe `wrapClass()` function in the runtime uses a Proxy to intercept calls and auto-construct.\n\n#### Function Parameters\n\n```typescript\n// Required param with example value (colon shorthand)\nfunction greet(name: 'Alice') { } // name is required, type inferred as string\n\n// Numeric type narrowing (all valid JS syntax)\nfunction calc(rate: 3.14) { } // number (float) -- has decimal point\nfunction calc(count: 42) { } // integer -- whole number\nfunction calc(index: +0) { } // non-negative integer -- + prefix\n\n// Optional param with default\nfunction greet(name = 'Alice') { } // name is optional, defaults to 'Alice'\n\n// Object parameter with shape\nfunction createUser(user: { name: '', age: 0 }) { }\n\n// Nullable type\nfunction find(id: 0 | null) { } // integer or null\n\n// Optional TS-style\nfunction greet(name?: '') { } // same as name = ''\n```\n\n#### Return Types\n\n```typescript\n// Return type annotation (arrow syntax)\nfunction add(a: 0, b: 0) -> 0 { return a + b }\n\n// Object return type\nfunction getUser(id: 0) -> { name: '', age: 0 } { ... }\n```\n\n#### Safety Markers\n\n```typescript\n// Unsafe function (skips runtime validation)\nfunction fastAdd(! a: 0, b: 0) { return a + b }\n\n// Safe function (explicit validation)\nfunction safeAdd(? a: 0, b: 0) { return a + b }\n\n// Unsafe block\nunsafe {\n // All calls in here skip validation\n fastPath(data)\n}\n```\n\n#### Type Declarations\n\n```typescript\n// Simple type from example\nType Name 'Alice'\n\n// Type with description and example\nType User {\n description: 'a user object'\n example: { name: '', age: 0 }\n}\n\n// Type with predicate (auto-generates type guard from example)\nType EvenNumber {\n description: 'an even number'\n example: 2\n predicate(x) { return x % 2 === 0 }\n}\n```\n\n#### Generic Declarations\n\n```typescript\n// Simple generic\nGeneric Box<T> {\n description: 'a boxed value'\n predicate(x, T) {\n return typeof x === 'object' && x !== null && 'value' in x && T(x.value)\n }\n}\n\n// Generic with default type parameter\nGeneric Container<T, U = ''> {\n description: 'container with label'\n predicate(obj, T, U) {\n return T(obj.item) && U(obj.label)\n }\n}\n```\n\n#### Bare Assignments\n\n```typescript\n// Uppercase identifiers auto-get const\nFoo = Type('test', 'example') // becomes: const Foo = Type(...)\nMyConfig = { debug: true } // becomes: const MyConfig = { ... }\n```\n\n#### Module Safety Directive\n\n```typescript\n// At top of file - sets default validation level\nsafety none // No validation (metadata only)\nsafety inputs // Validate function inputs (default)\nsafety all // Validate everything (debug mode)\n```\n\n#### TJS Mode Directives\n\nJavaScript semantics are the default. TJS improvements are opt-in via file-level directives:\n\n```typescript\nTjsStrict // Enables ALL modes below at once\n\nTjsEquals // == and != use structural equality (Is/IsNot)\nTjsClass // Classes callable without new, explicit new is banned\nTjsDate // Date is banned, use Timestamp/LegalDate instead\nTjsNoeval // eval() and new Function() are banned\nTjsStandard // Newlines as statement terminators (prevents ASI footguns)\nTjsSafeEval // Include Eval/SafeFunction in runtime for dynamic code\n```\n\nMultiple directives can be combined. Place them at the top of the file before any code.\n\n#### Equality Operators\n\nWith `TjsEquals` (or `TjsStrict`), TJS redefines equality to be structural, fixing JavaScript's confusing `==` vs `===` semantics.\n\n| Operator | Meaning | Example |\n| ----------- | -------------------------------- | --------------------------- |\n| `==` | Structural equality | `{a:1} == {a:1}` is `true` |\n| `!=` | Structural inequality | `{a:1} != {a:2}` is `true` |\n| `===` | Identity (same reference) | `obj === obj` is `true` |\n| `!==` | Not same reference | `{a:1} !== {a:1}` is `true` |\n| `a Is b` | Structural equality (explicit) | Same as `==` |\n| `a IsNot b` | Structural inequality (explicit) | Same as `!=` |\n\n```typescript\n// Structural equality - compares values deeply\nconst a = { x: 1, y: [2, 3] }\nconst b = { x: 1, y: [2, 3] }\na == b // true (same structure)\na === b // false (different objects)\n\n// Works with arrays too\n[1, 2, 3] == [1, 2, 3] // true\n\n// Infix operators for readability\nuser Is expectedUser\nresult IsNot errorValue\n```\n\n**Implementation Notes:**\n\n- **AJS (VM)**: The VM's expression evaluator (`src/vm/runtime.ts`) uses `isStructurallyEqual()` for `==`/`!=`\n- **TJS (browser/Node)**: Source transformation converts `==` to `Is()` and `!=` to `IsNot()` calls\n- **`===` and `!==`**: Always preserved as identity checks, never transformed\n- The `Is()` and `IsNot()` functions are available in `src/lang/runtime.ts` and exposed globally\n\n**Custom Equality Protocol:**\n\n- `[tjsEquals]` symbol (`Symbol.for('tjs.equals')`) — highest priority, ideal for Proxies\n- `.Equals` method — backward-compatible, works on any object/class\n- Priority: symbol → `.Equals` → structural comparison\n- `tjsEquals` is exported from `src/lang/runtime.ts` and available as `__tjs.tjsEquals`\n\n#### Polymorphic Functions\n\nMultiple function declarations with the same name are merged into a dispatcher:\n\n```typescript\nfunction area(radius: 3.14) {\n return Math.PI * radius * radius\n}\nfunction area(w: 0.0, h: 0.0) {\n return w * h\n}\n\narea(5) // dispatches to variant 1 (one number)\narea(3, 4) // dispatches to variant 2 (two numbers)\n```\n\nDispatch order: arity first, then type specificity, then declaration order. Ambiguous signatures (same types at same arity) are caught at transpile time.\n\n#### Polymorphic Constructors\n\nClasses can have multiple constructor signatures (requires `TjsClass` directive):\n\n```typescript\nTjsClass\n\nclass Point {\n constructor(x: 0.0, y: 0.0) {\n this.x = x\n this.y = y\n }\n constructor(coords: { x: 0.0; y: 0.0 }) {\n this.x = coords.x\n this.y = coords.y\n }\n}\n\nPoint(3, 4) // variant 1\nPoint({ x: 10, y: 20 }) // variant 2 (both produce correct instanceof)\n```\n\nThe first constructor becomes the real JS constructor; additional variants become factory functions using `Object.create`.\n\n#### Local Class Extensions\n\nAdd methods to built-in types without prototype pollution:\n\n```typescript\nextend String {\n capitalize() { return this[0].toUpperCase() + this.slice(1) }\n}\n\nextend Array {\n last() { return this[this.length - 1] }\n}\n\n'hello'.capitalize() // 'Hello' — rewritten to __ext_String.capitalize.call('hello')\n[1, 2, 3].last() // 3\n```\n\n- Methods are rewritten to `.call()` at transpile time for known-type receivers (zero overhead)\n- Runtime fallback via `registerExtension()`/`resolveExtension()` for unknown types\n- Arrow functions rejected (need `this` binding)\n- Multiple `extend` blocks for same type merge left-to-right\n- File-local only — no cross-module leaking\n\n## WASM Blocks\n\nTJS supports inline WebAssembly for performance-critical code. WASM blocks are compiled at transpile time and embedded as base64 in the output.\n\n### Syntax\n\n```typescript\nconst add = wasm (a: i32, b: i32) -> i32 {\n local.get $a\n local.get $b\n i32.add\n}\n```\n\n### Features\n\n- **Transpile-time compilation**: WASM bytecode is generated during transpilation, not at runtime\n- **WAT comments**: Human-readable WebAssembly Text format is included as comments above the base64\n- **Type-safe**: Parameters and return types are validated\n- **Self-contained**: Compiled WASM is embedded in output JS, no separate .wasm files needed\n\n### Output Example\n\nThe transpiler generates code like:\n\n```javascript\n/*\n * WASM Block: add\n * WAT (WebAssembly Text):\n * (func $add (param $a i32) (param $b i32) (result i32)\n * local.get 0\n * local.get 1\n * i32.add\n * )\n */\nconst add = await (async () => {\n const bytes = Uint8Array.from(atob('AGFzbQEAAAA...'), (c) => c.charCodeAt(0))\n const { instance } = await WebAssembly.instantiate(bytes)\n return instance.exports.fn\n})()\n```\n\n### SIMD Intrinsics (f32x4)\n\nWASM blocks support explicit SIMD via `f32x4_*` intrinsics:\n\n```typescript\nconst scale = wasm (arr: Float32Array, len: 0, factor: 0.0) -> 0 {\n let s = f32x4_splat(factor)\n for (let i = 0; i < len; i += 4) {\n let off = i * 4\n let v = f32x4_load(arr, off)\n f32x4_store(arr, off, f32x4_mul(v, s))\n }\n} fallback {\n for (let i = 0; i < len; i++) arr[i] *= factor\n}\n```\n\nAvailable: `f32x4_load`, `f32x4_store`, `f32x4_splat`, `f32x4_extract_lane`, `f32x4_replace_lane`, `f32x4_add`, `f32x4_sub`, `f32x4_mul`, `f32x4_div`, `f32x4_neg`, `f32x4_sqrt`.\n\n### Zero-Copy Arrays: `wasmBuffer()`\n\n`wasmBuffer(Constructor, length)` allocates typed arrays directly in WASM linear memory. When passed to a `wasm {}` block, these arrays are zero-copy — no marshalling overhead.\n\n```typescript\n// Allocate in WASM memory (zero-copy when passed to wasm blocks)\nconst xs = wasmBuffer(Float32Array, 50000)\n\n// Works like a normal Float32Array from JS\nxs[0] = 3.14\nfor (let i = 0; i < xs.length; i++) xs[i] = Math.random()\n\n// Zero-copy in WASM blocks — data is already in WASM memory\nfunction process(! xs: Float32Array, len: 0, delta: 0.0) {\n wasm {\n let vd = f32x4_splat(delta)\n for (let i = 0; i < len; i += 4) {\n let off = i * 4\n f32x4_store(xs, off, f32x4_add(f32x4_load(xs, off), vd))\n }\n } fallback {\n for (let i = 0; i < len; i++) xs[i] += delta\n }\n}\n\n// After WASM runs, JS sees mutations immediately (same memory)\n```\n\n- Regular `Float32Array` args are copied in before and out after each WASM call\n- `wasmBuffer` arrays skip both copies (detected via `buffer === wasmMemory.buffer`)\n- Uses a bump allocator — allocations persist for program lifetime (no deallocation)\n- All WASM blocks in a file share one `WebAssembly.Memory` (64MB / 1024 pages)\n- Supports `Float32Array`, `Float64Array`, `Int32Array`, `Uint8Array`\n\n### Current Limitations\n\n- No imports/exports beyond the function itself\n- `wasmBuffer` allocations are permanent (bump allocator, no free)\n\n## Dependencies\n\nRuntime (shipped): `acorn` (JS parser, ~30KB), `tosijs-schema` (validation, ~5KB). Both have zero transitive dependencies.\n\n## Forbidden Properties (Security)\n\nThe following property names are blocked in expression evaluation to prevent prototype pollution:\n\n- `__proto__`, `constructor`, `prototype`\n\nThese are hardcoded in `runtime.ts` and checked during member access in `evaluateExpr()`.\n\n## Batteries System\n\nThe batteries (`src/batteries/`) provide zero-config local AI development:\n\n- **Lazy initialization**: First import audits LM Studio models (cached 24 hours)\n- **HTTPS detection**: Blocks local LLM calls from HTTPS contexts (security)\n- **Capabilities interface**: `fetch`, `store` (KV + vector), `llmBattery` (predict/embed)\n\nRegister battery atoms: `new AgentVM(batteryAtoms)` then pass `{ capabilities: batteries }` to `run()`.\n\n### Capability Key Naming\n\nThe base `Capabilities` interface (`runtime.ts`) uses `llm` with `{ predict, embed? }`, but the battery atoms access capabilities via different keys:\n\n| Capability key | Used by | Contains |\n| -------------- | -------------------------------------------------------- | -------------------------------------------- |\n| `llmBattery` | `llmPredictBattery`, `llmVision` | Full `LLMCapability` (`predict` + `embed`) |\n| `vector` | `storeVectorize` | Just `{ embed }` (extracted from llmBattery) |\n| `store` | `storeSearch`, `storeCreateCollection`, `storeVectorAdd` | KV + vector store ops |\n\nBoth `llmBattery` and `vector` can be `undefined`/`null` if LM Studio isn't available or HTTPS is detected.\n\n### Battery Atom Return Types\n\n- **`llmPredictBattery`**: Returns OpenAI message format `{ role?, content?, tool_calls? }` — NOT a plain string\n- **`storeVectorize`**: Returns `number[]` (embedding vector)\n- **`storeSearch`**: Returns `any[]` (matched documents)\n\n## Development Configuration\n\n### Bun Plugin\n\n`bunfig.toml` preloads `src/bun-plugin/tjs-plugin.ts` which enables importing `.tjs` files directly in bun. It also aliases `tjs-lang` to `./src/index.ts` for local development (monorepo-style resolution).\n\n### Code Style\n\n- **Prettier**: Single quotes, no semicolons, 2-space indentation, 80 char width, es5 trailing commas\n- Prefix unused variables with `_` (enforced by ESLint: `argsIgnorePattern: '^_'`)\n- `any` types are allowed (`@typescript-eslint/no-explicit-any: 0`)\n- Module type is ESM (`\"type\": \"module\"` in package.json)\n- Build output goes to `dist/` (declaration files only via `tsconfig.build.json`, bundles via `scripts/build.ts`)\n- Run `npm run format` before committing (ESLint fix + Prettier)\n\n### Firebase Deployment\n\nThe playground is hosted on Firebase (`tjs-platform.web.app`). Demo build output goes to `.demo/` (gitignored) which is the Firebase hosting root. Cloud Functions live in `functions/` with their own build process (`functions/src/*.tjs` → transpile → bundle). Firebase config: `firebase.json`, `.firebaserc`, `firestore.rules`.\n\nThe `docs/` directory contains real documentation (markdown), not build artifacts. See `docs/README.md` for the documentation index.\n\n### Additional Directories\n\n- `tjs-src/` — TJS runtime written in TJS itself (self-hosting)\n- `guides/` — Usage patterns, benchmarks, examples (`patterns.md`, `benchmarks.md`, `tjs-examples.md`)\n- `examples/` — Standalone TJS example files (`hello.tjs`, `datetime.tjs`, `generic-demo.tjs`)\n- `editors/` — Syntax highlighting for Monaco, CodeMirror, Ace, VSCode\n\n### Additional Documentation\n\n- `DOCS-TJS.md` — TJS language guide\n- `DOCS-AJS.md` — AJS runtime guide\n- `CONTEXT.md` — Architecture deep dive\n- `AGENTS.md` — Agent workflow instructions (issue tracking with `bd`, mandatory push-before-done). **Critical**: work is NOT complete until `git push` succeeds; use `bd ready` to find work, `bd close <id>` to complete\n- `PLAN.md` — Roadmap\n\n### Known Gotcha: `tjs()` Returns an Object, Not a String\n\n`tjs(source)` returns `{ code, types, metadata, testResults, ... }`. Use `.code` to get the transpiled JavaScript string. This is a common mistake.\n\n### Known Gotcha: Running Emitted TJS Code in Tests\n\nTranspiled TJS code requires `globalThis.__tjs` to be set up with `createRuntime()` before execution:\n\n```typescript\nimport { createRuntime } from '../lang/runtime'\n\nconst saved = globalThis.__tjs\nglobalThis.__tjs = createRuntime()\ntry {\n const fn = new Function(result.code + '\\nreturn fnName')()\n // ... test fn\n} finally {\n globalThis.__tjs = saved\n}\n```\n\nA `{ standalone: true }` option to inline the ~1KB runtime is planned but not yet implemented.\n"
659
659
  },
660
660
  {
661
661
  "title": "Context: Working with tosijs-schema",
@@ -675,12 +675,24 @@
675
675
  "filename": "docs.test.ts",
676
676
  "path": "src/lang/docs.test.ts"
677
677
  },
678
+ {
679
+ "text": "Greet a person by name",
680
+ "title": "dts.test (inline docs)",
681
+ "filename": "dts.test.ts",
682
+ "path": "src/lang/emitters/dts.test.ts"
683
+ },
678
684
  {
679
685
  "text": "... *\\/) from source with position info.\n * These need to be preserved in TJS output in their original positions.\n * Comments inside function bodies are already preserved by the TS transpiler.\n\n---\n\n...",
680
686
  "title": "from-ts (inline docs)",
681
687
  "filename": "from-ts.ts",
682
688
  "path": "src/lang/emitters/from-ts.ts"
683
689
  },
690
+ {
691
+ "title": "GEMINI.md - Project Context & Instructions",
692
+ "filename": "GEMINI.md",
693
+ "path": "GEMINI.md",
694
+ "text": "# GEMINI.md - Project Context & Instructions\n\nThis file provides foundational mandates and contextual information for Gemini CLI when working in the `tjs-lang` repository.\n\n## Project Overview\n\n**tjs-lang** is a platform for type-safe JavaScript execution and AI agent orchestration. It bridges the gap between static typing and runtime safety by preserving type information and providing a secure execution environment.\n\n### Core Languages\n\n1. **TJS (Typed JavaScript):** A TypeScript-like dialect that transpiles to JavaScript with embedded `__tjs` metadata. This metadata enables runtime type validation, auto-documentation, and introspection.\n2. **AJS (Agent JavaScript):** A safe, sandboxed language for AI agents. It compiles to a JSON-serializable AST and executes in a gas-limited VM with capability-based security.\n\n### Architecture\n\n- **Builder Layer** (`src/builder.ts`): Fluent API for constructing AST nodes.\n- **Runtime Layer** (`src/vm/runtime.ts`): Security-critical core that executes AST nodes and manages fuel/capabilities.\n- **Lang Layer** (`src/lang/`): Contains the parser (based on Acorn), linter, and emitters for TJS and AJS.\n- **Batteries** (`src/batteries/`): Built-in capabilities like LLM integration (LM Studio), vector search, and persistence.\n\n## Development Lifecycle\n\n### Common Commands\n\n- **Install:** `bun install`\n- **Build:** `npm run make` (Format, build grammars, compile TS, and bundle)\n- **Fast Test:** `npm run test:fast` (Runs core tests, skips LLM and benchmarks)\n- **Full Test:** `bun test`\n- **Type Check:** `npm run typecheck`\n- **Lint:** `npm run lint`\n- **Format:** `npm run format` (ESLint fix + Prettier)\n- **Dev Server:** `npm run dev`\n- **CLI Tool:** `bun src/cli/tjs.ts <command>` (Commands: `check`, `run`, `types`, `emit`, `convert`, `test`)\n\n### Testing Strategy\n\n- **Unit Tests:** Located alongside source files (e.g., `src/lang/parser.test.ts`).\n- **Integration Tests:** Located in `src/use-cases/`.\n- **Security Tests:** Critical for the VM, found in `src/use-cases/malicious-actor.test.ts`.\n- **Reproduction:** Always create a reproduction test case before fixing a bug.\n\n## Engineering Standards & Conventions\n\n### Coding Style\n\n- **Formatting:** Prettier (Single quotes, no semicolons, 2-space indentation, 80 char width).\n- **Naming:** Follow existing conventions; prefix unused variables with `_`.\n- **Type Safety:** Prioritize TypeScript for all core logic. `any` is allowed but should be used judiciously.\n- **Security:** Rigorously protect the `src/vm/runtime.ts` file as it is security-critical. Avoid prototype access (`__proto__`, `constructor`, `prototype`) in expression evaluation.\n\n### Language Specifics\n\n- **TJS Types:** In TJS, colons indicate _examples_, not just type annotations (e.g., `name: 'Alice'` means a required string).\n- **AJS Expressions:** Be aware that AJS expressions have differences from JS (e.g., safe null member access, limited computed member access).\n- **Monadic Errors:** Prefer returning error values (Monadic errors) over throwing exceptions to ensure execution stability and security.\n\n### Documentation\n\n- Maintain `DOCS-TJS.md` and `DOCS-AJS.md` for language-specific documentation.\n- Update `CLAUDE.md` for tool-specific guidance if architectural patterns change.\n\n## Contextual Precedence\n\n- **Directives:** Explicit requests for implementation take priority.\n- **Inquiries:** Requests for analysis or advice should not result in code changes without a subsequent Directive.\n- **Security First:** Never compromise the sandboxing or fuel-metering integrity of the VM.\n"
695
+ },
684
696
  {
685
697
  "title": "icebox",
686
698
  "filename": "tasks-f6d70f.md",
package/demo/index.html CHANGED
@@ -3,10 +3,10 @@
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>tjs-lang | Secure Agent Runtime</title>
6
+ <title>tjs-lang | Typed JavaScript Platform</title>
7
7
  <meta
8
8
  name="description"
9
- content="A type-safe virtual machine for executing untrusted agent code safely. Build, run, and deploy AI agents with confidence."
9
+ content="A typed JavaScript platform with runtime type validation, safe eval, inline WASM, and a gas-metered VM for untrusted code. Transpiles TypeScript and TJS to JavaScript with live metadata."
10
10
  />
11
11
 
12
12
  <!-- Favicon -->
@@ -135,12 +135,14 @@ export type LLMProvider =
135
135
  | 'custom'
136
136
  | 'openai'
137
137
  | 'anthropic'
138
+ | 'gemini'
138
139
  | 'deepseek'
139
140
 
140
141
  export interface LLMSettings {
141
142
  preferredProvider: LLMProvider
142
143
  openaiKey: string
143
144
  anthropicKey: string
145
+ geminiKey: string
144
146
  deepseekKey: string
145
147
  customLlmUrl: string
146
148
  }
@@ -152,6 +154,7 @@ export function getSettings(): LLMSettings {
152
154
  'auto') as LLMProvider,
153
155
  openaiKey: localStorage.getItem('openaiKey') || '',
154
156
  anthropicKey: localStorage.getItem('anthropicKey') || '',
157
+ geminiKey: localStorage.getItem('geminiKey') || '',
155
158
  deepseekKey: localStorage.getItem('deepseekKey') || '',
156
159
  customLlmUrl: localStorage.getItem('customLlmUrl') || '',
157
160
  }
@@ -163,6 +166,7 @@ export function buildLLMCapability(settings: LLMSettings) {
163
166
  preferredProvider,
164
167
  openaiKey,
165
168
  anthropicKey,
169
+ geminiKey,
166
170
  deepseekKey,
167
171
  customLlmUrl,
168
172
  } = settings
@@ -171,9 +175,16 @@ export function buildLLMCapability(settings: LLMSettings) {
171
175
  const hasCustomUrl = customLlmUrl && customLlmUrl.trim() !== ''
172
176
  const hasOpenAI = openaiKey && openaiKey.trim() !== ''
173
177
  const hasAnthropic = anthropicKey && anthropicKey.trim() !== ''
178
+ const hasGemini = geminiKey && geminiKey.trim() !== ''
174
179
  const hasDeepseek = deepseekKey && deepseekKey.trim() !== ''
175
180
 
176
- if (!hasCustomUrl && !hasOpenAI && !hasAnthropic && !hasDeepseek) {
181
+ if (
182
+ !hasCustomUrl &&
183
+ !hasOpenAI &&
184
+ !hasAnthropic &&
185
+ !hasGemini &&
186
+ !hasDeepseek
187
+ ) {
177
188
  return null
178
189
  }
179
190
 
@@ -280,6 +291,33 @@ export function buildLLMCapability(settings: LLMSettings) {
280
291
  return data.content?.[0]?.text ?? ''
281
292
  }
282
293
 
294
+ const callGemini = async (prompt: string, options?: any): Promise<string> => {
295
+ const model = options?.model || 'gemini-2.0-flash'
296
+ const response = await fetch(
297
+ `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${geminiKey}`,
298
+ {
299
+ method: 'POST',
300
+ headers: { 'Content-Type': 'application/json' },
301
+ body: JSON.stringify({
302
+ contents: [{ parts: [{ text: prompt }] }],
303
+ generationConfig: {
304
+ temperature: options?.temperature ?? 0.7,
305
+ },
306
+ }),
307
+ }
308
+ )
309
+ if (!response.ok) {
310
+ const error = await response.json().catch(() => ({}))
311
+ throw new Error(
312
+ `Gemini Error: ${response.status} - ${
313
+ error.error?.message || 'Check your API key'
314
+ }`
315
+ )
316
+ }
317
+ const data = await response.json()
318
+ return data.candidates?.[0]?.content?.parts?.[0]?.text ?? ''
319
+ }
320
+
283
321
  const callDeepseek = async (
284
322
  prompt: string,
285
323
  options?: any
@@ -320,6 +358,8 @@ export function buildLLMCapability(settings: LLMSettings) {
320
358
  return callOpenAI(prompt, options)
321
359
  if (preferredProvider === 'anthropic' && hasAnthropic)
322
360
  return callAnthropic(prompt, options)
361
+ if (preferredProvider === 'gemini' && hasGemini)
362
+ return callGemini(prompt, options)
323
363
  if (preferredProvider === 'deepseek' && hasDeepseek)
324
364
  return callDeepseek(prompt, options)
325
365
 
@@ -329,6 +369,7 @@ export function buildLLMCapability(settings: LLMSettings) {
329
369
  custom: 'Custom Endpoint',
330
370
  openai: 'OpenAI',
331
371
  anthropic: 'Anthropic',
372
+ gemini: 'Google Gemini',
332
373
  deepseek: 'Deepseek',
333
374
  }
334
375
  throw new Error(
@@ -340,6 +381,7 @@ export function buildLLMCapability(settings: LLMSettings) {
340
381
  if (hasCustomUrl) return callCustom(prompt, options)
341
382
  if (hasOpenAI) return callOpenAI(prompt, options)
342
383
  if (hasAnthropic) return callAnthropic(prompt, options)
384
+ if (hasGemini) return callGemini(prompt, options)
343
385
  if (hasDeepseek) return callDeepseek(prompt, options)
344
386
 
345
387
  throw new Error('No LLM provider configured')
@@ -377,6 +419,7 @@ export function buildLLMBattery(settings: LLMSettings) {
377
419
  preferredProvider,
378
420
  openaiKey,
379
421
  anthropicKey,
422
+ geminiKey,
380
423
  deepseekKey,
381
424
  customLlmUrl,
382
425
  } = settings
@@ -384,9 +427,16 @@ export function buildLLMBattery(settings: LLMSettings) {
384
427
  const hasCustomUrl = customLlmUrl && customLlmUrl.trim() !== ''
385
428
  const hasOpenAI = openaiKey && openaiKey.trim() !== ''
386
429
  const hasAnthropic = anthropicKey && anthropicKey.trim() !== ''
430
+ const hasGemini = geminiKey && geminiKey.trim() !== ''
387
431
  const hasDeepseek = deepseekKey && deepseekKey.trim() !== ''
388
432
 
389
- if (!hasCustomUrl && !hasOpenAI && !hasAnthropic && !hasDeepseek) {
433
+ if (
434
+ !hasCustomUrl &&
435
+ !hasOpenAI &&
436
+ !hasAnthropic &&
437
+ !hasGemini &&
438
+ !hasDeepseek
439
+ ) {
390
440
  return null
391
441
  }
392
442
 
@@ -780,6 +830,59 @@ export function buildLLMBattery(settings: LLMSettings) {
780
830
  return data.choices?.[0]?.message ?? { content: '' }
781
831
  }
782
832
 
833
+ const callGemini = async (
834
+ system: string,
835
+ user: UserContent,
836
+ _tools?: any[],
837
+ _responseFormat?: any
838
+ ): Promise<BatteryResult> => {
839
+ const model = 'gemini-2.0-flash'
840
+ const userText = typeof user === 'string' ? user : user.text
841
+ const contents: any[] = []
842
+ if (system) {
843
+ contents.push({ role: 'user', parts: [{ text: system }] })
844
+ contents.push({
845
+ role: 'model',
846
+ parts: [{ text: 'Understood.' }],
847
+ })
848
+ }
849
+ const userParts: any[] = [{ text: userText }]
850
+ if (typeof user !== 'string' && user.images?.length) {
851
+ for (const img of user.images) {
852
+ const match = img.match(/^data:(.*?);base64,(.*)$/)
853
+ if (match) {
854
+ userParts.push({
855
+ inline_data: { mime_type: match[1], data: match[2] },
856
+ })
857
+ }
858
+ }
859
+ }
860
+ contents.push({ role: 'user', parts: userParts })
861
+
862
+ const response = await fetch(
863
+ `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${geminiKey}`,
864
+ {
865
+ method: 'POST',
866
+ headers: { 'Content-Type': 'application/json' },
867
+ body: JSON.stringify({
868
+ contents,
869
+ generationConfig: { temperature: 0.7 },
870
+ }),
871
+ }
872
+ )
873
+ if (!response.ok) {
874
+ const error = await response.json().catch(() => ({}))
875
+ throw new Error(
876
+ `Gemini Error: ${response.status} - ${
877
+ error.error?.message || 'Check your API key'
878
+ }`
879
+ )
880
+ }
881
+ const data = await response.json()
882
+ const text = data.candidates?.[0]?.content?.parts?.[0]?.text ?? ''
883
+ return { content: text }
884
+ }
885
+
783
886
  return {
784
887
  async predict(
785
888
  system: string,
@@ -794,6 +897,8 @@ export function buildLLMBattery(settings: LLMSettings) {
794
897
  return callOpenAI(system, user, tools, responseFormat)
795
898
  if (preferredProvider === 'anthropic' && hasAnthropic)
796
899
  return callAnthropic(system, user, tools, responseFormat)
900
+ if (preferredProvider === 'gemini' && hasGemini)
901
+ return callGemini(system, user, tools, responseFormat)
797
902
  if (preferredProvider === 'deepseek' && hasDeepseek)
798
903
  return callDeepseek(system, user, tools, responseFormat)
799
904
 
@@ -803,6 +908,7 @@ export function buildLLMBattery(settings: LLMSettings) {
803
908
  custom: 'Custom Endpoint',
804
909
  openai: 'OpenAI',
805
910
  anthropic: 'Anthropic',
911
+ gemini: 'Google Gemini',
806
912
  deepseek: 'Deepseek',
807
913
  }
808
914
  throw new Error(
@@ -815,6 +921,7 @@ export function buildLLMBattery(settings: LLMSettings) {
815
921
  if (hasOpenAI) return callOpenAI(system, user, tools, responseFormat)
816
922
  if (hasAnthropic)
817
923
  return callAnthropic(system, user, tools, responseFormat)
924
+ if (hasGemini) return callGemini(system, user, tools, responseFormat)
818
925
  if (hasDeepseek) return callDeepseek(system, user, tools, responseFormat)
819
926
 
820
927
  throw new Error('No LLM provider configured')