tjs-lang 0.2.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.
Files changed (91) hide show
  1. package/CONTEXT.md +594 -0
  2. package/LICENSE +190 -0
  3. package/README.md +220 -0
  4. package/bin/benchmarks.ts +351 -0
  5. package/bin/dev.ts +205 -0
  6. package/bin/docs.js +170 -0
  7. package/bin/install-cursor.sh +71 -0
  8. package/bin/install-vscode.sh +71 -0
  9. package/bin/select-local-models.d.ts +1 -0
  10. package/bin/select-local-models.js +28 -0
  11. package/bin/select-local-models.ts +31 -0
  12. package/demo/autocomplete.test.ts +232 -0
  13. package/demo/docs.json +186 -0
  14. package/demo/examples.test.ts +598 -0
  15. package/demo/index.html +91 -0
  16. package/demo/src/autocomplete.ts +482 -0
  17. package/demo/src/capabilities.ts +859 -0
  18. package/demo/src/demo-nav.ts +2097 -0
  19. package/demo/src/examples.test.ts +161 -0
  20. package/demo/src/examples.ts +476 -0
  21. package/demo/src/imports.test.ts +196 -0
  22. package/demo/src/imports.ts +421 -0
  23. package/demo/src/index.ts +639 -0
  24. package/demo/src/module-store.ts +635 -0
  25. package/demo/src/module-sw.ts +132 -0
  26. package/demo/src/playground.ts +949 -0
  27. package/demo/src/service-host.ts +389 -0
  28. package/demo/src/settings.ts +440 -0
  29. package/demo/src/style.ts +280 -0
  30. package/demo/src/tjs-playground.ts +1605 -0
  31. package/demo/src/ts-examples.ts +478 -0
  32. package/demo/src/ts-playground.ts +1092 -0
  33. package/demo/static/favicon.svg +30 -0
  34. package/demo/static/photo-1.jpg +0 -0
  35. package/demo/static/photo-2.jpg +0 -0
  36. package/demo/static/texts/ai-history.txt +9 -0
  37. package/demo/static/texts/coffee-origins.txt +9 -0
  38. package/demo/static/texts/renewable-energy.txt +9 -0
  39. package/dist/index.js +256 -0
  40. package/dist/index.js.map +37 -0
  41. package/dist/tjs-batteries.js +4 -0
  42. package/dist/tjs-batteries.js.map +15 -0
  43. package/dist/tjs-full.js +256 -0
  44. package/dist/tjs-full.js.map +37 -0
  45. package/dist/tjs-transpiler.js +220 -0
  46. package/dist/tjs-transpiler.js.map +21 -0
  47. package/dist/tjs-vm.js +4 -0
  48. package/dist/tjs-vm.js.map +14 -0
  49. package/docs/CNAME +1 -0
  50. package/docs/favicon.svg +30 -0
  51. package/docs/index.html +91 -0
  52. package/docs/index.js +10468 -0
  53. package/docs/index.js.map +92 -0
  54. package/docs/photo-1.jpg +0 -0
  55. package/docs/photo-1.webp +0 -0
  56. package/docs/photo-2.jpg +0 -0
  57. package/docs/photo-2.webp +0 -0
  58. package/docs/texts/ai-history.txt +9 -0
  59. package/docs/texts/coffee-origins.txt +9 -0
  60. package/docs/texts/renewable-energy.txt +9 -0
  61. package/docs/tjs-lang.svg +31 -0
  62. package/docs/tosijs-agent.svg +31 -0
  63. package/editors/README.md +325 -0
  64. package/editors/ace/ajs-mode.js +328 -0
  65. package/editors/ace/ajs-mode.ts +269 -0
  66. package/editors/ajs-syntax.ts +212 -0
  67. package/editors/build-grammars.ts +510 -0
  68. package/editors/codemirror/ajs-language.js +287 -0
  69. package/editors/codemirror/ajs-language.ts +1447 -0
  70. package/editors/codemirror/autocomplete.test.ts +531 -0
  71. package/editors/codemirror/component.ts +404 -0
  72. package/editors/monaco/ajs-monarch.js +243 -0
  73. package/editors/monaco/ajs-monarch.ts +225 -0
  74. package/editors/tjs-syntax.ts +115 -0
  75. package/editors/vscode/language-configuration.json +37 -0
  76. package/editors/vscode/package.json +65 -0
  77. package/editors/vscode/syntaxes/ajs-injection.tmLanguage.json +107 -0
  78. package/editors/vscode/syntaxes/ajs.tmLanguage.json +252 -0
  79. package/editors/vscode/syntaxes/tjs.tmLanguage.json +333 -0
  80. package/package.json +83 -0
  81. package/src/cli/commands/check.ts +41 -0
  82. package/src/cli/commands/convert.ts +133 -0
  83. package/src/cli/commands/emit.ts +260 -0
  84. package/src/cli/commands/run.ts +68 -0
  85. package/src/cli/commands/test.ts +194 -0
  86. package/src/cli/commands/types.ts +20 -0
  87. package/src/cli/create-app.ts +236 -0
  88. package/src/cli/playground.ts +250 -0
  89. package/src/cli/tjs.ts +166 -0
  90. package/src/cli/tjsx.ts +160 -0
  91. package/tjs-lang.svg +31 -0
