File size: 27,953 Bytes
3374e90
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
# BEX Engine v6 β€” WASM Plugin Engine

A **production-grade WASM/Wasmtime Component Model** plugin engine built in Rust. BEX enables sandboxed, deterministic plugin execution using WebAssembly components with WIT interface definitions. Designed for media streaming, metadata, and content provider applications with a **Pure C ABI** integration layer β€” no cxx dependency.

## What's New in v6

This version incorporates all fixes from the QuickJS Integration Plan v3 review:

### Critical Bug Fixes
- **`eval-js` now accepts `input` parameter** β€” user data is safely injected as a global variable instead of being concatenated into JS source code, eliminating code injection vulnerabilities
- **`call-js-fn` now accepts `fn-source` parameter** β€” functions are registered and auto-re-registered when source changes, eliminating the broken `track_functions` text parser
- **`TextEncoder`/`TextDecoder` are now spec-correct UTF-8** β€” properly handles CJK characters, emoji, and all Unicode code points
- **`crypto.getRandomValues` uses Rust-backed CSPRNG** β€” `rand::thread_rng()` instead of `Math.random()`
- **`crypto.subtle` is now fully implemented** β€” SHA-1/256/384/512, AES-CBC encrypt/decrypt, HMAC-SHA256/512, PBKDF2, all in pure JS with immediate-resolved Promises
- **`args_json` is passed as a string, not eval'd** β€” eliminates JS injection attack vector
- **Pool dispatch is non-blocking** β€” uses `try_send` instead of blocking `send`, returns `PoolBusy` instead of hanging Wasmtime threads
- **Pool shutdown uses SeqCst ordering** β€” fixes race condition on ARM/Apple Silicon

### Missing Features Now Implemented
- **`console.log` routes to Rust tracing** β€” no longer silently dropped
- **`setTimeout`/`setInterval` call callbacks synchronously** β€” no longer silently skipped
- **`clear-js-fn` WIT function** β€” allows unregistering JS functions when cipher rotates
- **`JsPoolConfig` wired into `EngineConfig`** β€” JS pool settings are configurable from engine config

### Design Improvements
- **Idle context eviction throttled to 30-second intervals** β€” reduces overhead from checking on every loop iteration
- **`apply_fn` helper removed** β€” `func.call((args_json,))` used directly
- **`max_stack_bytes` configurable via `JsPoolConfig`** β€” default 512KB
- **All compiler warnings fixed** β€” unused imports, dead code, unused variables
- **`atob`/`btoa` are Rust-backed** β€” correct Latin-1 handling via base64 crate
- **Additional polyfills** β€” `URL`, `URLSearchParams`, `performance.now()`, `structuredClone`, `navigator`, `location`, `queueMicrotask`

## Architecture

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    C++ Application                           β”‚
β”‚                  (via Pure C ABI)                             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚  BexResultCallback (function pointer)                β”‚   β”‚
β”‚  β”‚  bex_submit_*() β†’ request_id                         β”‚   β”‚
β”‚  β”‚  callback(user_data, req_id, success, payload, len)  β”‚   β”‚
β”‚  β”‚  bex_cancel_request(request_id)                       β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                BexRuntime (async callback-driven)             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  Scheduler   β”‚  β”‚  Cancellationβ”‚  β”‚  Tokio Runtime    β”‚  β”‚
β”‚  β”‚  (3 lanes)   β”‚  β”‚  Tokens      β”‚  β”‚  (async tasks)    β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚         β”‚     BEX Engine (Wasmtime)            β”‚            β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚  Wasmtime    β”‚  β”‚  Host    β”‚  β”‚  Plugin Registry       β”‚ β”‚
β”‚  β”‚  Component   β”‚  β”‚  APIs    β”‚  β”‚  (circuit breaker)     β”‚ β”‚
β”‚  β”‚  Model       β”‚  β”‚          β”‚  β”‚                        β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚         β”‚              β”‚                                     β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”            β”‚
β”‚  β”‚  WASM        β”‚  β”‚  HTTP    β”‚  β”‚  Redb DB     β”‚            β”‚
β”‚  β”‚  Components  β”‚  β”‚  (reqwest)β”‚  β”‚  (storage)   β”‚            β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜            β”‚
β”‚                                                               β”‚
β”‚  FlatBuffers (wire format)  β”‚  JSON (CLI/debug)              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

### Core Design Principles

1. **WASM-Only**: All plugins run as WebAssembly components via Wasmtime β€” no native plugins, no dual-mode engine
2. **Component Model**: Uses Wasmtime's Component Model with WIT interface definitions for type-safe host-guest communication
3. **Callback-Driven**: C++ backend submits requests via `bex_submit_*()`, receives results via a C function pointer callback (`BexResultCallback`) invoked from a background Tokio thread. No polling, no event queue, no drain pattern.
4. **Self-Describing IDs**: The engine treats IDs as opaque strings β€” it does not know or care what they mean. Each plugin defines its own ID format and parses IDs internally. For example, `get_servers` takes a single `id` parameter; the plugin parses `slug$ep=1$sub=1$dub=0` because it knows its own encoding scheme. There is no `episode_id` parameter anywhere in the engine.
5. **Sandboxed Execution**: Fuel-based metering, stack limits, epoch-based timeouts, and capability-based host APIs
6. **Lane-Based Scheduling**: Three concurrency lanes β€” Control (1), User (4), Background (2) β€” with semaphores, plus global WASM (4) and HTTP (8) permit limits
7. **Cancellation**: Each request gets a `CancellationToken`; cancel via `bex_cancel_request()`
8. **Pure C ABI**: The Rust engine exports `extern "C"` functions matching `bex_engine.h`. No cxx, no bridge codegen, no special build steps. Link the Rust static/shared library natively.

## Workspace Structure

```
bex-engine/
β”œβ”€β”€ crates/
β”‚   β”œβ”€β”€ bex-types/              # Shared types (Manifest, Capabilities, BexError, PluginInfo, etc.)
β”‚   β”œβ”€β”€ bex-pkg/                # BEX package format (pack/unpack/verify with zstd+CRC32+SHA256)
β”‚   β”œβ”€β”€ bex-db/                 # Redb-backed storage (KV, secrets, plugins, WASM blobs)
β”‚   β”œβ”€β”€ bex-wire/               # FlatBuffer wire-format types + builders
β”‚   β”œβ”€β”€ bex-core/               # Core engine (Wasmtime, host APIs, linker, compile cache)
β”‚   β”œβ”€β”€ bex-runtime/            # Async runtime (scheduler, cancellation, Pure C FFI via ffi.rs)
β”‚   └── bex-cli/                # Rust CLI tool (clap-based)
β”œβ”€β”€ plugins/
β”‚   β”œβ”€β”€ bex-gogoanime/          # GogoAnime/Anitaku streaming plugin
β”‚   β”œβ”€β”€ bex-kaianime/           # KaiAnime streaming plugin
β”‚   β”œβ”€β”€ bex-hianime/            # HiAnime streaming plugin
β”‚   β”œβ”€β”€ bex-imdb/               # IMDb metadata plugin
β”‚   └── bex-kisskh/             # KissKH streaming plugin
β”œβ”€β”€ cpp-cli/
β”‚   β”œβ”€β”€ bex_engine.h            # Pure C ABI header (the FFI boundary)
β”‚   β”œβ”€β”€ bexcli.cpp              # C++ CLI tool (uses promise/future + C callbacks)
β”‚   β”œβ”€β”€ CMakeLists.txt          # CMake build configuration (links libbex_runtime natively)
β”‚   └── wire_gen/               # Generated FlatBuffer C++ headers
β”œβ”€β”€ wit/
β”‚   └── plugin.wit              # Master WIT interface definitions
β”œβ”€β”€ dist/                       # Built WASM components and manifests
β”œβ”€β”€ build-plugins.sh            # Build, convert, and pack all plugins
└── Cargo.toml                  # Workspace root
```

## Pure C ABI