@@ -0,0 +1,598 @@
1
+ /**
2
+ * Tests for playground examples
3
+ *
4
+ * By default, uses LM Studio if available. Set SKIP_LLM_TESTS=1 to use mocks.
5
+ * Vision tests require a vision-capable model.
6
+ */
7
+
8
+ // Provide browser globals (document, window, etc.) for capabilities.ts
9
+ import { GlobalRegistrator } from '@happy-dom/global-registrator'
10
+ GlobalRegistrator.register()
11
+
12
+ import { describe, it, expect, beforeAll, afterAll } from 'bun:test'
13
+
14
+ afterAll(() => {
15
+ GlobalRegistrator.unregister()
16
+ })
17
+
18
+ // Poison pill: detect concurrent execution
19
+ let activeTests = 0
20
+ let maxConcurrentTests = 0
21
+
22
+ function trackTestStart() {
23
+ activeTests++
24
+ maxConcurrentTests = Math.max(maxConcurrentTests, activeTests)
25
+ }
26
+
27
+ function trackTestEnd() {
28
+ activeTests--
29
+ }
30
+ import { examples } from './src/examples'
31
+ import { AgentVM, transpile, coreAtoms, batteryAtoms, tjs } from '../src'
32
+ import { withRetry } from '../src/test-utils'
33
+
34
+ // Helper to detect if example is TJS (uses TJS-specific syntax)
35
+ function isTjsExample(code: string): boolean {
36
+ // TJS uses -> for return types, : for required params with example values
37
+ return /\)\s*->\s*/.test(code) || /mock\s*\{/.test(code)
38
+ }
39
+ import {
40
+ buildLLMCapability,
41
+ buildLLMBattery,
42
+ getLocalModels,
43
+ type LLMSettings,
44
+ } from './src/capabilities'
45
+
46
+ // Use the SAME code path as the playground
47
+ const LM_STUDIO_URL = 'http://localhost:1234/v1'
48
+
49
+ // Test settings that mirror what the playground uses
50
+ const testSettings: LLMSettings = {
51
+ preferredProvider: 'custom',
52
+ customLlmUrl: LM_STUDIO_URL,
53
+ openaiKey: '',
54
+ anthropicKey: '',
55
+ deepseekKey: '',
56
+ }
57
+
58
+ let llmCapability: ReturnType<typeof buildLLMCapability>
59
+ let llmBattery: ReturnType<typeof buildLLMBattery>
60
+ let hasLLM = false
61
+ let hasVision = false
62
+
63
+ // Check if a model ID indicates vision capability (same logic as capabilities.ts)
64
+ function isVisionModel(id: string): boolean {
65
+ return (
66
+ id.includes('-vl') ||
67
+ id.includes('vl-') ||
68
+ id.includes('vision') ||
69
+ id.includes('llava') ||
70
+ id.includes('gemma-3') ||
71
+ id.includes('gemma3') ||
72
+ id.includes('qwen3-vl') ||
73
+ id.includes('qwen2.5-vl') ||
74
+ id.includes('pixtral')
75
+ )
76
+ }
77
+
78
+ // Mock fetch for HTTP APIs (weather, iTunes, GitHub) - these we still mock
79
+ // because they're external APIs, not local LLM
80
+ const createHttpFetchCapability = () => {
81
+ // Load real images from disk for tests
82
+ const fs = require('fs')
83
+ const path = require('path')
84
+ const staticDir = path.join(__dirname, 'static')
85
+ const testDataDir = path.join(__dirname, '..', 'test-data')
86
+
87
+ const loadImage = (dir: string, filename: string): Uint8Array => {
88
+ try {
89
+ const buffer = fs.readFileSync(path.join(dir, filename))
90
+ return new Uint8Array(buffer)
91
+ } catch {
92
+ // Fallback to minimal JPEG header if file not found
93
+ return new Uint8Array([0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10])
94
+ }
95
+ }
96
+
97
+ const mockResponses: Record<string, { body: any; contentType: string }> = {
98
+ '/photo-1.jpg': {
99
+ body: loadImage(staticDir, 'photo-1.jpg'),
100
+ contentType: 'image/jpeg',
101
+ },
102
+ '/photo-2.jpg': {
103
+ body: loadImage(staticDir, 'photo-2.jpg'),
104
+ contentType: 'image/jpeg',
105
+ },
106
+ '/test-shapes.jpg': {
107
+ body: loadImage(testDataDir, 'test-shapes.jpg'),
108
+ contentType: 'image/jpeg',
109
+ },
110
+ '/test-text.jpg': {
111
+ body: loadImage(testDataDir, 'test-text.jpg'),
112
+ contentType: 'image/jpeg',
113
+ },
114
+ }
115
+
116
+ const jsonResponses: Record<string, any> = {
117
+ 'open-meteo.com': {
118
+ current_weather: {
119
+ temperature: 18.5,
120
+ windspeed: 12.3,
121
+ weathercode: 1,
122
+ time: '2024-01-15T12:00',
123
+ },
124
+ },
125
+ 'itunes.apple.com': {
126
+ resultCount: 3,
127
+ results: [
128
+ {
129
+ artistName: 'The Beatles',
130
+ trackName: 'Yesterday',
131
+ collectionName: 'Help!',
132
+ },
133
+ {
134
+ artistName: 'The Beatles',
135
+ trackName: 'Yesterday',
136
+ collectionName: '1',
137
+ },
138
+ {
139
+ artistName: 'Frank Sinatra',
140
+ trackName: 'Yesterday',
141
+ collectionName: 'My Way',
142
+ },
143
+ ],
144
+ },
145
+ 'api.github.com': {
146
+ total_count: 2,
147
+ items: [
148
+ {
149
+ full_name: 'user/tosijs',
150
+ stargazers_count: 100,
151
+ description: 'A great library',
152
+ },
153
+ {
154
+ full_name: 'other/tosijs-demo',
155
+ stargazers_count: 50,
156
+ description: 'Demo project',
157
+ },
158
+ ],
159
+ },
160
+ }
161
+
162
+ return async (url: string, options?: any) => {
163
+ let response: Response | undefined
164
+
165
+ for (const [path, data] of Object.entries(mockResponses)) {
166
+ if (url.endsWith(path)) {
167
+ response = new Response(data.body, {
168
+ headers: { 'content-type': data.contentType },
169
+ })
170
+ break
171
+ }
172
+ }
173
+
174
+ if (!response) {
175
+ for (const [domain, jsonData] of Object.entries(jsonResponses)) {
176
+ if (url.includes(domain)) {
177
+ response = new Response(JSON.stringify(jsonData), {
178
+ headers: { 'content-type': 'application/json' },
179
+ })
180
+ break
181
+ }
182
+ }
183
+ }
184
+
185
+ if (!response && url.includes('/texts/')) {
186
+ response = new Response(
187
+ 'This is sample text content for testing the summarizer example.',
188
+ { headers: { 'content-type': 'text/plain' } }
189
+ )
190
+ }
191
+
192
+ if (!response) {
193
+ throw new Error(`Unmocked URL: ${url}`)
194
+ }
195
+
196
+ // Same dataUrl handling as playground.ts
197
+ if (options?.responseType === 'dataUrl') {
198
+ const buffer = await response.arrayBuffer()
199
+ const bytes = new Uint8Array(buffer)
200
+ let binary = ''
201
+ for (let i = 0; i < bytes.length; i++) {
202
+ binary += String.fromCharCode(bytes[i])
203
+ }
204
+ const base64 = btoa(binary)
205
+ const ct =
206
+ response.headers.get('content-type') || 'application/octet-stream'
207
+ return `data:${ct};base64,${base64}`
208
+ }
209
+
210
+ const contentType = response.headers.get('content-type')
211
+ if (contentType?.includes('application/json')) {
212
+ return response.json()
213
+ }
214
+ return response.text()
215
+ }
216
+ }
217
+
218
+ const httpFetch = createHttpFetchCapability()
219
+
220
+ // Simple mock LLM for when LM Studio isn't available
221
+ const mockLLM = {
222
+ predict: async (prompt: string) => {
223
+ if (prompt.includes('capital of France')) return 'Paris'
224
+ if (prompt.includes('Summarize'))
225
+ return 'This is a summary of the provided text.'
226
+ if (prompt.includes('Extract person info')) {
227
+ return JSON.stringify({
228
+ name: 'John Smith',
229
+ age: 35,
230
+ occupation: 'software engineer',
231
+ location: 'San Francisco',
232
+ hobbies: ['hiking', 'photography'],
233
+ })
234
+ }
235
+ if (prompt.includes('cover versions') || prompt.includes('NOT by')) {
236
+ return JSON.stringify({
237
+ covers: [
238
+ { track: 'Yesterday', artist: 'Frank Sinatra', album: 'My Way' },
239
+ ],
240
+ })
241
+ }
242
+ if (prompt.includes('Extract the math expression')) return '23 * 47 + 156'
243
+ if (prompt.includes('Calculate:')) return '1237'
244
+ if (prompt.includes('friendly response')) return 'The answer is 1,237!'
245
+ if (prompt.includes('research agent'))
246
+ return '1. Point one\n2. Point two\n3. Point three'
247
+ if (prompt.includes('writer agent'))
248
+ return 'This is a well-written paragraph.'
249
+ if (prompt.includes('editor agent'))
250
+ return 'Suggestion: Add more detail.\n\nImproved: Better paragraph.'
251
+ // LLM Code Solver - generate valid AsyncJS code (Fibonacci)
252
+ if (prompt.includes('function called "solve"')) {
253
+ return `function solve() {
254
+ let a = 0
255
+ let b = 1
256
+ let i = 0
257
+ while (i < 10) {
258
+ let temp = a + b
259
+ a = b
260
+ b = temp
261
+ i = i + 1
262
+ }
263
+ return { result: a }
264
+ }`
265
+ }
266
+ // LLM Code Generator - return code without execution
267
+ if (
268
+ prompt.includes('Write an AsyncJS function') &&
269
+ prompt.includes('factorial')
270
+ ) {
271
+ return JSON.stringify({
272
+ code: `function factorial(n: 5) {
273
+ let result = 1
274
+ let i = n
275
+ while (i > 1) {
276
+ result = result * i
277
+ i = i - 1
278
+ }
279
+ return { result }
280
+ }`,
281
+ description: 'Calculates the factorial of n using iteration.',
282
+ })
283
+ }
284
+ return 'Mock LLM response'
285
+ },
286
+ }
287
+
288
+ // Mock LLM battery wrapper (for when LM Studio isn't available)
289
+ const mockLLMBattery = {
290
+ predict: async (
291
+ system: string,
292
+ user: any,
293
+ tools?: any[],
294
+ responseFormat?: any
295
+ ) => {
296
+ const prompt = typeof user === 'string' ? user : user.text
297
+ const content = await mockLLM.predict(prompt)
298
+ return { content }
299
+ },
300
+ embed: async () => {
301
+ throw new Error('Embedding not available in mock')
302
+ },
303
+ }
304
+
305
+ beforeAll(async () => {
306
+ // Skip LLM if SKIP_LLM_TESTS is set
307
+ if (process.env.SKIP_LLM_TESTS) {
308
+ console.log('SKIP_LLM_TESTS set, using mocks')
309
+ hasLLM = false
310
+ return
311
+ }
312
+
313
+ // Use the SAME builders as the playground
314
+ llmCapability = buildLLMCapability(testSettings)
315
+ llmBattery = buildLLMBattery(testSettings)
316
+ hasLLM = llmCapability !== null
317
+
318
+ if (hasLLM) {
319
+ // Check for vision models using the same getLocalModels function
320
+ try {
321
+ const models = await getLocalModels(LM_STUDIO_URL)
322
+ const visionModels = models.filter(isVisionModel)
323
+ console.log(
324
+ `LM Studio: ${models.length} models, ${visionModels.length} vision-capable`
325
+ )
326
+ if (visionModels.length > 0) {
327
+ console.log(`Vision models: ${visionModels.join(', ')}`)
328
+ // Actually test if vision works by sending a minimal request
329
+ for (const model of visionModels) {
330
+ console.log(`🔍 Testing vision capability: ${model}`)
331
+ try {
332
+ const testResponse = await fetch(
333
+ `${LM_STUDIO_URL}/chat/completions`,
334
+ {
335
+ method: 'POST',
336
+ headers: { 'Content-Type': 'application/json' },
337
+ body: JSON.stringify({
338
+ model,
339
+ messages: [
340
+ {
341
+ role: 'user',
342
+ content: [
343
+ {
344
+ type: 'text',
345
+ text: 'test',
346
+ },
347
+ {
348
+ type: 'image_url',
349
+ image_url: {
350
+ url: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
351
+ },
352
+ },
353
+ ],
354
+ },
355
+ ],
356
+ max_tokens: 1,
357
+ }),
358
+ }
359
+ )
360
+ if (testResponse.ok) {
361
+ hasVision = true
362
+ console.log(`✅ Vision test for ${model}: works`)
363
+ break
364
+ } else {
365
+ const errorData = await testResponse.json().catch(() => ({}))
366
+ console.log(
367
+ `🧪 Vision test for ${model}: HTTP ${
368
+ testResponse.status
369
+ } - ${JSON.stringify(errorData)}`
370
+ )
371
+ }
372
+ } catch (e: any) {
373
+ console.log(`🧪 Vision test for ${model}: ${e.message}`)
374
+ }
375
+ }
376
+ }
377
+ } catch (e) {
378
+ console.log('Could not fetch models:', e)
379
+ }
380
+ } else {
381
+ console.log('No LLM configured, using mocks')
382
+ }
383
+ }, 30000)
384
+
385
+ describe('Playground Examples', () => {
386
+ const vm = new AgentVM({ ...coreAtoms, ...batteryAtoms })
387
+
388
+ for (const example of examples) {
389
+ const isVision = example.name.startsWith('Vision:')
390
+ const shouldFail =
391
+ example.name === 'Fuel Exhaustion' || example.name === 'Fuel Limits'
392
+ // Examples that generate and run code need retry due to LLM variability
393
+ const needsRetry = example.code.includes('runCode(')
394
+ // TJS examples use the TJS transpiler, not AgentJS
395
+ const isTjs = isTjsExample(example.code)
396
+
397
+ it(`${example.name} - transpiles correctly`, () => {
398
+ if (isTjs) {
399
+ // TJS examples use the TJS transpiler
400
+ const result = tjs(example.code)
401
+ expect(result.code).toBeDefined()
402
+ expect(result.metadata).toBeDefined()
403
+ } else {
404
+ const result = transpile(example.code)
405
+ expect(result.ast).toBeDefined()
406
+ expect(result.error).toBeUndefined()
407
+ }
408
+ })
409
+
410
+ if (shouldFail) {
411
+ it(`${example.name} - runs out of fuel as expected`, async () => {
412
+ const result = transpile(example.code)
413
+ const runResult = await vm.run(result.ast, {}, { fuel: 1000 })
414
+ expect(runResult.error).toBeDefined()
415
+ const errorMsg =
416
+ typeof runResult.error === 'string'
417
+ ? runResult.error
418
+ : runResult.error?.message || JSON.stringify(runResult.error)
419
+ expect(errorMsg.toLowerCase()).toContain('fuel')
420
+ })
421
+ } else if (isVision) {
422
+ // Vision tests - check hasVision at runtime, not registration time
423
+ it(`${example.name} - runs successfully`, async () => {
424
+ if (!hasVision) {
425
+ console.log(`Skipping ${example.name}: no vision model available`)
426
+ return // Skip gracefully at runtime
427
+ }
428
+
429
+ trackTestStart()
430
+ try {
431
+ const result = transpile(example.code)
432
+
433
+ const args: Record<string, any> = {}
434
+ if (result.signature?.parameters) {
435
+ for (const [key, param] of Object.entries(
436
+ result.signature.parameters
437
+ )) {
438
+ if ('default' in param) {
439
+ args[key] = param.default
440
+ }
441
+ }
442
+ }
443
+
444
+ // Override with small test images for faster tests
445
+ if (example.name === 'Vision: OCR') {
446
+ args.imageUrl = '/test-text.jpg'
447
+ } else if (example.name === 'Vision: Classification') {
448
+ args.imageUrl = '/test-shapes.jpg'
449
+ }
450
+
451
+ // Use the SAME capabilities as the playground
452
+ const runResult = await vm.run(result.ast, args, {
453
+ fuel: 100000,
454
+ capabilities: {
455
+ fetch: httpFetch,
456
+ llm: llmCapability || mockLLM,
457
+ llmBattery: llmBattery || mockLLMBattery,
458
+ code: {
459
+ transpile: (source: string) => transpile(source).ast,
460
+ },
461
+ },
462
+ })
463
+
464
+ expect(runResult.error).toBeUndefined()
465
+ expect(runResult.result).toBeDefined()
466
+ } finally {
467
+ trackTestEnd()
468
+ }
469
+ }, 120000)
470
+ } else if (needsRetry) {
471
+ // Examples that use runCode need retry due to LLM variability
472
+ it(`${example.name} - runs successfully`, async () => {
473
+ await withRetry(async () => {
474
+ trackTestStart()
475
+ try {
476
+ const result = transpile(example.code)
477
+
478
+ const args: Record<string, any> = {}
479
+ if (result.signature?.parameters) {
480
+ for (const [key, param] of Object.entries(
481
+ result.signature.parameters
482
+ )) {
483
+ if ('default' in param) {
484
+ args[key] = param.default
485
+ }
486
+ }
487
+ }
488
+
489
+ const runResult = await vm.run(result.ast, args, {
490
+ fuel: 100000,
491
+ capabilities: {
492
+ fetch: httpFetch,
493
+ llm: llmCapability || mockLLM,
494
+ llmBattery: llmBattery || mockLLMBattery,
495
+ code: {
496
+ transpile: (source: string) => transpile(source).ast,
497
+ },
498
+ },
499
+ })
500
+
501
+ if (runResult.error) {
502
+ throw new Error(
503
+ runResult.error.message || String(runResult.error)
504
+ )
505
+ }
506
+ expect(runResult.result).toBeDefined()
507
+ } finally {
508
+ trackTestEnd()
509
+ }
510
+ })
511
+ }, 360000) // 3 attempts * 120s each
512
+ } else if (isTjs) {
513
+ // TJS examples run via direct JS execution
514
+ it(`${example.name} - runs successfully`, async () => {
515
+ trackTestStart()
516
+ try {
517
+ const result = tjs(example.code)
518
+ // Execute the transpiled JS code
519
+ const fn = new Function(
520
+ result.code + '\nreturn typeof greet === "function" ? greet : null'
521
+ )
522
+ const greetFn = fn()
523
+ if (greetFn) {
524
+ const output = greetFn('Test', 1)
525
+ expect(output).toContain('Hello')
526
+ }
527
+ } finally {
528
+ trackTestEnd()
529
+ }
530
+ }, 10000)
531
+ } else {
532
+ it(`${example.name} - runs successfully`, async () => {
533
+ trackTestStart()
534
+ try {
535
+ const result = transpile(example.code)
536
+
537
+ const args: Record<string, any> = {}
538
+ if (result.signature?.parameters) {
539
+ for (const [key, param] of Object.entries(
540
+ result.signature.parameters
541
+ )) {
542
+ if ('default' in param) {
543
+ args[key] = param.default
544
+ }
545
+ }
546
+ }
547
+
548
+ // Use the SAME capabilities as the playground
549
+ const runResult = await vm.run(result.ast, args, {
550
+ fuel: 100000, // High fuel for real LLM calls
551
+ capabilities: {
552
+ fetch: httpFetch,
553
+ llm: llmCapability || mockLLM,
554
+ llmBattery: llmBattery || mockLLMBattery,
555
+ code: {
556
+ transpile: (source: string) => transpile(source).ast,
557
+ },
558
+ },
559
+ })
560
+
561
+ expect(runResult.error).toBeUndefined()
562
+ expect(runResult.result).toBeDefined()
563
+ } finally {
564
+ trackTestEnd()
565
+ }
566
+ }, 120000) // Long timeout for LM Studio
567
+ }
568
+ }
569
+
570
+ // Poison pill: fail if tests ran concurrently
571
+ it('tests must run sequentially (use --max-concurrency 1)', () => {
572
+ expect(maxConcurrentTests).toBeLessThanOrEqual(1)
573
+ })
574
+ })
575
+
576
+ describe('Example Code Quality', () => {
577
+ it('all examples have unique names', () => {
578
+ const names = examples.map((e) => e.name)
579
+ expect(new Set(names).size).toBe(names.length)
580
+ })
581
+
582
+ it('all examples have descriptions', () => {
583
+ for (const example of examples) {
584
+ expect(example.description.length).toBeGreaterThan(5)
585
+ }
586
+ })
587
+
588
+ it('LLM examples are marked with requiresApi', () => {
589
+ for (const example of examples) {
590
+ if (
591
+ example.code.includes('llmPredict') ||
592
+ example.code.includes('llmVision')
593
+ ) {
594
+ expect(example.requiresApi).toBe(true)
595
+ }
596
+ }
597
+ })
598
+ })
@@ -0,0 +1,91 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>tjs-lang | Secure Agent Runtime</title>
7
+ <meta
8
+ name="description"
9
+ content="A type-safe virtual machine for executing untrusted agent code safely. Build, run, and deploy AI agents with confidence."
10
+ />
11
+
12
+ <!-- Favicon -->
13
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
14
+
15
+ <!-- Preload critical resources -->
16
+ <link rel="modulepreload" href="/index.js" />
17
+
18
+ <style>
19
+ /* Critical CSS - prevents flash of unstyled content */
20
+ :root {
21
+ --brand-color: #3d4a6b;
22
+ --brand-text-color: white;
23
+ --background: #ffffff;
24
+ --text-color: #1f2937;
25
+ --spacing: 10px;
26
+ }
27
+
28
+ * {
29
+ box-sizing: border-box;
30
+ }
31
+
32
+ html,
33
+ body {
34
+ margin: 0;
35
+ padding: 0;
36
+ height: 100%;
37
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
38
+ Roboto, sans-serif;
39
+ background: var(--background);
40
+ color: var(--text-color);
41
+ }
42
+
43
+ body {
44
+ display: flex;
45
+ flex-direction: column;
46
+ }
47
+
48
+ main {
49
+ flex: 1;
50
+ display: flex;
51
+ flex-direction: column;
52
+ overflow: hidden;
53
+ }
54
+
55
+ /* Loading state */
56
+ .loading {
57
+ display: flex;
58
+ align-items: center;
59
+ justify-content: center;
60
+ height: 100%;
61
+ font-size: 1.2em;
62
+ color: var(--text-color);
63
+ opacity: 0.6;
64
+ }
65
+
66
+ .loading::after {
67
+ content: '';
68
+ width: 20px;
69
+ height: 20px;
70
+ margin-left: 10px;
71
+ border: 2px solid var(--brand-color);
72
+ border-top-color: transparent;
73
+ border-radius: 50%;
74
+ animation: spin 1s linear infinite;
75
+ }
76
+
77
+ @keyframes spin {
78
+ to {
79
+ transform: rotate(360deg);
80
+ }
81
+ }
82
+ </style>
83
+ </head>
84
+ <body>
85
+ <main>
86
+ <div class="loading">Loading tjs-lang</div>
87
+ </main>
88
+
89
+ <script type="module" src="/index.js"></script>
90
+ </body>
91
+ </html>