The Rust engine exposes a **Pure C ABI** through `bex_engine.h`. No cxx, no code generation, no bridge crate. The Rust library compiles as both `cdylib` and `staticlib`, and CMake links it natively.

### How It Works

1. C++ calls `bex_submit_search(engine, plugin_id, query, callback, user_data)`
2. Rust spawns a Tokio task that does the work
3. On completion, Rust invokes `callback(user_data, request_id, success, payload, len)` from the Tokio background thread
4. C++ receives the result in the callback and can parse/copy the payload before the callback returns

### C++ Integration Example

```cpp
#include "bex_engine.h"

// 1. Define a callback handler
extern "C" void on_result(void* user_data, uint64_t req_id,
                          bool success, const uint8_t* payload, size_t len) {
    auto* promise = static_cast<std::promise<std::string>*>(user_data);
    if (success) {
        promise->set_value(std::string(reinterpret_cast<const char*>(payload), len));
    } else {
        promise->set_exception(std::make_exception_ptr(
            std::runtime_error(std::string(reinterpret_cast<const char*>(payload), len))));
    }
}

// 2. Create engine
BexEngine* engine = bex_engine_new("/path/to/data");

// 3. Submit async requests β€” returns request_id immediately
std::promise<std::string> promise;
uint64_t req1 = bex_submit_home(engine, "bex.gogoanime", on_result, &promise);
uint64_t req2 = bex_submit_search(engine, "bex.gogoanime", "one piece", on_result, &promise);
uint64_t req3 = bex_submit_info(engine, "bex.gogoanime", "one-piece", on_result, &promise);
uint64_t req4 = bex_submit_servers(engine, "bex.gogoanime", "one-piece$ep=1$sub=1$dub=0",
                                    on_result, &promise);

// 4. Wait for results (or use the callback in your event loop)
std::string result = promise.get_future().get();

// 5. Cancel a request
bex_cancel_request(engine, req2);

// 6. Plugin management (synchronous)
bex_engine_install(engine, "/path/to/plugin.bex");
bex_engine_uninstall(engine, "bex.gogoanime");
BexPluginInfoList plugins = bex_engine_list_plugins(engine);
bex_plugin_info_list_free(plugins);

// 7. API key management (synchronous)
bex_engine_secret_set(engine, "bex.imdb", "api-key", "your-key");

// 8. Shutdown
bex_engine_free(engine);
```

### Full C ABI Function Reference

#### Lifecycle

| Function | Description |
|----------|-------------|
| `bex_engine_new(data_dir)` | Create engine β†’ `BexEngine*` |
| `bex_engine_free(engine)` | Graceful shutdown and free |

#### Plugin Management (synchronous)

| Function | Description |
|----------|-------------|
| `bex_engine_install(engine, path)` | Install .bex plugin package β†’ `int` |
| `bex_engine_uninstall(engine, id)` | Uninstall plugin by ID β†’ `int` |
| `bex_engine_list_plugins(engine)` | List installed plugins β†’ `BexPluginInfoList` |
| `bex_engine_plugin_info(engine, id, out)` | Get detailed plugin info β†’ `int` |
| `bex_engine_enable(engine, id)` | Enable plugin β†’ `int` |
| `bex_engine_disable(engine, id)` | Disable plugin β†’ `int` |
| `bex_plugin_info_list_free(list)` | Free plugin list |
| `bex_plugin_info_free(info)` | Free plugin info struct |

#### Secret / API Key Management (synchronous)

| Function | Description |
|----------|-------------|
| `bex_engine_secret_set(engine, plugin_id, key, value)` | Store a secret β†’ `int` |
| `bex_engine_secret_get(engine, plugin_id, key, out_buf, out_buf_len)` | Retrieve a secret value β†’ `int` |
| `bex_engine_secret_delete(engine, plugin_id, key)` | Delete a secret β†’ `int` |
| `bex_engine_secret_keys(engine, plugin_id)` | List key names (comma-separated) β†’ `char*` |
| `bex_string_free(s)` | Free a string returned by the engine |

#### Async Operations (callback-driven)

| Function | Description |
|----------|-------------|
| `bex_submit_home(engine, plugin_id, callback, user_data)` | Submit home request β†’ `uint64_t` request_id |
| `bex_submit_search(engine, plugin_id, query, callback, user_data)` | Submit search request β†’ `uint64_t` request_id |
| `bex_submit_info(engine, plugin_id, media_id, callback, user_data)` | Submit info request β†’ `uint64_t` request_id |
| `bex_submit_servers(engine, plugin_id, id, callback, user_data)` | Submit servers request β†’ `uint64_t` request_id |
| `bex_submit_stream(engine, plugin_id, server_json, callback, user_data)` | Submit stream resolve β†’ `uint64_t` request_id |

#### Cancellation & Stats

| Function | Description |
|----------|-------------|
| `bex_cancel_request(engine, request_id)` | Cancel a pending request β†’ `bool` |
| `bex_engine_stats(engine)` | Get engine stats (JSON) β†’ `char*` |
| `bex_engine_last_error(engine)` | Get last error message β†’ `char*` |

## Quick Start

### Prerequisites

- Rust toolchain (stable)
- `wasm-tools` CLI: `cargo install wasm-tools-cli`
- C++17 compiler (for C++ CLI / integration)
- CMake 3.16+ (for C++ CLI / integration)

### Build the Engine

```bash
# Build the Rust engine and CLI
cargo build --release

# Build and pack all plugins (compile β†’ component convert β†’ pack)
bash build-plugins.sh

# Build the C++ CLI with CMake
cd cpp-cli && mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j$(nproc)
```

### Build a Single Plugin (Manual)

Plugins are built in three steps: compile to WASM, convert to a component, then pack.

```bash
# Step 1: Compile to wasm32-wasip1
cargo build -p bex-gogoanime --target wasm32-wasip1 --release

# Step 2: Convert to a WASM Component (requires wasm-tools + WASI adapter)
wasm-tools component new \
    target/wasm32-wasip1/release/bex_gogoanime.wasm \
    -o target/components/bex-gogoanime.component.wasm \
    --adapt ~/.cargo/registry/src/index.crates.io-*/wasi-preview1-component-adapter-provider-*/artefacts/wasi_snapshot_preview1.reactor.wasm

# Step 3: Pack into a .bex package
cargo run -p bex-cli --release -- pack \
    dist/bex-gogoanime.yaml \
    target/components/bex-gogoanime.component.wasm \
    dist/bex-gogoanime.bex
```

### Install and Use

```bash
# Install a plugin
cargo run -p bex-cli --release -- install dist/bex-gogoanime.bex

# List installed plugins
cargo run -p bex-cli --release -- list

# Get detailed plugin info
cargo run -p bex-cli --release -- plugin-info bex.gogoanime
```

## Plugin Management

### Rust CLI

```bash
# Install / uninstall
bex install dist/bex-gogoanime.bex
bex uninstall bex.gogoanime

# List installed plugins
bex list

# Show detailed plugin info (capabilities, enabled state, etc.)
bex plugin-info bex.gogoanime

# Enable / disable
bex enable bex.gogoanime
bex disable bex.gogoanime

# Inspect a .bex package without installing
bex inspect dist/bex-gogoanime.bex

# Engine stats
bex stats
```

### C++ CLI

```bash
# Install / uninstall
./bexcli install dist/bex-gogoanime.bex
./bexcli uninstall bex.gogoanime

# List plugins (with capabilities column)
./bexcli list

# Detailed plugin info (includes API keys list)
./bexcli info-plugin bex.gogoanime

# Enable / disable
./bexcli enable bex.gogoanime
./bexcli disable bex.gogoanime

# Engine stats
./bexcli stats
```

## API Key / Secret Management

The engine provides per-plugin secret storage backed by Redb. Secrets are scoped to a plugin ID and are accessible to the plugin at runtime via the `secrets` WIT interface. This is the mechanism for storing API keys, tokens, and other credentials that plugins need.

### Rust CLI

```bash
# Set an API key
bex set-key bex.imdb api-key "your-api-key-here"

# Get an API key value
bex get-key bex.imdb api-key

# Delete an API key
bex delete-key bex.imdb api-key

# List all keys for a plugin
bex list-keys bex.imdb
```

### C++ CLI

```bash
# Set an API key
./bexcli set-key bex.imdb api-key "your-api-key-here"

# Get an API key value
./bexcli get-key bex.imdb api-key

# Delete an API key
./bexcli delete-key bex.imdb api-key

# List all keys for a plugin
./bexcli list-keys bex.imdb
```

### C API

```c
// Store a secret
bex_engine_secret_set(engine, "bex.imdb", "api-key", "your-key");

// Retrieve a secret
char buf[4096];
size_t buf_len = sizeof(buf);
bex_engine_secret_get(engine, "bex.imdb", "api-key", buf, &buf_len);

// Delete a secret
bex_engine_secret_delete(engine, "bex.imdb", "api-key");

// List all secret key names (comma-separated, caller frees with bex_string_free)
char* keys = bex_engine_secret_keys(engine, "bex.imdb");
bex_string_free(keys);
```

### Plugin Access (WIT)

Inside a plugin, secrets are accessed read-only through the `secrets` host interface:

```rust
use bindings::bex::plugin::secrets;

fn get_api_key() -> Option<String> {
    secrets::get("api-key")
}
```

Secrets that a plugin expects are declared in the manifest:

```yaml
secrets:
  - api-key
  - tmdb-token
```

## Self-Describing IDs

Self-describing IDs are the core design pattern for how the BEX engine handles typed identifiers. The engine itself treats all IDs as opaque strings β€” it does not parse, validate, or interpret them. Only the plugin knows what its IDs mean and how to decode them.

This means:

- **`get_servers` takes a single `id` parameter** β€” there is no separate `episode_id` parameter
- **The engine never parses IDs** β€” it passes them straight through to the plugin
- **Each plugin defines its own ID encoding** β€” different plugins can use entirely different schemes
- **IDs are portable** β€” they can be stored, serialized, and passed between systems without the engine needing to understand them

### Example: GogoAnime Episode IDs

The GogoAnime plugin encodes episode context directly in the ID:

```
{slug}$ep={episode_number}$sub={0|1}$dub={0|1}
```

| ID | Meaning |
|----|---------|
| `one-piece$ep=1$sub=1$dub=0` | One Piece episode 1, subbed |
| `jujutsu-kaisen-tv$ep=24$sub=0$dub=1` | Jujutsu Kaisen episode 24, dubbed |

When `get_servers` is called with this ID, the GogoAnime plugin splits on `$` and parses each key-value pair to determine the slug, episode number, and sub/dub flags. The engine never does this parsing β€” it just passes the string through.

### Example: IMDb Media IDs

The IMDb plugin might use a different scheme entirely (e.g., `tt1234567`), and the engine works equally well because it does not interpret the ID.

### Usage

```bash
# The ID is self-describing β€” pass it directly
bex servers bex.gogoanime 'one-piece$ep=1$sub=1$dub=0'

# C++ CLI works the same way
./bexcli servers bex.gogoanime 'one-piece$ep=1$sub=1$dub=0'
```

## WIT Interface Definitions

### Host-Provided APIs (imports β€” plugins call these)

| Interface | Functions | Description |
|-----------|-----------|-------------|
| `http` | `send-request` | HTTP client with caching, redirect control, size limits |
| `kv` | `set`, `get`, `remove`, `keys` | Scoped key-value storage |
| `secrets` | `get` | Read-only secret/API key access |
| `log` | `write` | Structured logging through host |
| `clock` | `now-ms`, `monotonic` | Time access |
| `rng` | `bytes` | Secure random bytes |
| `js` | `eval-js`, `eval-js-opts`, `call-js-fn`, `clear-js-fn` | QuickJS sandbox β€” safe JS eval, function call, and cleanup |

### Plugin-Provided APIs (exports β€” host calls these)

| Function | Description |
|----------|-------------|
| `get-home` | Get home page sections |
| `get-category` | Browse by category with pagination |
| `search` | Search media content |
| `get-info` | Get detailed media info with episodes |
| `get-servers` | Get streaming servers for an episode |
| `resolve-stream` | Resolve stream source from server |
| `search-subtitles` | Search for subtitles |
| `download-subtitle` | Download subtitle file |
| `get-articles` | Get article sections |
| `search-articles` | Search articles |

## Writing a Plugin

### 1. Create a plugin project

```bash
cargo init --lib my-plugin
cd my-plugin
```

Add to `Cargo.toml`:

```toml
[package]
name = "my-plugin"
version = "0.1.0"
edition = "2021"

[dependencies]
wit-bindgen = { version = "0.57.1", features = ["bitflags"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"

[lib]
crate-type = ["cdylib"]

[package.metadata.component]
package = "bex:my-plugin"

[package.metadata.component.dependencies]
```

### 2. Copy the WIT definitions

Copy `wit/plugin.wit` from the engine repository into your plugin's `wit/` directory.

### 3. Generate bindings

Run `cargo build --target wasm32-wasip1 --release` once to generate `src/bindings.rs`, then implement the `Guest` trait:

```rust
#[allow(warnings)]
mod bindings;

use bindings::bex::plugin::common::*;
use bindings::bex::plugin::http;
use bindings::exports::api::Guest;

struct Component;

impl Guest for Component {
    fn get_home(_ctx: RequestContext) -> Result<Vec<HomeSection>, PluginError> {
        Ok(vec![HomeSection {
            id: "home".to_string(),
            title: "My Plugin".to_string(),
            subtitle: None,
            items: vec![],
            next_page: None,
            layout: CardLayout::Grid,
            show_rank: false,
            categories: vec![],
            extra: vec![],
        }])
    }

    fn search(_ctx: RequestContext, query: String, _filters: SearchFilters) -> Result<PagedResult, PluginError> {
        let response = http::send_request(&http::Request {
            method: http::Method::Get,
            url: format!("https://api.example.com/search?q={}", query),
            headers: vec![],
            body: None,
            timeout_ms: Some(10000),
            follow_redirects: true,
            cache_mode: http::CacheMode::Normal,
            max_bytes: Some(1024 * 1024),
        }).map_err(|e| PluginError::Network(format!("{:?}", e)))?;

        Ok(PagedResult { items: vec![], categories: vec![], next_page: None })
    }

    fn get_servers(_ctx: RequestContext, id: String) -> Result<Vec<Server>, PluginError> {
        // The ID is self-describing β€” parse it however your plugin needs
        // The engine does not interpret the ID, only your plugin does
        let parts: Vec<&str> = id.split('$').collect();
        // ... parse and fetch servers ...
        Ok(vec![])
    }

    // ... implement other methods ...
}

bindings::export!(Component with_types_in bindings);
```

### 4. Build, convert, and pack

```bash
# Step 1: Compile to WASM
cargo build --target wasm32-wasip1 --release

# Step 2: Convert to a WASM Component
wasm-tools component new \
    target/wasm32-wasip1/release/my_plugin.wasm \
    -o target/components/my-plugin.component.wasm \
    --adapt /path/to/wasi_snapshot_preview1.reactor.wasm

# Step 3: Pack into a .bex package
bex pack manifest.yaml target/components/my-plugin.component.wasm my-plugin.bex

# Step 4: Install
bex install my-plugin.bex
```

### Plugin Manifest

```yaml
schema: 1
id: bex.my-plugin
name: My Plugin
version: 1.0.0
authors:
  - Your Name
abi: ">=1.0.0,<2.0.0"
provides:
  home: true
  search: true
  info: true
  servers: true
  stream: true
network:
  hosts:
    - "api.example.com"
  concurrent: 4
storage: true
secrets:
  - api-key
display:
  description: My awesome plugin
  tags:
    - streaming
  priority: 100
```

### Capability Bits

| Bit | Name | Methods |
|-----|------|---------|
| 0 | `HOME` | `get_home` |
| 1 | `CATEGORY` | `get_category` |
| 2 | `SEARCH` | `search` |
| 3 | `INFO` | `get_info` |
| 4 | `SERVERS` | `get_servers` |
| 5 | `STREAM` | `resolve_stream` |
| 6 | `SUBTITLES` | `search_subtitles`, `download_subtitle` |
| 7 | `ARTICLES` | `get_articles`, `search_articles` |

## CMake Integration

The C++ CLI's `CMakeLists.txt` is designed to be self-contained and reusable. You can integrate the BEX engine into any C++ project with minimal setup. The only requirement is the `bex_engine.h` header and the Rust static/shared library β€” no cxx bridge, no code generation, no special include paths.

### Quick Integration

1. Build the Rust library:

```bash
cargo build -p bex-runtime --release
```

2. Copy the `cpp-cli/` directory into your project (or reference it via `BEX_ENGINE_ROOT`).

3. In your `CMakeLists.txt`:

```cmake
cmake_minimum_required(VERSION 3.16)
project(myapp LANGUAGES C CXX)

set(CMAKE_CXX_STANDARD 17)

# Point to the bex-engine root (required if not inside the repo)
set(BEX_ENGINE_ROOT "/path/to/bex-engine")

# Add the bex engine subdirectory
add_subdirectory(${BEX_ENGINE_ROOT}/cpp-cli bex_engine_build)

# Link against the imported bex::engine target
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE bex::engine)
```

### BEX_ENGINE_ROOT Variable

The CMake build uses `BEX_ENGINE_ROOT` to locate the Rust library and the C header:

- **Default**: If not set, it walks up from `CMAKE_SOURCE_DIR` to find a directory containing `Cargo.toml`
- **Override**: Set `-DBEX_ENGINE_ROOT=/path/to/bex-engine` when running cmake

### Imported Target

The CMakeLists.txt creates an INTERFACE library target `bex::engine` with all include paths and link libraries configured:

```cmake
target_link_libraries(myapp PRIVATE bex::engine)
```

### Helper Target

A `rustlib` custom target is provided to build the Rust library from CMake:

```bash
make rustlib
```

### Required Files

| File | Purpose |
|------|---------|
| `cpp-cli/bex_engine.h` | Pure C ABI header (the FFI boundary) |
| `target/release/libbex_runtime.a` | Rust static library (Pure C ABI exports) |

That's it. No generated headers, no bridge codegen, no extra include paths.

## Error Code Reference

| Code | Meaning |
|------|---------|
| `ABI_MISMATCH` | Plugin ABI version incompatible |
| `INVALID_MANIFEST` | Manifest validation failed |
| `HASH_MISMATCH` | Package integrity check failed |
| `NOT_FOUND` | Plugin or resource not found |
| `DISABLED` | Plugin is disabled |
| `UNSUPPORTED` | Operation not supported by plugin |
| `NETWORK_BLOCKED` | Host not in plugin's allowed list |
| `TIMEOUT` | Request timed out |
| `FUEL_EXHAUSTED` | WASM fuel limit exceeded |
| `CANCELLED` | Request was cancelled |
| `PLUGIN_FAULT` | Plugin panicked or crashed |
| `PLUGIN_ERROR` | Plugin returned an error |
| `NETWORK` | Network error |
| `STORAGE` | Storage error |
| `NOT_READY` | Engine not ready |
| `INTERNAL` | Internal engine error |

## Technologies

| Component | Technology | Version |
|-----------|-----------|---------|
| WASM Runtime | Wasmtime | 30 |
| Interface Types | WIT (Component Model) | - |
| WASI | WASI Preview 1/2 | - |
| Bindings | wit-bindgen | 0.57.1 |
| Database | Redb | 2 |
| HTTP | reqwest (rustls) | 0.12 |
| C++ FFI | Pure C ABI (extern "C") | - |
| Wire Format | FlatBuffers | - |
| Compression | zstd | 0.13 |
| Async Runtime | tokio | 1 |
| Cancellation | tokio-util (CancellationToken) | - |
| Package Format | Custom (BEX v1) | - |

## License

MIT