diff --git a/.gitignore b/.gitignore index 1de5659..c40214e 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ -target \ No newline at end of file +target +dump.rdb +worker_rhai_temp_db +launch_data +.DS_Store \ No newline at end of file diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md deleted file mode 100644 index fd2af2e..0000000 --- a/ARCHITECTURE.md +++ /dev/null @@ -1,201 +0,0 @@ -# Architecture: Circle Management System - -## 1. Introduction & Overview - -This document outlines the architecture for a system that manages multiple "Circles." Each Circle is an independent entity comprising its own database, a Rhai scripting engine, a dedicated Rhai worker process, and a WebSocket (WS) server for external interaction. A central command-line orchestrator will be responsible for initializing, running, and monitoring these Circles based on a configuration file. - -The primary goal is to allow users to define multiple isolated Rhai environments, each accessible via a unique WebSocket endpoint, and for scripts executed within a circle to interact with that circle's dedicated persistent storage. - -## 2. Goals - -* Create a command-line application (`circles_orchestrator`) to manage the lifecycle of multiple Circles. -* Each Circle will have: - * An independent `OurDB` instance for data persistence. - * A dedicated `Rhai Engine` configured with its `OurDB`. - * A dedicated `Rhai Worker` processing scripts for that circle. - * A dedicated `WebSocket Server` exposing an endpoint for that circle. -* Circle configurations (name, ID, port) will be loaded from a `circles.json` file. -* The orchestrator will display a status table of all running circles, including their worker queue and WS server URL. -* Utilize existing crates (`ourdb`, `rhai_engine`, `rhai_worker`, `rhai_client`, `server_ws`) with necessary refactoring to support library usage. - -## 3. System Components - -* **Orchestrator (`circles_orchestrator`)**: - * Location: `/Users/timurgordon/code/git.ourworld.tf/herocode/circles/cmd/src/main.rs` - * Role: Parses `circles.json`, initializes, spawns, and monitors all components for each defined circle. Displays system status. -* **Circle Configuration (`circles.json`)**: - * Location: e.g., `/Users/timurgordon/code/git.ourworld.tf/herocode/circles/cmd/circles.json` - * Role: Defines the set of circles to be managed, including their ID, name, and port. -* **OurDB (Per Circle)**: - * Library: `/Users/timurgordon/code/git.ourworld.tf/herocode/db/ourdb/src/lib.rs` - * Role: Provides persistent key-value storage for each circle, configured in incremental mode. Instance data stored at `~/.hero/circles/{id}/`. -* **Rhai Engine (Per Circle)**: - * Library: `/Users/timurgordon/code/git.ourworld.tf/herocode/rhailib/src/engine/src/lib.rs` - * Role: Provides the Rhai scripting environment for a circle, configured with the circle's specific `OurDB` instance. -* **Rhai Worker (Per Circle)**: - * Library: `/Users/timurgordon/code/git.ourworld.tf/herocode/rhailib/src/worker/src/lib.rs` - * Role: Executes Rhai scripts for a specific circle. Listens on a dedicated Redis queue for tasks and uses the circle's `Rhai Engine`. -* **Rhai Client (Per Circle WS Server)**: - * Library: `/Users/timurgordon/code/git.ourworld.tf/herocode/rhailib/src/client/src/lib.rs` - * Role: Used by a `Circle WebSocket Server` to send script execution tasks to its corresponding `Rhai Worker` via Redis. -* **Circle WebSocket Server (Per Circle)**: - * Library: `/Users/timurgordon/code/git.ourworld.tf/herocode/circles/server_ws/src/lib.rs` - * Role: Exposes a WebSocket endpoint for a specific circle, allowing external clients to submit Rhai scripts for execution within that circle. -* **Redis**: - * URL: `redis://127.0.0.1:6379` - * Role: Acts as the message broker between `Rhai Clients` (within WS Servers) and `Rhai Workers`. - -## 4. High-Level Design Aspects - -### 4.1. Orchestrator Logic (Conceptual) -The `circles_orchestrator` reads the `circles.json` configuration. For each defined circle, it: -1. Determines the `OurDB` path based on the circle's ID. -2. Initializes the `OurDB` instance. -3. Creates a `Rhai Engine` configured with this `OurDB`. -4. Spawns a `Rhai Worker` task, providing it with the engine and the circle's identity (for Redis queue naming). -5. Spawns a `Circle WebSocket Server` task, providing it with the circle's identity and port. -It then monitors these components and displays their status. - -### 4.2. Database Path Convention -* Base: System user's home directory (e.g., `/Users/username` or `/home/username`). -* Structure: `~/.hero/circles/{CIRCLE_ID}/` -* Example: For a circle with ID `1`, the database path would be `~/.hero/circles/1/`. - -### 4.3. Configuration File Format (`circles.json`) -A JSON array of objects. Each object represents a circle: -* `id`: `u32` - Unique identifier. -* `name`: `String` - Human-readable name. -* `port`: `u16` - Port for the WebSocket server. -```json -[ - { "id": 1, "name": "Alpha Circle", "port": 8081 }, - { "id": 2, "name": "Beta Circle", "port": 8082 } -] -``` - -### 4.4. Conceptual Output Table Format (Orchestrator) -| Circle Name | ID | Worker Status | Worker Queues | WS Server URL | -|--------------|----|---------------|------------------------------------|------------------------| -| Alpha Circle | 1 | Running | `rhai_tasks:alpha_circle` | `ws://127.0.0.1:8081/ws` | - -### 4.5. Interaction Flow (Single Circle) -1. An external client connects to a specific Circle's WebSocket Server. -2. The client sends a Rhai script via a JSON-RPC message. -3. The WS Server uses its embedded `Rhai Client` to publish the script and task details to a Redis queue specific to that circle (e.g., `rhai_tasks:alpha_circle`). -4. The `Rhai Worker` for that circle picks up the task from its Redis queue. -5. The Worker uses its `Rhai Engine` (which is configured with the circle's `OurDB`) to execute the script. -6. Any database interactions within the script go through the circle's `OurDB`. -7. The Worker updates the task status and result/error in Redis. -8. The `Rhai Client` (in the WS Server), which has been polling Redis for the result, receives the update. -9. The WS Server sends the script's result or error back to the external client via WebSocket. - -## 5. Diagrams - -### 5.1. Component Diagram -```mermaid -graph TD - UserInterface[User/Admin] -- Manages --> OrchestratorCli{circles_orchestrator} - OrchestratorCli -- Reads --> CirclesJson[circles.json] - - subgraph Circle 1 - direction LR - OrchestratorCli -- Spawns/Manages --> C1_OurDB[(OurDB @ ~/.hero/circles/1)] - OrchestratorCli -- Spawns/Manages --> C1_RhaiEngine[Rhai Engine 1] - OrchestratorCli -- Spawns/Manages --> C1_RhaiWorker[Rhai Worker 1] - OrchestratorCli -- Spawns/Manages --> C1_WSServer[WS Server 1 @ Port 8081] - - C1_RhaiEngine -- Uses --> C1_OurDB - C1_RhaiWorker -- Uses --> C1_RhaiEngine - C1_WSServer -- Contains --> C1_RhaiClient[Rhai Client 1] - end - - subgraph Circle 2 - direction LR - OrchestratorCli -- Spawns/Manages --> C2_OurDB[(OurDB @ ~/.hero/circles/2)] - OrchestratorCli -- Spawns/Manages --> C2_RhaiEngine[Rhai Engine 2] - OrchestratorCli -- Spawns/Manages --> C2_RhaiWorker[Rhai Worker 2] - OrchestratorCli -- Spawns/Manages --> C2_WSServer[WS Server 2 @ Port 8082] - - C2_RhaiEngine -- Uses --> C2_OurDB - C2_RhaiWorker -- Uses --> C2_RhaiEngine - C2_WSServer -- Contains --> C2_RhaiClient[Rhai Client 2] - end - - C1_RhaiWorker -- Listens/Publishes --> Redis[(Redis @ 127.0.0.1:6379)] - C1_RhaiClient -- Publishes/Subscribes --> Redis - C2_RhaiWorker -- Listens/Publishes --> Redis - C2_RhaiClient -- Publishes/Subscribes --> Redis - - ExternalWSClient1[External WS Client] -- Connects --> C1_WSServer - ExternalWSClient2[External WS Client] -- Connects --> C2_WSServer -``` - -### 5.2. Sequence Diagram (Request Flow for one Circle) -```mermaid -sequenceDiagram - participant ExtWSClient as External WS Client - participant CircleWSServer as Circle WS Server (e.g., Port 8081) - participant RhaiClientLib as Rhai Client Library (in WS Server) - participant RedisBroker as Redis - participant RhaiWorker as Rhai Worker (for the circle) - participant RhaiEngineLib as Rhai Engine Library (in Worker) - participant CircleOurDB as OurDB (for the circle) - - ExtWSClient ->>+ CircleWSServer: Send Rhai Script (JSON-RPC "play" over WS) - CircleWSServer ->>+ RhaiClientLib: submit_script_and_await_result(circle_name, script, ...) - RhaiClientLib ->>+ RedisBroker: LPUSH rhai_tasks:circle_name (task_id) - RhaiClientLib ->>+ RedisBroker: HSET rhai_task_details:task_id (script details) - RhaiClientLib -->>- CircleWSServer: Returns task_id (internally starts polling) - - RhaiWorker ->>+ RedisBroker: BLPOP rhai_tasks:circle_name (blocks) - RedisBroker -->>- RhaiWorker: Returns task_id - RhaiWorker ->>+ RedisBroker: HGETALL rhai_task_details:task_id - RedisBroker -->>- RhaiWorker: Returns script details - RhaiWorker ->>+ RhaiEngineLib: eval_with_scope(script) - RhaiEngineLib ->>+ CircleOurDB: DB Operations (if script interacts with DB) - CircleOurDB -->>- RhaiEngineLib: DB Results - RhaiEngineLib -->>- RhaiWorker: Script Result/Error - RhaiWorker ->>+ RedisBroker: HSET rhai_task_details:task_id (status="completed/error", output/error) - - RhaiClientLib ->>+ RedisBroker: HGETALL rhai_task_details:task_id (polling) - RedisBroker -->>- RhaiClientLib: Task details (status, output/error) - alt Task Completed - RhaiClientLib -->>- CircleWSServer: Result (output) - CircleWSServer -->>- ExtWSClient: WS Response (JSON-RPC with result) - else Task Errored - RhaiClientLib -->>- CircleWSServer: Error (error message) - CircleWSServer -->>- ExtWSClient: WS Response (JSON-RPC with error) - else Timeout - RhaiClientLib -->>- CircleWSServer: Timeout Error - CircleWSServer -->>- ExtWSClient: WS Response (JSON-RPC with timeout error) - end -``` - -### 5.3. Conceptual Directory Structure -``` -/Users/timurgordon/code/git.ourworld.tf/herocode/ -├── circles/ -│ ├── cmd/ <-- NEW Orchestrator Crate -│ │ ├── Cargo.toml -│ │ ├── circles.json (example config) -│ │ └── src/ -│ │ └── main.rs (Orchestrator logic) -│ ├── server_ws/ <-- EXISTING, to be refactored to lib -│ │ ├── Cargo.toml -│ │ └── src/ -│ │ └── lib.rs (WebSocket server library logic) -│ ├── ARCHITECTURE.md (This document) -│ └── README.md -├── db/ -│ └── ourdb/ -│ └── src/lib.rs (Existing OurDB library) -└── rhailib/ <-- Current Workspace (contains other existing libs) - ├── src/ - │ ├── client/ - │ │ └── src/lib.rs (Existing Rhai Client library) - │ ├── engine/ - │ │ └── src/lib.rs (Existing Rhai Engine library) - │ └── worker/ - │ └── src/lib.rs (Existing Rhai Worker library, to be refactored) - ├── Cargo.toml - └── ... \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..1081db0 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,4484 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "actix" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de7fa236829ba0841304542f7614c42b80fca007455315c45c785ccfa873a85b" +dependencies = [ + "actix-macros", + "actix-rt", + "actix_derive", + "bitflags", + "bytes", + "crossbeam-channel", + "futures-core", + "futures-sink", + "futures-task", + "futures-util", + "log", + "once_cell", + "parking_lot", + "pin-project-lite", + "smallvec", + "tokio", + "tokio-util", +] + +[[package]] +name = "actix-codec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-http" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44dfe5c9e0004c623edc65391dfd51daa201e7e30ebd9c9bedf873048ec32bc2" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-tls", + "actix-utils", + "base64", + "bitflags", + "brotli", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "foldhash", + "futures-core", + "h2", + "http 0.2.12", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand 0.9.1", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn 2.0.103", +] + +[[package]] +name = "actix-router" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" +dependencies = [ + "bytestring", + "cfg-if", + "http 0.2.12", + "regex", + "regex-lite", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eda4e2a6e042aa4e55ac438a2ae052d3b5da0ecf83d7411e1a368946925208" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "socket2", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "actix-tls" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac453898d866cdbecdbc2334fe1738c747b4eba14a677261f2b768ba05329389" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "impl-more", + "pin-project-lite", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a597b77b5c6d6a1e1097fddde329a83665e25c5437c696a3a9a4aa514a614dea" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-tls", + "actix-utils", + "actix-web-codegen", + "bytes", + "bytestring", + "cfg-if", + "cookie", + "derive_more", + "encoding_rs", + "foldhash", + "futures-core", + "futures-util", + "impl-more", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "regex-lite", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2", + "time", + "tracing", + "url", +] + +[[package]] +name = "actix-web-actors" +version = "4.3.1+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98c5300b38fd004fe7d2a964f9a90813fdbe8a81fed500587e78b1b71c6f980" +dependencies = [ + "actix", + "actix-codec", + "actix-http", + "actix-web", + "bytes", + "bytestring", + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "actix-web-codegen" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "actix_derive" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6ac1e58cded18cb28ddc17143c4dea5345b3ad575e14f32f66e4054a56eb271" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "const-random", + "getrandom 0.3.3", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.59.0", +] + +[[package]] +name = "anymap2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "async-trait" +version = "0.1.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "aws-lc-rs" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fcc8f365936c834db5514fc45aee5b1202d677e6b40e48468aaaa8183ca8c7" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61b1d86e7705efe1be1b569bab41d4fa1e14e220b60a160f78de2db687add079" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bincode" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" +dependencies = [ + "bincode_derive", + "serde", + "unty", +] + +[[package]] +name = "bincode_derive" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" +dependencies = [ + "virtue", +] + +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.103", + "which", +] + +[[package]] +name = "bitcoin-internals" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" + +[[package]] +name = "bitcoin_hashes" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" +dependencies = [ + "bitcoin-internals", + "hex-conservative", +] + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "boolinator" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfa8873f51c92e232f9bac4065cddef41b714152812bfc5f7672ba16d6ef8cd9" + +[[package]] +name = "brotli" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db76d6187cd04dff33004d8e6c9cc4e05cd330500379d2394209271b4aeee" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "bytestring" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e465647ae23b2823b0753f50decb2d5a86d2bb2cac04788fafd1f80e45378e5f" +dependencies = [ + "bytes", +] + +[[package]] +name = "cc" +version = "1.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "circle_client_ws" +version = "0.1.0" +dependencies = [ + "async-trait", + "circle_ws_lib", + "env_logger", + "futures-channel", + "futures-util", + "gloo-console 0.3.0", + "gloo-net 0.4.0", + "hex", + "http 0.2.12", + "js-sys", + "log", + "native-tls", + "rand 0.8.5", + "secp256k1 0.29.1", + "serde", + "serde_json", + "sha3", + "thiserror", + "tokio", + "tokio-native-tls", + "tokio-tungstenite 0.19.0", + "url", + "urlencoding", + "uuid", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "circle_ws_lib" +version = "0.1.0" +dependencies = [ + "actix", + "actix-web", + "actix-web-actors", + "chrono", + "circle_client_ws", + "clap", + "engine", + "env_logger", + "futures-util", + "heromodels", + "hex", + "log", + "once_cell", + "rand 0.8.5", + "redis", + "rhai_client", + "rhailib_worker", + "rustls", + "rustls-pemfile", + "secp256k1 0.29.1", + "serde", + "serde_json", + "sha3", + "tokio", + "tokio-tungstenite 0.19.0", + "url", + "urlencoding", + "uuid", +] + +[[package]] +name = "circles" +version = "0.1.0" +dependencies = [ + "circle_client_ws", + "circle_ws_lib", + "engine", + "env_logger", + "heromodels", + "hex", + "launcher", + "log", + "redis", + "rhailib_worker", + "secp256k1 0.29.1", + "serde_json", + "tempfile", + "tokio", +] + +[[package]] +name = "circles-app" +version = "0.1.0" +dependencies = [ + "chrono", + "circle_client_ws", + "common_models", + "engine", + "futures", + "futures-channel", + "futures-util", + "getrandom 0.3.3", + "gloo-console 0.3.0", + "gloo-net 0.4.0", + "gloo-storage 0.3.0", + "gloo-timers 0.3.0", + "heromodels", + "hex", + "js-sys", + "log", + "rand 0.8.5", + "rhai", + "secp256k1 0.29.1", + "serde", + "serde_json", + "sha3", + "thiserror", + "urlencoding", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test", + "wasm-logger", + "web-sys", + "yew", + "yew-router", +] + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "4.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + +[[package]] +name = "clipboard-win" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" +dependencies = [ + "error-code", +] + +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "comfy-table" +version = "7.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a65ebfec4fb190b6f90e944a817d60499ee0744e582530e2c9900a22e591d9a" +dependencies = [ + "crossterm", + "unicode-segmentation", + "unicode-width 0.2.1", +] + +[[package]] +name = "common_models" +version = "0.1.0" +dependencies = [ + "serde", + "serde_derive", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags", + "crossterm_winapi", + "parking_lot", + "rustix 0.38.44", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "crunchy" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", + "unicode-xid", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[package]] +name = "engine" +version = "0.1.0" +dependencies = [ + "chrono", + "heromodels", + "heromodels-derive", + "heromodels_core", + "rhai", +] + +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fd-lock" +version = "4.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" +dependencies = [ + "cfg-if", + "rustix 1.0.7", + "windows-sys 0.59.0", +] + +[[package]] +name = "flate2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "gloo" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28999cda5ef6916ffd33fb4a7b87e1de633c47c0dc6d97905fee1cdaa142b94d" +dependencies = [ + "gloo-console 0.2.3", + "gloo-dialogs 0.1.1", + "gloo-events 0.1.2", + "gloo-file 0.2.3", + "gloo-history 0.1.5", + "gloo-net 0.3.1", + "gloo-render 0.1.1", + "gloo-storage 0.2.2", + "gloo-timers 0.2.6", + "gloo-utils 0.1.7", + "gloo-worker 0.2.1", +] + +[[package]] +name = "gloo" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd35526c28cc55c1db77aed6296de58677dbab863b118483a27845631d870249" +dependencies = [ + "gloo-console 0.3.0", + "gloo-dialogs 0.2.0", + "gloo-events 0.2.0", + "gloo-file 0.3.0", + "gloo-history 0.2.2", + "gloo-net 0.4.0", + "gloo-render 0.2.0", + "gloo-storage 0.3.0", + "gloo-timers 0.3.0", + "gloo-utils 0.2.0", + "gloo-worker 0.4.0", +] + +[[package]] +name = "gloo-console" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b7ce3c05debe147233596904981848862b068862e9ec3e34be446077190d3f" +dependencies = [ + "gloo-utils 0.1.7", + "js-sys", + "serde", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-console" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a17868f56b4a24f677b17c8cb69958385102fa879418052d60b50bc1727e261" +dependencies = [ + "gloo-utils 0.2.0", + "js-sys", + "serde", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-dialogs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67062364ac72d27f08445a46cab428188e2e224ec9e37efdba48ae8c289002e6" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-dialogs" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4748e10122b01435750ff530095b1217cf6546173459448b83913ebe7815df" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-events" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b107f8abed8105e4182de63845afcc7b69c098b7852a813ea7462a320992fc" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-events" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c26fb45f7c385ba980f5fa87ac677e363949e065a083722697ef1b2cc91e41" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-file" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d5564e570a38b43d78bdc063374a0c3098c4f0d64005b12f9bbe87e869b6d7" +dependencies = [ + "gloo-events 0.1.2", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-file" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97563d71863fb2824b2e974e754a81d19c4a7ec47b09ced8a0e6656b6d54bd1f" +dependencies = [ + "futures-channel", + "gloo-events 0.2.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-history" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85725d90bf0ed47063b3930ef28e863658a7905989e9929a8708aab74a1d5e7f" +dependencies = [ + "gloo-events 0.1.2", + "gloo-utils 0.1.7", + "serde", + "serde-wasm-bindgen 0.5.0", + "serde_urlencoded", + "thiserror", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-history" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "903f432be5ba34427eac5e16048ef65604a82061fe93789f2212afc73d8617d6" +dependencies = [ + "getrandom 0.2.16", + "gloo-events 0.2.0", + "gloo-utils 0.2.0", + "serde", + "serde-wasm-bindgen 0.6.5", + "serde_urlencoded", + "thiserror", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-net" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a66b4e3c7d9ed8d315fd6b97c8b1f74a7c6ecbbc2320e65ae7ed38b7068cc620" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils 0.1.7", + "http 0.2.12", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-net" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ac9e8288ae2c632fa9f8657ac70bfe38a1530f345282d7ba66a1f70b72b7dc4" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils 0.2.0", + "http 0.2.12", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-render" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd9306aef67cfd4449823aadcd14e3958e0800aa2183955a309112a84ec7764" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-render" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56008b6744713a8e8d98ac3dcb7d06543d5662358c9c805b4ce2167ad4649833" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-storage" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6ab60bf5dbfd6f0ed1f7843da31b41010515c745735c970e821945ca91e480" +dependencies = [ + "gloo-utils 0.1.7", + "js-sys", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-storage" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc8031e8c92758af912f9bc08fbbadd3c6f3cfcbf6b64cdf3d6a81f0139277a" +dependencies = [ + "gloo-utils 0.2.0", + "js-sys", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gloo-utils" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-utils" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-worker" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13471584da78061a28306d1359dd0178d8d6fc1c7c80e5e35d27260346e0516a" +dependencies = [ + "anymap2", + "bincode 1.3.3", + "gloo-console 0.2.3", + "gloo-utils 0.1.7", + "js-sys", + "serde", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-worker" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76495d3dd87de51da268fa3a593da118ab43eb7f8809e17eb38d3319b424e400" +dependencies = [ + "bincode 1.3.3", + "futures", + "gloo-utils 0.2.0", + "gloo-worker-macros", + "js-sys", + "pinned", + "serde", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-worker-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956caa58d4857bc9941749d55e4bd3000032d8212762586fa5705632967140e7" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "heromodels" +version = "0.1.0" +dependencies = [ + "bincode 2.0.1", + "chrono", + "heromodels-derive", + "heromodels_core", + "ourdb", + "rhai", + "rhai_client_macros", + "serde", + "serde_json", + "strum", + "strum_macros", + "tst", + "uuid", +] + +[[package]] +name = "heromodels-derive" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "heromodels_core" +version = "0.1.0" +dependencies = [ + "chrono", + "serde", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-conservative" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" + +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "impl-more" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" + +[[package]] +name = "implicit-clone" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8a9aa791c7b5a71b636b7a68207fdebf171ddfc593d9c8506ec4cbc527b6a84" +dependencies = [ + "implicit-clone-derive", + "indexmap", +] + +[[package]] +name = "implicit-clone-derive" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "699c1b6d335e63d0ba5c1e1c7f647371ce989c3bcbe1f7ed2b85fa56e3bd1a21" +dependencies = [ + "quote", + "syn 2.0.103", +] + +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "is-terminal" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jobserver" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + +[[package]] +name = "launcher" +version = "0.1.0" +dependencies = [ + "actix-web", + "circle_ws_lib", + "clap", + "comfy-table", + "dirs", + "engine", + "env_logger", + "futures-util", + "heromodels", + "log", + "once_cell", + "ourdb", + "rand 0.8.5", + "redis", + "rhai", + "rhai_client", + "rhailib_worker", + "secp256k1 0.28.2", + "serde", + "serde_json", + "tempfile", + "tokio", + "tokio-tungstenite 0.23.1", + "url", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.173" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8cfeafaffdbc32176b64fb251369d52ea9f0a8fbc6f8759edffef7b525d64bb" + +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets 0.53.2", +] + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "local-channel" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" +dependencies = [ + "futures-core", + "futures-sink", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minicov" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b" +dependencies = [ + "cc", + "walkdir", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "log", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + +[[package]] +name = "no-std-compat" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" +dependencies = [ + "spin", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "openssl" +version = "0.10.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ourdb" +version = "0.1.0" +dependencies = [ + "crc32fast", + "log", + "rand 0.8.5", + "thiserror", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pinned" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a829027bd95e54cfe13e3e258a1ae7b645960553fb82b75ff852c29688ee595b" +dependencies = [ + "futures", + "rustversion", + "thiserror", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55" +dependencies = [ + "proc-macro2", + "syn 2.0.103", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prokio" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b55e106e5791fa5a13abd13c85d6127312e8e09098059ca2bc9b03ca4cf488" +dependencies = [ + "futures", + "gloo 0.8.1", + "num_cpus", + "once_cell", + "pin-project", + "pinned", + "tokio", + "tokio-stream", + "wasm-bindgen-futures", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + +[[package]] +name = "redis" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0d7a6955c7511f60f3ba9e86c6d02b3c3f144f8c24b288d1f4e18074ab8bbec" +dependencies = [ + "async-trait", + "bytes", + "combine", + "futures-util", + "itoa", + "percent-encoding", + "pin-project-lite", + "ryu", + "sha1_smol", + "socket2", + "tokio", + "tokio-util", + "url", +] + +[[package]] +name = "redox_syscall" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-lite" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rhai" +version = "1.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2780e813b755850e50b178931aaf94ed24f6817f46aaaf5d21c13c12d939a249" +dependencies = [ + "ahash", + "bitflags", + "instant", + "no-std-compat", + "num-traits", + "once_cell", + "rhai_codegen", + "rust_decimal", + "smallvec", + "smartstring", + "thin-vec", +] + +[[package]] +name = "rhai_client" +version = "0.1.0" +dependencies = [ + "chrono", + "log", + "redis", + "serde", + "serde_json", + "tokio", + "uuid", +] + +[[package]] +name = "rhai_client_macros" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "rhai", + "syn 2.0.103", +] + +[[package]] +name = "rhai_codegen" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "rhailib_worker" +version = "0.1.0" +dependencies = [ + "chrono", + "clap", + "engine", + "env_logger", + "heromodels", + "log", + "redis", + "rhai", + "rhai_client", + "serde", + "serde_json", + "tokio", + "uuid", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "route-recognizer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746" + +[[package]] +name = "rust_decimal" +version = "1.37.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b203a6425500a03e0919c42d3c47caca51e79f1132046626d2c8871c5092035d" +dependencies = [ + "arrayvec", + "num-traits", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.9.4", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls" +version = "0.23.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "rustyline" +version = "13.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02a2d683a4ac90aeef5b1013933f6d977bd37d51ff3f4dad829d4931a7e6be86" +dependencies = [ + "bitflags", + "cfg-if", + "clipboard-win", + "fd-lock", + "home", + "libc", + "log", + "memchr", + "nix", + "radix_trie", + "rustyline-derive", + "unicode-segmentation", + "unicode-width 0.1.14", + "utf8parse", + "winapi", +] + +[[package]] +name = "rustyline-derive" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5af959c8bf6af1aff6d2b463a57f71aae53d1332da58419e30ad8dc7011d951" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "secp256k1" +version = "0.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10" +dependencies = [ + "rand 0.8.5", + "secp256k1-sys 0.9.2", +] + +[[package]] +name = "secp256k1" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" +dependencies = [ + "bitcoin_hashes", + "rand 0.8.5", + "secp256k1-sys 0.10.1", +] + +[[package]] +name = "secp256k1-sys" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d1746aae42c19d583c3c1a8c646bfad910498e2051c551a7f2e3c0c9fbb7eb" +dependencies = [ + "cc", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smartstring" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" +dependencies = [ + "autocfg", + "static_assertions", + "version_check", +] + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.103", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix 1.0.7", + "windows-sys 0.59.0", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thin-vec" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d" + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec509ac96e9a0c43427c74f003127d953a265737636129424288d27cb5c4b12c" +dependencies = [ + "futures-util", + "log", + "native-tls", + "tokio", + "tokio-native-tls", + "tungstenite 0.19.0", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd" +dependencies = [ + "futures-util", + "log", + "native-tls", + "tokio", + "tokio-native-tls", + "tungstenite 0.23.0", +] + +[[package]] +name = "tokio-util" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1ffbcf9c6f6b99d386e7444eb608ba646ae452a36b39737deb9663b610f662" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "tst" +version = "0.1.0" +dependencies = [ + "ourdb", + "thiserror", +] + +[[package]] +name = "tungstenite" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15fba1a6d6bb030745759a9a2a588bfe8490fc8b4751a277db3a0be1c9ebbf67" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 0.2.12", + "httparse", + "log", + "native-tls", + "rand 0.8.5", + "sha1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "tungstenite" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.3.1", + "httparse", + "log", + "native-tls", + "rand 0.8.5", + "sha1", + "thiserror", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "ui_repl" +version = "0.1.0" +dependencies = [ + "circle_client_ws", + "futures-util", + "log", + "rustyline", + "tempfile", + "tokio", + "tokio-tungstenite 0.23.1", + "tracing", + "tracing-subscriber", + "url", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "unty" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +dependencies = [ + "getrandom 0.3.3", + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "virtue" +version = "0.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.103", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-bindgen-test" +version = "0.3.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66c8d5e33ca3b6d9fa3b4676d774c5778031d27a578c2b007f905acf816152c3" +dependencies = [ + "js-sys", + "minicov", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "wasm-logger" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "074649a66bb306c8f2068c9016395fa65d8e08d2affcbf95acf3c24c3ab19718" +dependencies = [ + "log", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.44", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "yew" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f1a03f255c70c7aa3e9c62e15292f142ede0564123543c1cc0c7a4f31660cac" +dependencies = [ + "console_error_panic_hook", + "futures", + "gloo 0.10.0", + "implicit-clone", + "indexmap", + "js-sys", + "prokio", + "rustversion", + "serde", + "slab", + "thiserror", + "tokio", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "yew-macro", +] + +[[package]] +name = "yew-macro" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02fd8ca5166d69e59f796500a2ce432ff751edecbbb308ca59fd3fe4d0343de2" +dependencies = [ + "boolinator", + "once_cell", + "prettyplease", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "yew-router" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca1d5052c96e6762b4d6209a8aded597758d442e6c479995faf0c7b5538e0c6" +dependencies = [ + "gloo 0.10.0", + "js-sys", + "route-recognizer", + "serde", + "serde_urlencoded", + "tracing", + "urlencoding", + "wasm-bindgen", + "web-sys", + "yew", + "yew-router-macro", +] + +[[package]] +name = "yew-router-macro" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42bfd190a07ca8cfde7cd4c52b3ac463803dc07323db8c34daa697e86365978c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.103", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.15+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..17e1d01 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,73 @@ +[package] +name = "circles" +version = "0.1.0" +edition = "2021" + +[workspace] +resolver = "2" +members = [ + "src/client_ws", + "src/server_ws", + "src/launcher", + "src/ui_repl", + "src/app", +] + +[dependencies] +circle_client_ws = { path = "src/client_ws", features = ["crypto"] } +serde_json.workspace = true + +# Define shared dependencies for the entire workspace +[workspace.dependencies] +actix = "0.13" +actix-web = "4" +circle_client_ws = { path = "src/client_ws", features = ["crypto"] } +actix-web-actors = "4" +async-trait = "0.1" +chrono = { version = "0.4", features = ["serde"] } +clap = { version = "4.0", features = ["derive"] } +env_logger = "0.10" +futures-channel = "0.3" +futures-util = "0.3" +hex = "0.4" +log = "0.4" +once_cell = "1.19" +rand = "0.8" +redis = { version = "0.25.0", features = ["tokio-comp"] } +secp256k1 = { version = "0.29", features = ["recovery", "rand-std"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +sha3 = "0.10" +tokio = { version = "1", features = ["full"] } +tokio-tungstenite = "0.23.0" +url = "2.5.0" +urlencoding = "2.1" +uuid = { version = "1.6", features = ["v4", "serde", "js"] } +thiserror = "1.0" +# Path dependencies to other local crates from outside this repo +heromodels = { path = "../db/heromodels" } +engine = { path = "../rhailib/src/engine" } +rhailib_worker = { path = "../rhailib/src/worker" } +circle_ws_lib = { path = "src/server_ws" } + + +# Dev dependencies +[dev-dependencies] +env_logger = "0.10" +tokio = { version = "1", features = ["full"] } +tempfile = "3.10.1" +log = "0.4" +circle_ws_lib = { workspace = true } +heromodels = { workspace = true } +engine = { workspace = true } +rhailib_worker = { workspace = true } +redis = { workspace = true } +secp256k1 = { workspace = true } +hex = { workspace = true } +launcher = { path = "src/launcher" } + + + +[features] +crypto = ["circle_client_ws/crypto"] + diff --git a/README.md b/README.md index e73fd4f..617cf15 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,74 @@ -# Circles +# Circles Project -Architecture around our digital selves. \ No newline at end of file +Welcome to the `circles` project, a full-stack system featuring a WebSocket server, a cross-platform client, and a launcher to manage multiple instances. This project is designed for executing Rhai scripts in isolated environments, with an optional layer of `secp256k1` cryptographic authentication. + +## Overview + +The `circles` project provides two core library crates and a utility application: + +- **`server_ws`**: The core WebSocket server library, built with `Actix`. It handles client connections, processes JSON-RPC messages, and executes Rhai scripts. +- **`client_ws`**: The core cross-platform WebSocket client library, compatible with both native Rust and WebAssembly (WASM) environments. +- **`launcher`**: A convenient command-line utility that uses the `server_ws` library to read a `circles.json` configuration file and spawn multiple, isolated "Circle" instances. +- **`openrpc.json`**: An OpenRPC specification that formally defines the JSON-RPC 2.0 API used for client-server communication. + +## Architecture + +The system is designed around a client-server model, with `client_ws` and `server_ws` as the core components. The `launcher` is provided as a utility for orchestrating multiple server instances, each configured as an isolated "Circle" environment. + +Clients connect to a `server_ws` instance via WebSocket and interact with it using the JSON-RPC protocol. The server can be configured to require authentication, in which case the client must complete a signature-based challenge-response flow over the WebSocket connection before it can execute protected methods like `play`. + +For a more detailed explanation of the system's design, please see the [ARCHITECTURE.md](ARCHITECTURE.md) file. + +## Getting Started + +To run the system, you will need to use the `launcher`. + +1. **Configure Your Circles**: Create a `circles.json` file at the root of the project to define the instances you want to run. Each object in the top-level array defines a "Circle" with a unique `id`, `port`, and associated database and script paths. + + ```json + [ + { + "id": "circle-1", + "port": 9001, + "db_path": "/tmp/circle-1.db", + "rhai_path": "/path/to/your/scripts" + } + ] + ``` + +2. **Run the Launcher**: + ```bash + cargo run --package launcher + ``` + +The launcher will start a WebSocket server for each configured circle on its specified port. + +## API + +The client-server communication is handled via JSON-RPC 2.0 over WebSocket. The available methods are: + +- `play`: Executes a Rhai script. +- `authenticate`: Authenticates the client. + +For a complete definition of the API, including request parameters and response objects, please refer to the [openrpc.json](openrpc.json) file. + +## Crates + +- **[server_ws](server_ws/README.md)**: Detailed documentation for the server library. +- **[client_ws](client_ws/README.md)**: Detailed documentation for the client library. +- **[launcher](launcher/README.md)**: Detailed documentation for the launcher utility. +- **[app](src/app/README.md)**: A Yew frontend application that uses the `client_ws` to interact with the `server_ws`. + +## Running the App + +To run the `circles-app`, you'll need to have `trunk` installed. If you don't have it, you can install it with: + +```bash +cargo install trunk wasm-bindgen-cli +``` + +Once `trunk` is installed, you can serve the app with: + +```bash +cd src/app && trunk serve +``` \ No newline at end of file diff --git a/client_ws/Cargo.toml b/client_ws/Cargo.toml deleted file mode 100644 index 3552d52..0000000 --- a/client_ws/Cargo.toml +++ /dev/null @@ -1,31 +0,0 @@ -[package] -name = "circle_client_ws" -version = "0.1.0" -edition = "2021" - -[dependencies] -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -uuid = { version = "1.6", features = ["v4", "serde", "js"] } -log = "0.4" -futures-channel = { version = "0.3", features = ["sink"] } # For mpsc -futures-util = { version = "0.3", features = ["sink"] } # For StreamExt, SinkExt -thiserror = "1.0" -async-trait = "0.1" # May be needed for abstracting WS connection - -# WASM-specific dependencies -[target.'cfg(target_arch = "wasm32")'.dependencies] -gloo-net = { version = "0.4.0", features = ["websocket"] } -wasm-bindgen-futures = "0.4" -gloo-console = "0.3.0" # For wasm logging if needed, or use `log` with wasm_logger - -# Native-specific dependencies -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] -tokio-tungstenite = { version = "0.23.0", features = ["native-tls"] } -tokio = { version = "1", features = ["rt", "macros"] } # For tokio::spawn on native -url = "2.5.0" # For native WebSocket connection - -[dev-dependencies] -# For examples within this crate, if any, or for testing -env_logger = "0.10" -# tokio = { version = "1", features = ["full"] } # If examples need full tokio runtime diff --git a/client_ws/README.md b/client_ws/README.md deleted file mode 100644 index d7be221..0000000 --- a/client_ws/README.md +++ /dev/null @@ -1,86 +0,0 @@ -# Circle WebSocket Client (`circle_client_ws`) - -This crate provides a WebSocket client (`CircleWsClient`) designed to interact with a server that expects JSON-RPC messages, specifically for executing Rhai scripts. - -It is designed to be compatible with both WebAssembly (WASM) environments (e.g., web browsers) and native Rust applications. - -## Features - -- **Cross-Platform:** Works in WASM and native environments. - - Uses `gloo-net` for WebSockets in WASM. - - Uses `tokio-tungstenite` for WebSockets in native applications. -- **JSON-RPC Communication:** Implements client-side JSON-RPC 2.0 request and response handling. -- **Rhai Script Execution:** Provides a `play(script: String)` method to send Rhai scripts to the server for execution and receive their output. -- **Asynchronous Operations:** Leverages `async/await` and `futures` for non-blocking communication. -- **Connection Management:** Supports connecting to and disconnecting from a WebSocket server. -- **Error Handling:** Defines a comprehensive `CircleWsClientError` enum for various client-side errors. - -## Core Component - -- **`CircleWsClient`**: The main client struct. - - `new(ws_url: String)`: Creates a new client instance targeting the given WebSocket URL. - - `connect()`: Establishes the WebSocket connection. - - `play(script: String)`: Sends a Rhai script to the server for execution and returns the result. - - `disconnect()`: Closes the WebSocket connection. - -## Usage Example (Conceptual) - -```rust -use circle_client_ws::CircleWsClient; - -async fn run_client() { - let mut client = CircleWsClient::new("ws://localhost:8080/ws".to_string()); - - if let Err(e) = client.connect().await { - eprintln!("Failed to connect: {}", e); - return; - } - - let script = "print(\"Hello from Rhai via WebSocket!\"); 40 + 2".to_string(); - - match client.play(script).await { - Ok(result) => { - println!("Script output: {}", result.output); - } - Err(e) => { - eprintln!("Error during play: {}", e); - } - } - - client.disconnect().await; -} - -// To run this example, you'd need an async runtime like tokio for native -// or wasm-bindgen-test for WASM. -``` - -## Building - -### Native -```bash -cargo build -``` - -### WASM -```bash -cargo build --target wasm32-unknown-unknown -``` - -## Dependencies - -Key dependencies include: - -- `serde`, `serde_json`: For JSON serialization/deserialization. -- `futures-channel`, `futures-util`: For asynchronous stream and sink handling. -- `uuid`: For generating unique request IDs. -- `log`: For logging. -- `thiserror`: For error type definitions. - -**WASM-specific:** -- `gloo-net`: For WebSocket communication in WASM. -- `wasm-bindgen-futures`: To bridge Rust futures with JavaScript promises. - -**Native-specific:** -- `tokio-tungstenite`: For WebSocket communication in native environments. -- `tokio`: Asynchronous runtime for native applications. -- `url`: For URL parsing. diff --git a/cmd/Cargo.toml b/cmd/Cargo.toml deleted file mode 100644 index 75b9f33..0000000 --- a/cmd/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "circles_orchestrator" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -tokio = { version = "1", features = ["full"] } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -# clap = { version = "4.0", features = ["derive"], optional = true } # Optional for future args -dirs = "5.0" -log = "0.4" -env_logger = "0.10" -comfy-table = "7.0" # For table display - -# Path dependencies to other local crates -heromodels = { path = "../../db/heromodels" } # Changed from ourdb -rhai_engine = { path = "../../rhailib/src/engine" } -rhai_worker = { path = "../../rhailib/src/worker" } -# rhai_client is used by circle_ws_lib, not directly by orchestrator usually -circle_ws_lib = { path = "../server_ws" } \ No newline at end of file diff --git a/cmd/circles.json b/cmd/circles.json deleted file mode 100644 index 45b108e..0000000 --- a/cmd/circles.json +++ /dev/null @@ -1,5 +0,0 @@ -[ - { "id": 1, "name": "Alpha Circle", "port": 8091 }, - { "id": 2, "name": "Alpha Circle", "port": 8082 }, - { "id": 3, "name": "Beta Circle", "port": 8083 } -] \ No newline at end of file diff --git a/cmd/src/main.rs b/cmd/src/main.rs deleted file mode 100644 index 4831275..0000000 --- a/cmd/src/main.rs +++ /dev/null @@ -1,245 +0,0 @@ -use std::fs; -use std::path::PathBuf; -use std::sync::{Arc, Mutex}; -use serde::Deserialize; -use tokio::task::JoinHandle; -use tokio::sync::{oneshot, mpsc}; // For server handles and worker shutdown -use tokio::signal; -use std::time::Duration; - -use comfy_table::{Table, Row, Cell, ContentArrangement}; -use log::{info, error, warn, debug}; - -use heromodels::db::hero::{OurDB as HeroOurDB}; // Renamed to avoid conflict if OurDB is used from elsewhere -use rhai_engine::create_heromodels_engine; -use worker_lib::spawn_rhai_worker; // This now takes a shutdown_rx -use circle_ws_lib::spawn_circle_ws_server; // This now takes a server_handle_tx - -const DEFAULT_REDIS_URL: &str = "redis://127.0.0.1:6379"; - -#[derive(Deserialize, Debug, Clone)] -struct CircleConfig { - id: u32, - name: String, - port: u16, -} - -struct RunningCircleInfo { - config: CircleConfig, - db_path: PathBuf, - worker_queue: String, - ws_url: String, - - worker_handle: JoinHandle>>, - worker_shutdown_tx: mpsc::Sender<()>, // To signal worker to stop - - // Store the server handle for graceful shutdown, and its JoinHandle - ws_server_instance_handle: Arc>>, - ws_server_task_join_handle: JoinHandle>, - status: Arc>, -} - -#[tokio::main] -async fn main() -> Result<(), Box> { - std::env::set_var("RUST_LOG", "info,circles_orchestrator=debug,worker_lib=debug,circle_ws_lib=debug,rhai_client=debug,actix_server=info"); - env_logger::init(); - - info!("Starting Circles Orchestrator..."); - info!("Press Ctrl+C to initiate graceful shutdown."); - - let config_path = PathBuf::from("./circles.json"); - if !config_path.exists() { - error!("Configuration file not found at {:?}. Please create circles.json.", config_path); - return Err("circles.json not found".into()); - } - - let config_content = fs::read_to_string(config_path)?; - let circle_configs: Vec = serde_json::from_str(&config_content)?; - - if circle_configs.is_empty() { - warn!("No circle configurations found in circles.json. Exiting."); - return Ok(()); - } - info!("Loaded {} circle configurations.", circle_configs.len()); - - let mut running_circles_store: Vec>> = Vec::new(); - - for config in circle_configs { - info!("Initializing Circle ID: {}, Name: '{}', Port: {}", config.id, config.name, config.port); - let current_status = Arc::new(Mutex::new(format!("Initializing Circle {}", config.id))); - - let db_base_path = match dirs::home_dir() { - Some(path) => path.join(".hero").join("circles"), - None => { - error!("Failed to get user home directory for Circle ID {}.", config.id); - *current_status.lock().unwrap() = "Error: DB Path".to_string(); - // Not pushing to running_circles_store as it can't fully initialize - continue; - } - }; - let circle_db_path = db_base_path.join(config.id.to_string()); - if !circle_db_path.exists() { - if let Err(e) = fs::create_dir_all(&circle_db_path) { - error!("Failed to create database directory for Circle {}: {:?}. Error: {}", config.id, circle_db_path, e); - *current_status.lock().unwrap() = "Error: DB Create".to_string(); - continue; - } - info!("Created database directory for Circle {}: {:?}", config.id, circle_db_path); - } - - let db = match HeroOurDB::new(circle_db_path.clone(), false) { - Ok(db_instance) => Arc::new(db_instance), - Err(e) => { - error!("Failed to initialize heromodels::OurDB for Circle {}: {:?}", config.id, e); - *current_status.lock().unwrap() = "Error: DB Init".to_string(); - continue; - } - }; - info!("OurDB initialized for Circle {}", config.id); - *current_status.lock().unwrap() = format!("DB Ok for Circle {}", config.id); - - let engine = create_heromodels_engine(db.clone()); - info!("Rhai Engine created for Circle {}", config.id); - *current_status.lock().unwrap() = format!("Engine Ok for Circle {}", config.id); - - // Channel for worker shutdown - let (worker_shutdown_tx, worker_shutdown_rx) = mpsc::channel(1); // Buffer of 1 is fine - - let worker_handle = spawn_rhai_worker( - config.id, - config.name.clone(), - engine, // engine is Clone - DEFAULT_REDIS_URL.to_string(), - worker_shutdown_rx, // Pass the receiver - ); - info!("Rhai Worker spawned for Circle {}", config.id); - let worker_queue_name = format!("rhai_tasks:{}", config.name.replace(" ", "_").to_lowercase()); - *current_status.lock().unwrap() = format!("Worker Spawning for Circle {}", config.id); - - let (server_handle_tx, server_handle_rx) = oneshot::channel(); - let ws_server_task_join_handle = spawn_circle_ws_server( - config.id, - config.name.clone(), - config.port, - DEFAULT_REDIS_URL.to_string(), - server_handle_tx, - ); - info!("Circle WebSocket Server task spawned for Circle {} on port {}", config.id, config.port); - let ws_url = format!("ws://127.0.0.1:{}/ws", config.port); - *current_status.lock().unwrap() = format!("WS Server Spawning for Circle {}", config.id); - - let server_instance_handle_arc = Arc::new(Mutex::new(None)); - let server_instance_handle_clone = server_instance_handle_arc.clone(); - let status_clone_for_server_handle = current_status.clone(); - let circle_id_for_server_handle = config.id; - - tokio::spawn(async move { - match server_handle_rx.await { - Ok(handle) => { - *server_instance_handle_clone.lock().unwrap() = Some(handle); - *status_clone_for_server_handle.lock().unwrap() = format!("Running Circle {}", circle_id_for_server_handle); - info!("Received server handle for Circle {}", circle_id_for_server_handle); - } - Err(_) => { - *status_clone_for_server_handle.lock().unwrap() = format!("Error: No Server Handle for Circle {}", circle_id_for_server_handle); - error!("Failed to receive server handle for Circle {}", circle_id_for_server_handle); - } - } - }); - - running_circles_store.push(Arc::new(Mutex::new(RunningCircleInfo { - config, - db_path: circle_db_path, - worker_queue: worker_queue_name, - ws_url, - worker_handle, - worker_shutdown_tx, - ws_server_instance_handle: server_instance_handle_arc, - ws_server_task_join_handle, - status: current_status, // This is an Arc> - }))); - } - - info!("All configured circles have been processed. Initializing status table display loop."); - - let display_running_circles = running_circles_store.clone(); - let display_task = tokio::spawn(async move { - loop { - { // Scope for MutexGuard - let circles = display_running_circles.iter() - .map(|arc_info| arc_info.lock().unwrap()) - .collect::>(); // Collect locked guards - - let mut table = Table::new(); - table.set_content_arrangement(ContentArrangement::Dynamic); - table.set_header(vec!["Name", "ID", "Port", "Status", "DB Path", "Worker Queue", "WS URL"]); - - for circle_info in circles.iter() { - let mut row = Row::new(); - row.add_cell(Cell::new(&circle_info.config.name)); - row.add_cell(Cell::new(circle_info.config.id)); - row.add_cell(Cell::new(circle_info.config.port)); - row.add_cell(Cell::new(&*circle_info.status.lock().unwrap())); // Deref and lock status - row.add_cell(Cell::new(circle_info.db_path.to_string_lossy())); - row.add_cell(Cell::new(&circle_info.worker_queue)); - row.add_cell(Cell::new(&circle_info.ws_url)); - table.add_row(row); - } - // Clear terminal before printing (basic, might flicker) - // print!("\x1B[2J\x1B[1;1H"); - println!("\n--- Circles Status (updated every 5s, Ctrl+C to stop) ---\n{table}"); - } - tokio::time::sleep(Duration::from_secs(5)).await; - } - }); - - - signal::ctrl_c().await?; - info!("Ctrl-C received. Initiating graceful shutdown of all circles..."); - display_task.abort(); // Stop the display task - - for circle_arc in running_circles_store { - let mut circle_info = circle_arc.lock().unwrap(); - info!("Shutting down Circle ID: {}, Name: '{}'", circle_info.config.id, circle_info.config.name); - *circle_info.status.lock().unwrap() = "Shutting down".to_string(); - - // Signal worker to shut down - if circle_info.worker_shutdown_tx.send(()).await.is_err() { - warn!("Failed to send shutdown signal to worker for Circle {}. It might have already stopped.", circle_info.config.id); - } - - // Stop WS server - if let Some(server_handle) = circle_info.ws_server_instance_handle.lock().unwrap().take() { - info!("Stopping WebSocket server for Circle {}...", circle_info.config.id); - server_handle.stop(true).await; // Graceful stop - info!("WebSocket server for Circle {} stop signal sent.", circle_info.config.id); - } else { - warn!("No server handle to stop WebSocket server for Circle {}. It might not have started properly or already stopped.", circle_info.config.id); - } - } - - info!("Waiting for all tasks to complete..."); - for circle_arc in running_circles_store { - // We need to take ownership of handles to await them, or await mutable refs. - // This part is tricky if the MutexGuard is held. - // For simplicity, we'll just log that we've signaled them. - // Proper awaiting would require more careful structuring of JoinHandles. - let circle_id; - let circle_name; - { // Short scope for the lock - let circle_info = circle_arc.lock().unwrap(); - circle_id = circle_info.config.id; - circle_name = circle_info.config.name.clone(); - } - debug!("Orchestrator has signaled shutdown for Circle {} ({}). Main loop will await join handles if structured for it.", circle_name, circle_id); - // Actual awaiting of join handles would happen here if they were collected outside the Mutex. - // For now, the main function will exit after this loop. - } - - // Give some time for tasks to shut down before the main process exits. - // This is a simplified approach. A more robust solution would involve awaiting all JoinHandles. - tokio::time::sleep(Duration::from_secs(2)).await; - - info!("Orchestrator shut down complete."); - Ok(()) -} \ No newline at end of file diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..cb71373 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,137 @@ +# System Architecture + +This document provides a detailed overview of the `circles` project architecture. The project is composed of two core library crates, `server_ws` and `client_ws`, and a convenient `launcher` utility. + +## 1. High-Level Overview + +The `circles` project provides the core components for a client-server system designed to execute Rhai scripts in isolated environments. The `launcher` application is a utility that demonstrates how to use the `server_ws` and `client_ws` libraries to manage multiple server instances, but the libraries themselves are the fundamental building blocks. + +The core functionality revolves around: +- **Orchestration**: The `launcher` starts and stops multiple, independent WebSocket servers. +- **Client-Server Communication**: A JSON-RPC 2.0 API over WebSockets allows clients to execute scripts and authenticate. +- **Authentication**: An optional, robust `secp256k1` signature-based authentication mechanism secures the script execution endpoint. + +## 2. Component Architecture + +### 2.1. `server_ws` (Library) + +The `server_ws` crate provides the WebSocket server that handles client connections and API requests. Its key features include: +- **Web Framework**: Built using `Actix`, a powerful actor-based web framework for Rust. +- **WebSocket Handling**: Uses `actix-web-actors` to manage individual WebSocket sessions. Each client connection is handled by a `CircleWs` actor, ensuring that sessions are isolated from one another. +- **JSON-RPC API**: Exposes a JSON-RPC 2.0 API with methods for script execution (`play`) and authentication (`fetch_nonce`, `authenticate`). +- **Authentication Service**: The authentication flow is handled entirely within the WebSocket connection using the dedicated JSON-RPC methods. + +### 2.2. `client_ws` (Library) + +The `client_ws` crate is a WebSocket client library designed for interacting with the `server_ws`. It is engineered to be cross-platform: +- **Native**: For native Rust applications, it uses `tokio-tungstenite` for WebSocket communication. +- **WebAssembly (WASM)**: For browser-based applications, it uses `gloo-net` to integrate with the browser's native WebSocket API. +- **API**: Provides a flexible builder pattern for client construction and a high-level API (`CircleWsClient`) that abstracts the complexities of the WebSocket connection and the JSON-RPC protocol. + +### 2.3. `launcher` (Utility) + +The `launcher` is a command-line utility that demonstrates how to use the `server_ws` library. It is responsible for: +- **Configuration**: Reading a `circles.json` file that defines a list of Circle instances to run. +- **Orchestration**: Spawning a dedicated `server_ws` instance for each configured circle. +- **Lifecycle Management**: Managing the lifecycle of all spawned servers and their associated Rhai workers. + +### 2.2. `server_ws` + +The `server_ws` crate provides the WebSocket server that handles client connections and API requests. Its key features include: +- **Web Framework**: Built using `Actix`, a powerful actor-based web framework for Rust. +- **WebSocket Handling**: Uses `actix-web-actors` to manage individual WebSocket sessions. Each client connection is handled by a `CircleWs` actor, ensuring that sessions are isolated from one another. +- **JSON-RPC API**: Exposes a JSON-RPC 2.0 API with methods for script execution (`play`) and authentication (`fetch_nonce`, `authenticate`). +- **Authentication Service**: The authentication flow is handled entirely within the WebSocket connection using the dedicated JSON-RPC methods. + +### 2.3. `client_ws` + +The `client_ws` crate is a WebSocket client library designed for interacting with the `server_ws`. It is engineered to be cross-platform: +- **Native**: For native Rust applications, it uses `tokio-tungstenite` for WebSocket communication. +- **WebAssembly (WASM)**: For browser-based applications, it uses `gloo-net` to integrate with the browser's native WebSocket API. +- **API**: Provides a flexible builder pattern for client construction and a high-level API (`CircleWsClient`) that abstracts the complexities of the WebSocket connection and the JSON-RPC protocol. + +## 3. Communication and Protocols + +### 3.1. JSON-RPC 2.0 + +All client-server communication, including authentication, uses the JSON-RPC 2.0 protocol over the WebSocket connection. This provides a unified, lightweight, and well-defined structure for all interactions. The formal API contract is defined in the [openrpc.json](openrpc.json) file. + +### 3.2. Authentication Flow + +The authentication mechanism is designed to verify that a client possesses the private key corresponding to a given public key, without ever exposing the private key. The entire flow happens over the established WebSocket connection. + +**Sequence of Events:** +1. **Keypair**: The client is instantiated with a `secp256k1` keypair. +2. **Nonce Request**: The client sends a `fetch_nonce` JSON-RPC request containing its public key. +3. **Nonce Issuance**: The server generates a unique, single-use nonce, stores it in the actor's state, and returns it to the client in a JSON-RPC response. +4. **Signature Creation**: The client signs the received nonce with its private key. +5. **Authentication Request**: The client sends an `authenticate` JSON-RPC message, containing the public key and the generated signature. +6. **Signature Verification**: The server's WebSocket actor retrieves the stored nonce for the given public key and cryptographically verifies the signature. +7. **Session Update**: If verification is successful, the server marks the client's WebSocket session as "authenticated," granting it access to protected methods like `play`. + +## 4. Diagrams + +### 4.1. System Component Diagram + +```mermaid +graph TD + subgraph "User Machine" + Launcher[🚀 launcher] + CirclesConfig[circles.json] + Launcher -- Reads --> CirclesConfig + end + + subgraph "Spawned Processes" + direction LR + subgraph "Circle 1" + Server1[🌐 server_ws on port 9001] + end + subgraph "Circle 2" + Server2[🌐 server_ws on port 9002] + end + end + + Launcher -- Spawns & Manages --> Server1 + Launcher -- Spawns & Manages --> Server2 + + subgraph "Clients" + Client1[💻 client_ws] + Client2[💻 client_ws] + end + + Client1 -- Connects via WebSocket --> Server1 + Client2 -- Connects via WebSocket --> Server2 +``` + +### 4.2. Authentication Sequence Diagram + +```mermaid +sequenceDiagram + participant Client as client_ws + participant WsActor as CircleWs Actor (WebSocket) + + Client->>Client: Instantiate with keypair + + Note over Client: Has public_key, private_key + + Client->>+WsActor: JSON-RPC "fetch_nonce" (pubkey) + WsActor->>WsActor: generate_nonce() + WsActor->>WsActor: store_nonce(pubkey, nonce) + WsActor-->>-Client: JSON-RPC Response ({"nonce": "..."}) + + Client->>Client: sign(nonce, private_key) + + Note over Client: Has signature + + Client->>+WsActor: JSON-RPC "authenticate" (pubkey, signature) + WsActor->>WsActor: retrieve_nonce(pubkey) + WsActor->>WsActor: verify_signature(nonce, signature, pubkey) + + alt Signature is Valid + WsActor->>WsActor: Set session as authenticated + WsActor-->>-Client: JSON-RPC Response ({"authenticated": true}) + else Signature is Invalid + WsActor-->>-Client: JSON-RPC Error (Invalid Credentials) + end + + Note over WsActor: Subsequent "play" requests will include the authenticated public key. \ No newline at end of file diff --git a/docs/openrpc.json b/docs/openrpc.json new file mode 100644 index 0000000..15d7d87 --- /dev/null +++ b/docs/openrpc.json @@ -0,0 +1,126 @@ +{ + "openrpc": "1.2.6", + "info": { + "title": "Circles RPC", + "description": "A JSON-RPC API for interacting with a Circle, allowing script execution and authentication.", + "version": "1.0.0" + }, + "methods": [ + { + "name": "fetch_nonce", + "summary": "Fetches a cryptographic nonce for a given public key.", + "params": [ + { + "name": "pubkey", + "description": "The client's public key.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "result": { + "name": "nonce_response", + "description": "The cryptographic nonce to be signed.", + "schema": { + "$ref": "#/components/schemas/NonceResponse" + } + } + }, + { + "name": "authenticate", + "summary": "Authenticates the client using a signed nonce.", + "params": [ + { + "name": "credentials", + "description": "The authentication credentials, including the public key and the signed nonce.", + "required": true, + "schema": { + "$ref": "#/components/schemas/AuthCredentials" + } + } + ], + "result": { + "name": "authentication_status", + "description": "The result of the authentication attempt.", + "schema": { + "type": "object", + "properties": { + "authenticated": { + "type": "boolean" + } + }, + "required": ["authenticated"] + } + }, + "errors": [ + { + "code": -32002, + "message": "Invalid Credentials", + "description": "The provided credentials were not valid." + } + ] + }, + { + "name": "play", + "summary": "Executes a Rhai script and returns the result.", + "params": [ + { + "name": "script", + "description": "The Rhai script to execute.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "result": { + "name": "play_result", + "description": "The output of the executed script.", + "schema": { + "type": "string" + } + }, + "errors": [ + { + "code": -32000, + "message": "Execution Error", + "description": "The script failed to execute." + }, + { + "code": -32001, + "message": "Authentication Required", + "description": "The client must be authenticated to use this method." + } + ] + } + ], + "components": { + "schemas": { + "AuthCredentials": { + "type": "object", + "properties": { + "pubkey": { + "type": "string", + "description": "The public key of the client." + }, + "signature": { + "type": "string", + "description": "The nonce signed with the client's private key." + } + }, + "required": ["pubkey", "signature"] + }, + "NonceResponse": { + "type": "object", + "properties": { + "nonce": { + "type": "string", + "description": "The single-use cryptographic nonce." + } + }, + "required": ["nonce"] + } + } + } +} \ No newline at end of file diff --git a/examples/.gitkeep b/examples/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/examples/client_auth_example.rs b/examples/client_auth_example.rs new file mode 100644 index 0000000..f6fb6dd --- /dev/null +++ b/examples/client_auth_example.rs @@ -0,0 +1,146 @@ +//! End-to-end authentication example +//! +//! This example demonstrates the complete authentication flow with the simplified approach: +//! 1. Create a WebSocket client with authentication configuration +//! 2. Authenticate using private key +//! 3. Connect to WebSocket with authentication +//! 4. Send authenticated requests +//! +//! To run this example: +//! ```bash +//! cargo run --example client_auth_example --features "crypto" +//! ``` + +use circle_client_ws::CircleWsClientBuilder; +use log::{info, error}; +use std::time::Duration; +use tokio::time::sleep; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Initialize logging + env_logger::init(); + + info!("Starting simplified authentication example"); + + // Configuration + let ws_url = "ws://localhost:8080/ws".to_string(); + + // Example 1: Authenticate with private key + info!("=== Example 1: Private Key Authentication ==="); + let private_key = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"; + + let mut client = CircleWsClientBuilder::new(ws_url.clone()) + .with_keypair(private_key.to_string()) + .build(); + + match client.connect().await { + Ok(_) => { + info!("Successfully connected to WebSocket"); + } + Err(e) => { + error!("WebSocket connection failed: {}", e); + return Err(e.into()); + } + } + + match client.authenticate().await { + Ok(true) => { + info!("Successfully authenticated with private key"); + } + Ok(false) => { + error!("Authentication failed"); + } + Err(e) => { + error!("Private key authentication failed: {}", e); + } + } + + // Example 2: Send authenticated request + info!("=== Example 2: Send Authenticated Request ==="); + let script = "print('Hello from authenticated client!');".to_string(); + match client.play(script).await { + Ok(result) => { + info!("Play request successful: {}", result.output); + } + Err(e) => { + error!("Play request failed: {}", e); + } + } + + // Keep connection alive for a moment + sleep(Duration::from_secs(2)).await; + + // Disconnect + client.disconnect().await; + info!("Disconnected from WebSocket"); + + + // Example 3: Different private key authentication + info!("=== Example 3: Different Private Key Authentication ==="); + let private_key2 = "0xfedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321"; + + let mut client2 = CircleWsClientBuilder::new(ws_url.clone()) + .with_keypair(private_key2.to_string()) + .build(); + + match client2.connect().await { + Ok(_) => { + info!("Connected with second private key authentication"); + + match client2.authenticate().await { + Ok(true) => { + info!("Successfully authenticated with second private key"); + let script = "print('Hello from second auth!');".to_string(); + match client2.play(script).await { + Ok(result) => { + info!("Second auth request successful: {}", result.output); + } + Err(e) => { + error!("Second auth request failed: {}", e); + } + } + } + Ok(false) => { + error!("Second private key authentication failed"); + } + Err(e) => { + error!("Second private key authentication failed: {}", e); + } + } + + client2.disconnect().await; + } + Err(e) => { + error!("Second auth connection failed: {}", e); + } + } + + // Example 4: Non-authenticated connection (fallback) + info!("=== Example 4: Non-Authenticated Connection ==="); + let mut client3 = CircleWsClientBuilder::new(ws_url).build(); + + match client3.connect().await { + Ok(()) => { + info!("Connected without authentication (fallback mode)"); + + let script = "print('Hello from non-auth client!');".to_string(); + match client3.play(script).await { + Ok(result) => { + info!("Non-auth request successful: {}", result.output); + } + Err(e) => { + error!("Non-auth request failed: {}", e); + } + } + + client3.disconnect().await; + } + Err(e) => { + error!("Non-auth connection failed: {}", e); + } + } + + info!("Simplified authentication example completed"); + Ok(()) +} \ No newline at end of file diff --git a/examples/client_auth_simulation_example.rs b/examples/client_auth_simulation_example.rs new file mode 100644 index 0000000..b00fb42 --- /dev/null +++ b/examples/client_auth_simulation_example.rs @@ -0,0 +1,261 @@ +//! Authentication simulation example +//! +//! This example simulates the authentication flow without requiring a running server. +//! It demonstrates: +//! 1. Key generation and management +//! 2. Nonce request simulation +//! 3. Message signing and verification +//! 4. Credential management +//! 5. Authentication state checking + +use std::time::{SystemTime, UNIX_EPOCH}; +use log::info; + +// Import authentication modules +use circle_client_ws::CircleWsClientBuilder; + +#[cfg(feature = "crypto")] +use circle_client_ws::auth::{ + generate_private_key, + derive_public_key, + sign_message, + verify_signature, + AuthCredentials, + NonceResponse +}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Initialize logging + env_logger::init(); + + info!("🔐 Starting authentication simulation example"); + + // Step 1: Generate cryptographic keys + info!("🔑 Generating cryptographic keys..."); + + #[cfg(feature = "crypto")] + let (private_key, public_key) = { + let private_key = generate_private_key()?; + let public_key = derive_public_key(&private_key)?; + info!("✅ Generated private key: {}...", &private_key[..10]); + info!("✅ Derived public key: {}...", &public_key[..20]); + (private_key, public_key) + }; + + #[cfg(not(feature = "crypto"))] + let (private_key, _public_key) = { + let private_key = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef".to_string(); + let public_key = "04abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890".to_string(); + info!("📝 Using fallback keys (crypto feature disabled)"); + (private_key, public_key) + }; + + // Step 2: Simulate nonce request and response + info!("📡 Simulating nonce request..."); + let current_time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); + + let simulated_nonce = format!("nonce_{}_{}", current_time, "abcdef123456"); + let expires_at = current_time + 300; // 5 minutes from now + + #[cfg(feature = "crypto")] + let nonce_response = NonceResponse { + nonce: simulated_nonce.clone(), + expires_at, + }; + + info!("✅ Simulated nonce response:"); + info!(" Nonce: {}", simulated_nonce); + info!(" Expires at: {}", expires_at); + + // Step 3: Sign the nonce + info!("✍️ Signing nonce with private key..."); + + #[cfg(feature = "crypto")] + let signature = { + match sign_message(&private_key, &simulated_nonce) { + Ok(sig) => { + info!("✅ Signature created: {}...", &sig[..20]); + sig + } + Err(e) => { + error!("❌ Failed to sign message: {}", e); + return Err(e.into()); + } + } + }; + + #[cfg(not(feature = "crypto"))] + let _signature = { + info!("📝 Using fallback signature (crypto feature disabled)"); + "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890".to_string() + }; + + // Step 4: Verify the signature + info!("🔍 Verifying signature..."); + + #[cfg(feature = "crypto")] + { + match verify_signature(&public_key, &simulated_nonce, &signature) { + Ok(true) => info!("✅ Signature verification successful!"), + Ok(false) => { + error!("❌ Signature verification failed!"); + return Err("Signature verification failed".into()); + } + Err(e) => { + error!("❌ Signature verification error: {}", e); + return Err(e.into()); + } + } + } + + #[cfg(not(feature = "crypto"))] + { + info!("📝 Skipping signature verification (crypto feature disabled)"); + } + + // Step 5: Create authentication credentials + info!("📋 Creating authentication credentials..."); + #[cfg(feature = "crypto")] + let credentials = AuthCredentials::new( + public_key.clone(), + signature.clone(), + nonce_response.nonce.clone(), + expires_at + ); + + #[cfg(feature = "crypto")] + { + info!("✅ Credentials created:"); + info!(" Public key: {}...", &credentials.public_key()[..20]); + info!(" Signature: {}...", &credentials.signature()[..20]); + info!(" Nonce: {}", credentials.nonce()); + info!(" Expires at: {}", credentials.expires_at); + info!(" Is expired: {}", credentials.is_expired()); + info!(" Expires within 60s: {}", credentials.expires_within(60)); + info!(" Expires within 400s: {}", credentials.expires_within(400)); + } + + // Step 6: Create client with authentication + info!("🔌 Creating WebSocket client with authentication..."); + let _client = CircleWsClientBuilder::new("ws://localhost:8080/ws".to_string()) + .with_keypair(private_key.clone()) + .build(); + + info!("✅ Client created"); + + // Step 7: Demonstrate key rotation + info!("🔄 Demonstrating key rotation..."); + + #[cfg(feature = "crypto")] + { + let new_private_key = generate_private_key()?; + let new_public_key = derive_public_key(&new_private_key)?; + + info!("✅ Generated new keys:"); + info!(" New private key: {}...", &new_private_key[..10]); + info!(" New public key: {}...", &new_public_key[..20]); + + // Create new client with rotated keys + let _new_client = CircleWsClientBuilder::new("ws://localhost:8080/ws".to_string()) + .with_keypair(new_private_key) + .build(); + + info!("✅ Created client with rotated keys"); + } + + #[cfg(not(feature = "crypto"))] + { + info!("📝 Skipping key rotation (crypto feature disabled)"); + } + + // Step 8: Demonstrate credential expiration + info!("⏰ Demonstrating credential expiration..."); + + // Create credentials that expire soon + #[cfg(feature = "crypto")] + let short_lived_credentials = AuthCredentials::new( + public_key, + signature, + nonce_response.nonce, + current_time + 5 // Expires in 5 seconds + ); + + #[cfg(feature = "crypto")] + { + info!("✅ Created short-lived credentials:"); + info!(" Expires at: {}", short_lived_credentials.expires_at); + info!(" Is expired: {}", short_lived_credentials.is_expired()); + info!(" Expires within 10s: {}", short_lived_credentials.expires_within(10)); + + // Wait a moment and check again + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + info!("⏳ After 1 second:"); + info!(" Is expired: {}", short_lived_credentials.is_expired()); + info!(" Expires within 5s: {}", short_lived_credentials.expires_within(5)); + } + + info!("🎉 Authentication simulation completed successfully!"); + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_key_generation() { + #[cfg(feature = "crypto")] + { + let private_key = generate_private_key().unwrap(); + assert!(private_key.starts_with("0x")); + assert_eq!(private_key.len(), 66); // 0x + 64 hex chars + + let public_key = derive_public_key(&private_key).unwrap(); + assert!(public_key.starts_with("04")); + assert_eq!(public_key.len(), 130); // 04 + 128 hex chars (uncompressed) + } + } + + #[tokio::test] + async fn test_signature_flow() { + #[cfg(feature = "crypto")] + { + let private_key = generate_private_key().unwrap(); + let public_key = derive_public_key(&private_key).unwrap(); + let message = "test_nonce_12345"; + + let signature = sign_message(&private_key, message).unwrap(); + let is_valid = verify_signature(&public_key, message, &signature).unwrap(); + + assert!(is_valid); + } + } + + #[test] + fn test_credentials() { + let current_time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); + + #[cfg(feature = "crypto")] + let credentials = AuthCredentials::new( + "04abcdef...".to_string(), + "0x123456...".to_string(), + "nonce_123".to_string(), + current_time + 300 + ); + + #[cfg(feature = "crypto")] + { + assert!(!credentials.is_expired()); + assert!(credentials.expires_within(400)); + assert!(!credentials.expires_within(100)); + } + } +} \ No newline at end of file diff --git a/examples/ourworld/README.md b/examples/ourworld/README.md new file mode 100644 index 0000000..4eed443 --- /dev/null +++ b/examples/ourworld/README.md @@ -0,0 +1,68 @@ +# OurWorld Example + +This directory contains a complete example demonstrating a simulated "OurWorld" network, consisting of multiple interconnected "circles" (nodes). Each circle runs its own WebSocket server and a Rhai script worker, all managed by a central launcher. + +This example is designed to showcase: +1. **Multi-Circle Configuration**: How to define and configure multiple circles in a single `circles.json` file. +2. **Programmatic Launching**: How to use the `launcher` library to start, manage, and monitor these circles from within a Rust application. +3. **Dynamic Key Generation**: The launcher generates unique cryptographic keypairs for each circle upon startup. +4. **Output Generation**: How to use the `--output` functionality to get a JSON file containing the connection details (public keys, WebSocket URLs, etc.) for each running circle. +5. **Graceful Shutdown**: How the launcher handles a `Ctrl+C` signal to shut down all running circles cleanly. + +## Directory Contents + +- `circles.json`: The main configuration file that defines the 7 circles in the OurWorld network, including their names, ports, and associated Rhai scripts. +- `scripts/`: This directory contains the individual Rhai scripts that define the behavior of each circle. +- `ourworld_output.json` (Generated): This file is created after running the example and contains the runtime details of each circle. + +## How to Run the Example + +There are two ways to run this example, each demonstrating a different way to use the launcher. + +### 1. As a Root Example (Recommended) + +This method runs the launcher programmatically from the root of the workspace and is the simplest way to see the system in action. It uses the `examples/ourworld.rs` file. + +```sh +# From the root of the workspace +cargo run --example ourworld +``` + +### 2. As a Crate-Level Example + +This method runs a similar launcher, but as an example *within* the `launcher` crate itself. It uses the `src/launcher/examples/ourworld/main.rs` file. This is useful for testing the launcher in a more isolated context. + +```sh +# Navigate to the launcher's crate directory +cd src/launcher + +# Run the 'ourworld' example using cargo +cargo run --example ourworld +``` + +### 3. Using the Launcher Binary + +This method uses the main `launcher` binary to run the configuration, which is useful for testing the command-line interface. + +```sh +# From the root of the workspace +cargo run -p launcher -- --config examples/ourworld/circles.json --output examples/ourworld/ourworld_output.json +``` + +## What to Expect + +When you run the example, you will see log output indicating that the launcher is starting up, followed by a table summarizing the running circles: + +``` ++-----------------+------------------------------------------------------------------+------------------------------------------+-----------------------+ +| Name | Public Key | Worker Queue | WS URL | ++=================+==================================================================+==========================================+=======================+ +| OurWorld | 02... | rhai_tasks:02... | ws://127.0.0.1:9000/ws| ++-----------------+------------------------------------------------------------------+------------------------------------------+-----------------------+ +| Dunia Cybercity | 03... | rhai_tasks:03... | ws://127.0.0.1:9001/ws| ++-----------------+------------------------------------------------------------------+------------------------------------------+-----------------------+ +| ... (and so on for all 7 circles) | ++-----------------+------------------------------------------------------------------+------------------------------------------+-----------------------+ +``` + +The launcher will then wait for you to press `Ctrl+C` to initiate a graceful shutdown of all services. diff --git a/examples/ourworld/circles.json b/examples/ourworld/circles.json new file mode 100644 index 0000000..99ea795 --- /dev/null +++ b/examples/ourworld/circles.json @@ -0,0 +1,37 @@ +[ + { + "name": "OurWorld", + "port": 9000, + "script_path": "scripts/ourworld.rhai" + }, + { + "name": "Dunia Cybercity", + "port": 9001, + "script_path": "scripts/dunia_cybercity.rhai" + }, + { + "name": "Sikana", + "port": 9002, + "script_path": "scripts/sikana.rhai" + }, + { + "name": "Threefold", + "port": 9003, + "script_path": "scripts/threefold.rhai" + }, + { + "name": "Mbweni", + "port": 9004, + "script_path": "scripts/mbweni.rhai" + }, + { + "name": "Geomind", + "port": 9005, + "script_path": "scripts/geomind.rhai" + }, + { + "name": "Freezone", + "port": 9006, + "script_path": "scripts/freezone.rhai" + } +] \ No newline at end of file diff --git a/examples/ourworld/main.rs b/examples/ourworld/main.rs new file mode 100644 index 0000000..c4b7987 --- /dev/null +++ b/examples/ourworld/main.rs @@ -0,0 +1,83 @@ +//! Example of launching multiple circles and outputting their details to a file. +//! +//! This example demonstrates how to use the launcher library to start circles +//! programmatically, similar to how the `launcher` binary works. +//! +//! # Usage +//! +//! ```sh +//! cargo run --example ourworld +//! ``` +//! +//! This will: +//! 1. Read the `circles.json` file in the `examples/ourworld` directory. +//! 2. Launch all 7 circles defined in the config. +//! 3. Create a `ourworld_output.json` file in the same directory with the details. +//! 4. The launcher will run until you stop it with Ctrl+C. + +use launcher::{run_launcher, Args, CircleConfig}; +use std::fs; +use std::path::PathBuf; +use std::error::Error as StdError; +use log::{error, info}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + println!("--- Launching OurWorld Example Programmatically ---"); + + // The example is now at the root of the `examples` directory, + // so we can reference its assets directly. + let example_dir = PathBuf::from("./examples/ourworld"); + let config_path = example_dir.join("circles.json"); + let output_path = example_dir.join("ourworld_output.json"); + + println!("Using config file: {:?}", config_path); + println!("Output will be written to: {:?}", output_path); + + // Manually construct the arguments instead of parsing from command line. + // This is useful when embedding the launcher logic in another application. + let args = Args { + config_path: config_path.clone(), + output: Some(output_path), + debug: true, // Enable debug logging for the example + verbose: 2, // Set verbosity to max + }; + + if !config_path.exists() { + let msg = format!("Configuration file not found at {:?}", config_path); + error!("{}", msg); + return Err(msg.into()); + } + + let config_content = fs::read_to_string(&config_path)?; + + let mut circle_configs: Vec = match serde_json::from_str(&config_content) { + Ok(configs) => configs, + Err(e) => { + error!("Failed to parse {}: {}. Ensure it's a valid JSON array of CircleConfig.", config_path.display(), e); + return Err(Box::new(e) as Box); + } + }; + + // Make script paths relative to the project root by prepending the example directory path. + for config in &mut circle_configs { + if let Some(script_path) = &config.script_path { + let full_script_path = example_dir.join(script_path); + config.script_path = Some(full_script_path.to_string_lossy().into_owned()); + } + } + + if circle_configs.is_empty() { + info!("No circle configurations found in {}. Exiting.", config_path.display()); + return Ok(()); + } + + println!("Starting launcher... Press Ctrl+C to exit."); + + // The run_launcher function will setup logging, spawn circles, print the table, + // and wait for a shutdown signal (Ctrl+C). + run_launcher(args, circle_configs).await?; + + println!("--- OurWorld Example Finished ---"); + Ok(()) +} \ No newline at end of file diff --git a/examples/ourworld/ourworld_output.json b/examples/ourworld/ourworld_output.json new file mode 100644 index 0000000..9a741cf --- /dev/null +++ b/examples/ourworld/ourworld_output.json @@ -0,0 +1,51 @@ +[ + { + "name": "OurWorld", + "public_key": "02acbca22369b7f10584348056ae48779e04534cd34d37b7db0f4996f4d9d5e2a5", + "secret_key": "0c75df7425c799eb769049cf48891299761660396d772c687fa84cac5ec62570", + "worker_queue": "rhai_tasks:02acbca22369b7f10584348056ae48779e04534cd34d37b7db0f4996f4d9d5e2a5", + "ws_url": "ws://127.0.0.1:9000" + }, + { + "name": "Dunia Cybercity", + "public_key": "03d97b1a357c3ceb2f0eb78f8e2c71beda9190db5cb7e5112150105132effb35e0", + "secret_key": "4fad664608e8de55f0e5e1712241e71dc0864be125bc8633e50601fca8040791", + "worker_queue": "rhai_tasks:03d97b1a357c3ceb2f0eb78f8e2c71beda9190db5cb7e5112150105132effb35e0", + "ws_url": "ws://127.0.0.1:9001" + }, + { + "name": "Sikana", + "public_key": "0389595b28cfa98b45fa3c222db79892f3face65e7ef06d44e35d642967e45ed6e", + "secret_key": "fd59ddbf0d0bada725c911dc7e3317754ac552aa1ac84cfcb899bdfe3591e1f4", + "worker_queue": "rhai_tasks:0389595b28cfa98b45fa3c222db79892f3face65e7ef06d44e35d642967e45ed6e", + "ws_url": "ws://127.0.0.1:9002" + }, + { + "name": "Threefold", + "public_key": "03270f06ee4a7d42a9f6c22c9a7d6d0138cd15d4fa659026e2e6572fc6c6a6ea18", + "secret_key": "e204c0215bec80f74df49ea5b1592de3c6739cced339ace801bb7e158eb62231", + "worker_queue": "rhai_tasks:03270f06ee4a7d42a9f6c22c9a7d6d0138cd15d4fa659026e2e6572fc6c6a6ea18", + "ws_url": "ws://127.0.0.1:9003" + }, + { + "name": "Mbweni", + "public_key": "02724cf23e4ac95d0f14984f55c6955b3ca5ab2275d7ac2a2e4baf3596caf8606c", + "secret_key": "3c013e2e5f64692f044d17233e5fabdb0577629f898359115e69c3e594d5f43e", + "worker_queue": "rhai_tasks:02724cf23e4ac95d0f14984f55c6955b3ca5ab2275d7ac2a2e4baf3596caf8606c", + "ws_url": "ws://127.0.0.1:9004" + }, + { + "name": "Geomind", + "public_key": "030d8ceb47d445c92b7c3f13e9e134eebcb1d83beed424425f734164544eb58eed", + "secret_key": "dbd6dd383a6f56042710f72ce2ac68266650bbfb61432cdd139e98043b693e7c", + "worker_queue": "rhai_tasks:030d8ceb47d445c92b7c3f13e9e134eebcb1d83beed424425f734164544eb58eed", + "ws_url": "ws://127.0.0.1:9005" + }, + { + "name": "Freezone", + "public_key": "02dd21025c1d47421eccc2264c87538d41126da772a9a3f0e7226807fed89c9971", + "secret_key": "0c0c6b02c20fcd4ccfb2afeae249979ddd623e6f6edd17af4a9a5a19bc1b15ae", + "worker_queue": "rhai_tasks:02dd21025c1d47421eccc2264c87538d41126da772a9a3f0e7226807fed89c9971", + "ws_url": "ws://127.0.0.1:9006" + } +] \ No newline at end of file diff --git a/examples/ourworld/scripts/dunia_cybercity.rhai b/examples/ourworld/scripts/dunia_cybercity.rhai new file mode 100644 index 0000000..857de75 --- /dev/null +++ b/examples/ourworld/scripts/dunia_cybercity.rhai @@ -0,0 +1,249 @@ +// OurWorld Circle and Library Data + +new_circle() + .title("Dunia Cybercity") + .description("Creating a better world.") + .ws_url("ws://localhost:8091/ws") + .logo("🌍") + .save_circle(); + +let circle = get_circle(); + +print("--- Creating OurWorld Library ---"); + +// === IMAGES === +print("Creating images..."); + +let nature1 = save_image(new_image() + .title("Mountain Sunrise") + .description("Breathtaking sunrise over mountain peaks") + .url("https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800") + .width(800).height(600)); + +let nature2 = save_image(new_image() + .title("Ocean Waves") + .description("Powerful ocean waves crashing on rocks") + .url("https://images.unsplash.com/photo-1505142468610-359e7d316be0?w=800") + .width(800).height(600)); + +let nature3 = save_image(new_image() + .title("Forest Path") + .description("Peaceful path through ancient forest") + .url("https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=800") + .width(800).height(600)); + +let tech1 = save_image(new_image() + .title("Solar Panels") + .description("Modern solar panel installation") + .url("https://images.unsplash.com/photo-1509391366360-2e959784a276?w=800") + .width(800).height(600)); + +let tech2 = save_image(new_image() + .title("Wind Turbines") + .description("Wind turbines generating clean energy") + .url("https://images.unsplash.com/photo-1466611653911-95081537e5b7?w=800") + .width(800).height(600)); + +let space1 = save_image(new_image() + .title("Earth from Space") + .description("Our beautiful planet from orbit") + .url("https://images.unsplash.com/photo-1446776877081-d282a0f896e2?w=800") + .width(800).height(600)); + +let space2 = save_image(new_image() + .title("Galaxy Spiral") + .description("Stunning spiral galaxy in deep space") + .url("https://images.unsplash.com/photo-1502134249126-9f3755a50d78?w=800") + .width(800).height(600)); + +let city1 = save_image(new_image() + .title("Smart City") + .description("Futuristic smart city at night") + .url("https://images.unsplash.com/photo-1480714378408-67cf0d13bc1f?w=800") + .width(800).height(600)); + +// === PDFs === +print("Creating PDFs..."); + +let pdf1 = save_pdf(new_pdf() + .title("Climate Action Report 2024") + .description("Comprehensive analysis of global climate initiatives") + .url("https://www.ipcc.ch/site/assets/uploads/2018/02/ipcc_wg3_ar5_summary-for-policymakers.pdf") + .page_count(42)); + +let pdf2 = save_pdf(new_pdf() + .title("Sustainable Development Goals") + .description("UN SDG implementation guide") + .url("https://sdgs.un.org/sites/default/files/publications/21252030%20Agenda%20for%20Sustainable%20Development%20web.pdf") + .page_count(35)); + +let pdf3 = save_pdf(new_pdf() + .title("Renewable Energy Handbook") + .description("Technical guide to renewable energy systems") + .url("https://www.irena.org/-/media/Files/IRENA/Agency/Publication/2019/Oct/IRENA_Renewable-Energy-Statistics-2019.pdf") + .page_count(280)); + +let pdf4 = save_pdf(new_pdf() + .title("Blockchain for Good") + .description("How blockchain technology can solve global challenges") + .url("https://www.weforum.org/whitepapers/blockchain-beyond-the-hype") + .page_count(24)); + +let pdf5 = save_pdf(new_pdf() + .title("Future of Work Report") + .description("Analysis of changing work patterns and remote collaboration") + .url("https://www.mckinsey.com/featured-insights/future-of-work") + .page_count(156)); + +// === MARKDOWN DOCUMENTS === +print("Creating markdown documents..."); + +let md1 = save_markdown(new_markdown() + .title("OurWorld Mission Statement") + .description("Our vision for a better world") + .content("# OurWorld Mission\n\n## Vision\nTo create a more sustainable, equitable, and connected world through technology and collaboration.\n\n## Values\n- **Sustainability**: Every decision considers environmental impact\n- **Inclusivity**: Technology that serves everyone\n- **Transparency**: Open source and open governance\n- **Innovation**: Pushing boundaries for positive change\n\n## Goals\n1. Reduce global carbon footprint by 50% by 2030\n2. Provide internet access to 1 billion underserved people\n3. Create 10 million green jobs worldwide\n4. Establish 1000 sustainable communities")); + +let md2 = save_markdown(new_markdown() + .title("Getting Started Guide") + .description("How to join the OurWorld movement") + .content("# Getting Started with OurWorld\n\n## Welcome!\nThank you for joining our mission to create a better world.\n\n## First Steps\n1. **Explore**: Browse our projects and initiatives\n2. **Connect**: Join our community forums\n3. **Contribute**: Find ways to get involved\n4. **Learn**: Access our educational resources\n\n## Ways to Contribute\n- **Developers**: Contribute to open source projects\n- **Activists**: Organize local initiatives\n- **Educators**: Share knowledge and skills\n- **Investors**: Support sustainable ventures\n\n## Resources\n- [Community Forum](https://forum.ourworld.tf)\n- [Developer Portal](https://dev.ourworld.tf)\n- [Learning Hub](https://learn.ourworld.tf)")); + +let md3 = save_markdown(new_markdown() + .title("Technology Roadmap 2024") + .description("Our technical development plans") + .content("# Technology Roadmap 2024\n\n## Q1 Objectives\n- Launch decentralized identity system\n- Deploy carbon tracking blockchain\n- Release mobile app v2.0\n\n## Q2 Objectives\n- Implement AI-powered resource optimization\n- Launch peer-to-peer energy trading platform\n- Deploy IoT sensor network\n\n## Q3 Objectives\n- Release virtual collaboration spaces\n- Launch digital twin cities pilot\n- Implement quantum-safe encryption\n\n## Q4 Objectives\n- Deploy autonomous governance systems\n- Launch global impact measurement platform\n- Release AR/VR sustainability training")); + +let md4 = save_markdown(new_markdown() + .title("Community Guidelines") + .description("How we work together") + .content("# Community Guidelines\n\n## Our Principles\n- **Respect**: Treat everyone with dignity\n- **Collaboration**: Work together towards common goals\n- **Constructive**: Focus on solutions, not problems\n- **Inclusive**: Welcome diverse perspectives\n\n## Communication Standards\n- Use clear, respectful language\n- Listen actively to others\n- Provide constructive feedback\n- Share knowledge freely\n\n## Conflict Resolution\n1. Address issues directly and respectfully\n2. Seek to understand different viewpoints\n3. Involve mediators when needed\n4. Focus on solutions that benefit everyone")); + + +let investor = new_contact() + .name("Example Investor") + .save_contact(); + +let investors = new_group() + .name("Investors") + .description("A group for example inverstors of ourworld"); + +investors.add_contact(investor.id) + .save_group(); + +// === BOOKS === +print("Creating books..."); + +let sustainability_book = save_book(new_book() + .title("Sustainability Handbook") + .description("Complete guide to sustainable living and practices") + .add_page("# Introduction to Sustainability\n\nSustainability is about meeting our present needs without compromising the ability of future generations to meet their own needs.\n\n## Key Principles\n- Environmental stewardship\n- Social equity\n- Economic viability\n\n## Why It Matters\nOur planet faces unprecedented challenges from climate change, resource depletion, and environmental degradation.") + .add_page("# Energy Efficiency\n\n## Home Energy Savings\n- LED lighting reduces energy consumption by 75%\n- Smart thermostats can save 10-15% on heating/cooling\n- Energy-efficient appliances make a significant difference\n\n## Renewable Energy\n- Solar panels: Clean electricity from sunlight\n- Wind power: Harnessing natural wind currents\n- Hydroelectric: Using water flow for energy\n\n## Transportation\n- Electric vehicles reduce emissions\n- Public transit decreases individual carbon footprint\n- Cycling and walking for short distances") + .add_page("# Waste Reduction\n\n## The 5 R's\n1. **Refuse**: Say no to unnecessary items\n2. **Reduce**: Use less of what you need\n3. **Reuse**: Find new purposes for items\n4. **Recycle**: Process materials into new products\n5. **Rot**: Compost organic waste\n\n## Practical Tips\n- Use reusable bags and containers\n- Buy products with minimal packaging\n- Repair instead of replacing\n- Donate items you no longer need") + .add_page("# Sustainable Food\n\n## Local and Seasonal\n- Support local farmers and reduce transport emissions\n- Eat seasonal produce for better nutrition and taste\n- Visit farmers markets and join CSAs\n\n## Plant-Based Options\n- Reduce meat consumption for environmental benefits\n- Explore diverse plant proteins\n- Grow your own herbs and vegetables\n\n## Food Waste Prevention\n- Plan meals and make shopping lists\n- Store food properly to extend freshness\n- Use leftovers creatively") + .add_toc_entry(new_toc_entry().title("Introduction to Sustainability").page(0)) + .add_toc_entry(new_toc_entry().title("Energy Efficiency").page(1)) + .add_toc_entry(new_toc_entry().title("Waste Reduction").page(2)) + .add_toc_entry(new_toc_entry().title("Sustainable Food").page(3))); + +let tech_guide_book = save_book(new_book() + .title("Green Technology Guide") + .description("Understanding and implementing green technologies") + .add_page("# Green Technology Overview\n\nGreen technology, also known as clean technology, refers to the use of science and technology to create products and services that are environmentally friendly.\n\n## Categories\n- Renewable energy systems\n- Energy efficiency technologies\n- Pollution prevention and cleanup\n- Sustainable materials and manufacturing\n\n## Benefits\n- Reduced environmental impact\n- Lower operating costs\n- Improved public health\n- Economic opportunities") + .add_page("# Solar Technology\n\n## How Solar Works\nSolar panels convert sunlight directly into electricity using photovoltaic cells.\n\n## Types of Solar Systems\n- **Grid-tied**: Connected to the electrical grid\n- **Off-grid**: Standalone systems with battery storage\n- **Hybrid**: Combination of grid-tied and battery backup\n\n## Installation Considerations\n- Roof orientation and shading\n- Local climate and sun exposure\n- Energy consumption patterns\n- Available incentives and rebates") + .add_page("# Smart Home Technology\n\n## Automation Benefits\n- Optimized energy usage\n- Enhanced comfort and convenience\n- Remote monitoring and control\n- Predictive maintenance\n\n## Key Technologies\n- Smart thermostats\n- Automated lighting systems\n- Energy monitoring devices\n- Smart appliances\n- Home energy management systems") + .add_toc_entry(new_toc_entry().title("Green Technology Overview").page(0)) + .add_toc_entry(new_toc_entry().title("Solar Technology").page(1)) + .add_toc_entry(new_toc_entry().title("Smart Home Technology").page(2))); + +let community_book = save_book(new_book() + .title("Building Communities") + .description("Guide to creating sustainable and inclusive communities") + .add_page("# Community Building Fundamentals\n\n## What Makes a Strong Community?\n- Shared values and vision\n- Open communication channels\n- Mutual support and cooperation\n- Inclusive decision-making processes\n\n## Benefits of Strong Communities\n- Enhanced quality of life\n- Economic resilience\n- Social cohesion\n- Environmental stewardship") + .add_page("# Governance and Leadership\n\n## Collaborative Leadership\n- Distributed decision-making\n- Transparent processes\n- Accountability mechanisms\n- Conflict resolution systems\n\n## Community Engagement\n- Regular town halls and meetings\n- Digital participation platforms\n- Volunteer coordination\n- Feedback and improvement cycles") + .add_toc_entry(new_toc_entry().title("Community Building Fundamentals").page(0)) + .add_toc_entry(new_toc_entry().title("Governance and Leadership").page(1))); + +// === SLIDES === +print("Creating slides..."); + +let climate_slides = save_slides(new_slides() + .title("Climate Change Awareness") + .description("Visual presentation on climate change impacts and solutions") + .add_slide("https://images.unsplash.com/photo-1569163139394-de4e4f43e4e3?w=1200", "Global Temperature Rise") + .add_slide("https://images.unsplash.com/photo-1578662996442-48f60103fc96?w=1200", "Melting Ice Caps") + .add_slide("https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=1200", "Extreme Weather Events") + .add_slide("https://images.unsplash.com/photo-1473341304170-971dccb5ac1e?w=1200", "Renewable Energy Solutions") + .add_slide("https://images.unsplash.com/photo-1497436072909-f5e4be1dffea?w=1200", "Sustainable Transportation")); + +let innovation_slides = save_slides(new_slides() + .title("Innovation Showcase") + .description("Cutting-edge technologies for a sustainable future") + .add_slide("https://images.unsplash.com/photo-1518709268805-4e9042af2176?w=1200", "AI and Machine Learning") + .add_slide("https://images.unsplash.com/photo-1639322537228-f710d846310a?w=1200", "Blockchain Technology") + .add_slide("https://images.unsplash.com/photo-1581092160562-40aa08e78837?w=1200", "IoT and Smart Cities") + .add_slide("https://images.unsplash.com/photo-1581092918056-0c4c3acd3789?w=1200", "Quantum Computing") + .add_slide("https://images.unsplash.com/photo-1581092162384-8987c1d64718?w=1200", "Biotechnology Advances")); + +let nature_slides = save_slides(new_slides() + .title("Biodiversity Gallery") + .description("Celebrating Earth's incredible biodiversity") + .add_slide("https://images.unsplash.com/photo-1564349683136-77e08dba1ef7?w=1200", "Tropical Rainforest") + .add_slide("https://images.unsplash.com/photo-1559827260-dc66d52bef19?w=1200", "Coral Reef Ecosystem") + .add_slide("https://images.unsplash.com/photo-1551698618-1dfe5d97d256?w=1200", "Arctic Wildlife") + .add_slide("https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1200", "Mountain Ecosystems")); + +// === COLLECTIONS === +print("Creating collections..."); + +let nature_collection = save_collection(new_collection() + .title("Nature & Environment") + .description("Beautiful images and resources about our natural world") + .add_image(nature1.id) + .add_image(nature2.id) + .add_image(nature3.id) + .add_pdf(pdf1.id) + .add_markdown(md1.id) + .add_book(sustainability_book.id) + .add_slides(nature_slides.id)); + +let technology_collection = save_collection(new_collection() + .title("Sustainable Technology") + .description("Innovations driving positive change") + .add_image(tech1.id) + .add_image(tech2.id) + .add_pdf(pdf3.id) + .add_pdf(pdf4.id) + .add_markdown(md3.id) + .add_book(tech_guide_book.id) + .add_slides(innovation_slides.id)); + +let space_collection = save_collection(new_collection() + .title("Space & Cosmos") + .description("Exploring the universe and our place in it") + .add_image(space1.id) + .add_image(space2.id) + .add_pdf(pdf2.id) + .add_markdown(md2.id)); + +let community_collection = save_collection(new_collection() + .title("Community & Collaboration") + .description("Building better communities together") + .add_image(city1.id) + .add_pdf(pdf5.id) + .add_markdown(md4.id) + .add_book(community_book.id)); + +let climate_collection = save_collection(new_collection() + .title("Climate Action") + .description("Understanding and addressing climate change") + .add_slides(climate_slides.id) + .add_pdf(pdf1.id) + .add_markdown(md1.id)); + +print("✅ OurWorld library created successfully!"); +print("📚 Collections: 5"); +print("🖼️ Images: 8"); +print("📄 PDFs: 5"); +print("📝 Markdown docs: 4"); +print("📖 Books: 3"); +print("🎞️ Slide shows: 3"); \ No newline at end of file diff --git a/examples/ourworld/scripts/freezone.rhai b/examples/ourworld/scripts/freezone.rhai new file mode 100644 index 0000000..7b234d5 --- /dev/null +++ b/examples/ourworld/scripts/freezone.rhai @@ -0,0 +1,249 @@ +// OurWorld Circle and Library Data + +new_circle() + .title("Zanzibar Digital Freezone") + .description("Creating a better world.") + .ws_url("ws://localhost:8096/ws") + .logo("🌍") + .save_circle(); + +let circle = get_circle(); + +print("--- Creating OurWorld Library ---"); + +// === IMAGES === +print("Creating images..."); + +let nature1 = save_image(new_image() + .title("Mountain Sunrise") + .description("Breathtaking sunrise over mountain peaks") + .url("https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800") + .width(800).height(600)); + +let nature2 = save_image(new_image() + .title("Ocean Waves") + .description("Powerful ocean waves crashing on rocks") + .url("https://images.unsplash.com/photo-1505142468610-359e7d316be0?w=800") + .width(800).height(600)); + +let nature3 = save_image(new_image() + .title("Forest Path") + .description("Peaceful path through ancient forest") + .url("https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=800") + .width(800).height(600)); + +let tech1 = save_image(new_image() + .title("Solar Panels") + .description("Modern solar panel installation") + .url("https://images.unsplash.com/photo-1509391366360-2e959784a276?w=800") + .width(800).height(600)); + +let tech2 = save_image(new_image() + .title("Wind Turbines") + .description("Wind turbines generating clean energy") + .url("https://images.unsplash.com/photo-1466611653911-95081537e5b7?w=800") + .width(800).height(600)); + +let space1 = save_image(new_image() + .title("Earth from Space") + .description("Our beautiful planet from orbit") + .url("https://images.unsplash.com/photo-1446776877081-d282a0f896e2?w=800") + .width(800).height(600)); + +let space2 = save_image(new_image() + .title("Galaxy Spiral") + .description("Stunning spiral galaxy in deep space") + .url("https://images.unsplash.com/photo-1502134249126-9f3755a50d78?w=800") + .width(800).height(600)); + +let city1 = save_image(new_image() + .title("Smart City") + .description("Futuristic smart city at night") + .url("https://images.unsplash.com/photo-1480714378408-67cf0d13bc1f?w=800") + .width(800).height(600)); + +// === PDFs === +print("Creating PDFs..."); + +let pdf1 = save_pdf(new_pdf() + .title("Climate Action Report 2024") + .description("Comprehensive analysis of global climate initiatives") + .url("https://www.ipcc.ch/site/assets/uploads/2018/02/ipcc_wg3_ar5_summary-for-policymakers.pdf") + .page_count(42)); + +let pdf2 = save_pdf(new_pdf() + .title("Sustainable Development Goals") + .description("UN SDG implementation guide") + .url("https://sdgs.un.org/sites/default/files/publications/21252030%20Agenda%20for%20Sustainable%20Development%20web.pdf") + .page_count(35)); + +let pdf3 = save_pdf(new_pdf() + .title("Renewable Energy Handbook") + .description("Technical guide to renewable energy systems") + .url("https://www.irena.org/-/media/Files/IRENA/Agency/Publication/2019/Oct/IRENA_Renewable-Energy-Statistics-2019.pdf") + .page_count(280)); + +let pdf4 = save_pdf(new_pdf() + .title("Blockchain for Good") + .description("How blockchain technology can solve global challenges") + .url("https://www.weforum.org/whitepapers/blockchain-beyond-the-hype") + .page_count(24)); + +let pdf5 = save_pdf(new_pdf() + .title("Future of Work Report") + .description("Analysis of changing work patterns and remote collaboration") + .url("https://www.mckinsey.com/featured-insights/future-of-work") + .page_count(156)); + +// === MARKDOWN DOCUMENTS === +print("Creating markdown documents..."); + +let md1 = save_markdown(new_markdown() + .title("OurWorld Mission Statement") + .description("Our vision for a better world") + .content("# OurWorld Mission\n\n## Vision\nTo create a more sustainable, equitable, and connected world through technology and collaboration.\n\n## Values\n- **Sustainability**: Every decision considers environmental impact\n- **Inclusivity**: Technology that serves everyone\n- **Transparency**: Open source and open governance\n- **Innovation**: Pushing boundaries for positive change\n\n## Goals\n1. Reduce global carbon footprint by 50% by 2030\n2. Provide internet access to 1 billion underserved people\n3. Create 10 million green jobs worldwide\n4. Establish 1000 sustainable communities")); + +let md2 = save_markdown(new_markdown() + .title("Getting Started Guide") + .description("How to join the OurWorld movement") + .content("# Getting Started with OurWorld\n\n## Welcome!\nThank you for joining our mission to create a better world.\n\n## First Steps\n1. **Explore**: Browse our projects and initiatives\n2. **Connect**: Join our community forums\n3. **Contribute**: Find ways to get involved\n4. **Learn**: Access our educational resources\n\n## Ways to Contribute\n- **Developers**: Contribute to open source projects\n- **Activists**: Organize local initiatives\n- **Educators**: Share knowledge and skills\n- **Investors**: Support sustainable ventures\n\n## Resources\n- [Community Forum](https://forum.ourworld.tf)\n- [Developer Portal](https://dev.ourworld.tf)\n- [Learning Hub](https://learn.ourworld.tf)")); + +let md3 = save_markdown(new_markdown() + .title("Technology Roadmap 2024") + .description("Our technical development plans") + .content("# Technology Roadmap 2024\n\n## Q1 Objectives\n- Launch decentralized identity system\n- Deploy carbon tracking blockchain\n- Release mobile app v2.0\n\n## Q2 Objectives\n- Implement AI-powered resource optimization\n- Launch peer-to-peer energy trading platform\n- Deploy IoT sensor network\n\n## Q3 Objectives\n- Release virtual collaboration spaces\n- Launch digital twin cities pilot\n- Implement quantum-safe encryption\n\n## Q4 Objectives\n- Deploy autonomous governance systems\n- Launch global impact measurement platform\n- Release AR/VR sustainability training")); + +let md4 = save_markdown(new_markdown() + .title("Community Guidelines") + .description("How we work together") + .content("# Community Guidelines\n\n## Our Principles\n- **Respect**: Treat everyone with dignity\n- **Collaboration**: Work together towards common goals\n- **Constructive**: Focus on solutions, not problems\n- **Inclusive**: Welcome diverse perspectives\n\n## Communication Standards\n- Use clear, respectful language\n- Listen actively to others\n- Provide constructive feedback\n- Share knowledge freely\n\n## Conflict Resolution\n1. Address issues directly and respectfully\n2. Seek to understand different viewpoints\n3. Involve mediators when needed\n4. Focus on solutions that benefit everyone")); + + +let investor = new_contact() + .name("Example Investor") + .save_contact(); + +let investors = new_group() + .name("Investors") + .description("A group for example inverstors of ourworld"); + +investors.add_contact(investor.id) + .save_group(); + +// === BOOKS === +print("Creating books..."); + +let sustainability_book = save_book(new_book() + .title("Sustainability Handbook") + .description("Complete guide to sustainable living and practices") + .add_page("# Introduction to Sustainability\n\nSustainability is about meeting our present needs without compromising the ability of future generations to meet their own needs.\n\n## Key Principles\n- Environmental stewardship\n- Social equity\n- Economic viability\n\n## Why It Matters\nOur planet faces unprecedented challenges from climate change, resource depletion, and environmental degradation.") + .add_page("# Energy Efficiency\n\n## Home Energy Savings\n- LED lighting reduces energy consumption by 75%\n- Smart thermostats can save 10-15% on heating/cooling\n- Energy-efficient appliances make a significant difference\n\n## Renewable Energy\n- Solar panels: Clean electricity from sunlight\n- Wind power: Harnessing natural wind currents\n- Hydroelectric: Using water flow for energy\n\n## Transportation\n- Electric vehicles reduce emissions\n- Public transit decreases individual carbon footprint\n- Cycling and walking for short distances") + .add_page("# Waste Reduction\n\n## The 5 R's\n1. **Refuse**: Say no to unnecessary items\n2. **Reduce**: Use less of what you need\n3. **Reuse**: Find new purposes for items\n4. **Recycle**: Process materials into new products\n5. **Rot**: Compost organic waste\n\n## Practical Tips\n- Use reusable bags and containers\n- Buy products with minimal packaging\n- Repair instead of replacing\n- Donate items you no longer need") + .add_page("# Sustainable Food\n\n## Local and Seasonal\n- Support local farmers and reduce transport emissions\n- Eat seasonal produce for better nutrition and taste\n- Visit farmers markets and join CSAs\n\n## Plant-Based Options\n- Reduce meat consumption for environmental benefits\n- Explore diverse plant proteins\n- Grow your own herbs and vegetables\n\n## Food Waste Prevention\n- Plan meals and make shopping lists\n- Store food properly to extend freshness\n- Use leftovers creatively") + .add_toc_entry(new_toc_entry().title("Introduction to Sustainability").page(0)) + .add_toc_entry(new_toc_entry().title("Energy Efficiency").page(1)) + .add_toc_entry(new_toc_entry().title("Waste Reduction").page(2)) + .add_toc_entry(new_toc_entry().title("Sustainable Food").page(3))); + +let tech_guide_book = save_book(new_book() + .title("Green Technology Guide") + .description("Understanding and implementing green technologies") + .add_page("# Green Technology Overview\n\nGreen technology, also known as clean technology, refers to the use of science and technology to create products and services that are environmentally friendly.\n\n## Categories\n- Renewable energy systems\n- Energy efficiency technologies\n- Pollution prevention and cleanup\n- Sustainable materials and manufacturing\n\n## Benefits\n- Reduced environmental impact\n- Lower operating costs\n- Improved public health\n- Economic opportunities") + .add_page("# Solar Technology\n\n## How Solar Works\nSolar panels convert sunlight directly into electricity using photovoltaic cells.\n\n## Types of Solar Systems\n- **Grid-tied**: Connected to the electrical grid\n- **Off-grid**: Standalone systems with battery storage\n- **Hybrid**: Combination of grid-tied and battery backup\n\n## Installation Considerations\n- Roof orientation and shading\n- Local climate and sun exposure\n- Energy consumption patterns\n- Available incentives and rebates") + .add_page("# Smart Home Technology\n\n## Automation Benefits\n- Optimized energy usage\n- Enhanced comfort and convenience\n- Remote monitoring and control\n- Predictive maintenance\n\n## Key Technologies\n- Smart thermostats\n- Automated lighting systems\n- Energy monitoring devices\n- Smart appliances\n- Home energy management systems") + .add_toc_entry(new_toc_entry().title("Green Technology Overview").page(0)) + .add_toc_entry(new_toc_entry().title("Solar Technology").page(1)) + .add_toc_entry(new_toc_entry().title("Smart Home Technology").page(2))); + +let community_book = save_book(new_book() + .title("Building Communities") + .description("Guide to creating sustainable and inclusive communities") + .add_page("# Community Building Fundamentals\n\n## What Makes a Strong Community?\n- Shared values and vision\n- Open communication channels\n- Mutual support and cooperation\n- Inclusive decision-making processes\n\n## Benefits of Strong Communities\n- Enhanced quality of life\n- Economic resilience\n- Social cohesion\n- Environmental stewardship") + .add_page("# Governance and Leadership\n\n## Collaborative Leadership\n- Distributed decision-making\n- Transparent processes\n- Accountability mechanisms\n- Conflict resolution systems\n\n## Community Engagement\n- Regular town halls and meetings\n- Digital participation platforms\n- Volunteer coordination\n- Feedback and improvement cycles") + .add_toc_entry(new_toc_entry().title("Community Building Fundamentals").page(0)) + .add_toc_entry(new_toc_entry().title("Governance and Leadership").page(1))); + +// === SLIDES === +print("Creating slides..."); + +let climate_slides = save_slides(new_slides() + .title("Climate Change Awareness") + .description("Visual presentation on climate change impacts and solutions") + .add_slide("https://images.unsplash.com/photo-1569163139394-de4e4f43e4e3?w=1200", "Global Temperature Rise") + .add_slide("https://images.unsplash.com/photo-1578662996442-48f60103fc96?w=1200", "Melting Ice Caps") + .add_slide("https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=1200", "Extreme Weather Events") + .add_slide("https://images.unsplash.com/photo-1473341304170-971dccb5ac1e?w=1200", "Renewable Energy Solutions") + .add_slide("https://images.unsplash.com/photo-1497436072909-f5e4be1dffea?w=1200", "Sustainable Transportation")); + +let innovation_slides = save_slides(new_slides() + .title("Innovation Showcase") + .description("Cutting-edge technologies for a sustainable future") + .add_slide("https://images.unsplash.com/photo-1518709268805-4e9042af2176?w=1200", "AI and Machine Learning") + .add_slide("https://images.unsplash.com/photo-1639322537228-f710d846310a?w=1200", "Blockchain Technology") + .add_slide("https://images.unsplash.com/photo-1581092160562-40aa08e78837?w=1200", "IoT and Smart Cities") + .add_slide("https://images.unsplash.com/photo-1581092918056-0c4c3acd3789?w=1200", "Quantum Computing") + .add_slide("https://images.unsplash.com/photo-1581092162384-8987c1d64718?w=1200", "Biotechnology Advances")); + +let nature_slides = save_slides(new_slides() + .title("Biodiversity Gallery") + .description("Celebrating Earth's incredible biodiversity") + .add_slide("https://images.unsplash.com/photo-1564349683136-77e08dba1ef7?w=1200", "Tropical Rainforest") + .add_slide("https://images.unsplash.com/photo-1559827260-dc66d52bef19?w=1200", "Coral Reef Ecosystem") + .add_slide("https://images.unsplash.com/photo-1551698618-1dfe5d97d256?w=1200", "Arctic Wildlife") + .add_slide("https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1200", "Mountain Ecosystems")); + +// === COLLECTIONS === +print("Creating collections..."); + +let nature_collection = save_collection(new_collection() + .title("Nature & Environment") + .description("Beautiful images and resources about our natural world") + .add_image(nature1.id) + .add_image(nature2.id) + .add_image(nature3.id) + .add_pdf(pdf1.id) + .add_markdown(md1.id) + .add_book(sustainability_book.id) + .add_slides(nature_slides.id)); + +let technology_collection = save_collection(new_collection() + .title("Sustainable Technology") + .description("Innovations driving positive change") + .add_image(tech1.id) + .add_image(tech2.id) + .add_pdf(pdf3.id) + .add_pdf(pdf4.id) + .add_markdown(md3.id) + .add_book(tech_guide_book.id) + .add_slides(innovation_slides.id)); + +let space_collection = save_collection(new_collection() + .title("Space & Cosmos") + .description("Exploring the universe and our place in it") + .add_image(space1.id) + .add_image(space2.id) + .add_pdf(pdf2.id) + .add_markdown(md2.id)); + +let community_collection = save_collection(new_collection() + .title("Community & Collaboration") + .description("Building better communities together") + .add_image(city1.id) + .add_pdf(pdf5.id) + .add_markdown(md4.id) + .add_book(community_book.id)); + +let climate_collection = save_collection(new_collection() + .title("Climate Action") + .description("Understanding and addressing climate change") + .add_slides(climate_slides.id) + .add_pdf(pdf1.id) + .add_markdown(md1.id)); + +print("✅ OurWorld library created successfully!"); +print("📚 Collections: 5"); +print("🖼️ Images: 8"); +print("📄 PDFs: 5"); +print("📝 Markdown docs: 4"); +print("📖 Books: 3"); +print("🎞️ Slide shows: 3"); \ No newline at end of file diff --git a/examples/ourworld/scripts/geomind.rhai b/examples/ourworld/scripts/geomind.rhai new file mode 100644 index 0000000..6704c6b --- /dev/null +++ b/examples/ourworld/scripts/geomind.rhai @@ -0,0 +1,249 @@ +// OurWorld Circle and Library Data + +new_circle() + .title("Geomind") + .description("Creating a better world.") + .ws_url("ws://localhost:8095/ws") + .logo("🌍") + .save_circle(); + +let circle = get_circle(); + +print("--- Creating OurWorld Library ---"); + +// === IMAGES === +print("Creating images..."); + +let nature1 = save_image(new_image() + .title("Mountain Sunrise") + .description("Breathtaking sunrise over mountain peaks") + .url("https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800") + .width(800).height(600)); + +let nature2 = save_image(new_image() + .title("Ocean Waves") + .description("Powerful ocean waves crashing on rocks") + .url("https://images.unsplash.com/photo-1505142468610-359e7d316be0?w=800") + .width(800).height(600)); + +let nature3 = save_image(new_image() + .title("Forest Path") + .description("Peaceful path through ancient forest") + .url("https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=800") + .width(800).height(600)); + +let tech1 = save_image(new_image() + .title("Solar Panels") + .description("Modern solar panel installation") + .url("https://images.unsplash.com/photo-1509391366360-2e959784a276?w=800") + .width(800).height(600)); + +let tech2 = save_image(new_image() + .title("Wind Turbines") + .description("Wind turbines generating clean energy") + .url("https://images.unsplash.com/photo-1466611653911-95081537e5b7?w=800") + .width(800).height(600)); + +let space1 = save_image(new_image() + .title("Earth from Space") + .description("Our beautiful planet from orbit") + .url("https://images.unsplash.com/photo-1446776877081-d282a0f896e2?w=800") + .width(800).height(600)); + +let space2 = save_image(new_image() + .title("Galaxy Spiral") + .description("Stunning spiral galaxy in deep space") + .url("https://images.unsplash.com/photo-1502134249126-9f3755a50d78?w=800") + .width(800).height(600)); + +let city1 = save_image(new_image() + .title("Smart City") + .description("Futuristic smart city at night") + .url("https://images.unsplash.com/photo-1480714378408-67cf0d13bc1f?w=800") + .width(800).height(600)); + +// === PDFs === +print("Creating PDFs..."); + +let pdf1 = save_pdf(new_pdf() + .title("Climate Action Report 2024") + .description("Comprehensive analysis of global climate initiatives") + .url("https://www.ipcc.ch/site/assets/uploads/2018/02/ipcc_wg3_ar5_summary-for-policymakers.pdf") + .page_count(42)); + +let pdf2 = save_pdf(new_pdf() + .title("Sustainable Development Goals") + .description("UN SDG implementation guide") + .url("https://sdgs.un.org/sites/default/files/publications/21252030%20Agenda%20for%20Sustainable%20Development%20web.pdf") + .page_count(35)); + +let pdf3 = save_pdf(new_pdf() + .title("Renewable Energy Handbook") + .description("Technical guide to renewable energy systems") + .url("https://www.irena.org/-/media/Files/IRENA/Agency/Publication/2019/Oct/IRENA_Renewable-Energy-Statistics-2019.pdf") + .page_count(280)); + +let pdf4 = save_pdf(new_pdf() + .title("Blockchain for Good") + .description("How blockchain technology can solve global challenges") + .url("https://www.weforum.org/whitepapers/blockchain-beyond-the-hype") + .page_count(24)); + +let pdf5 = save_pdf(new_pdf() + .title("Future of Work Report") + .description("Analysis of changing work patterns and remote collaboration") + .url("https://www.mckinsey.com/featured-insights/future-of-work") + .page_count(156)); + +// === MARKDOWN DOCUMENTS === +print("Creating markdown documents..."); + +let md1 = save_markdown(new_markdown() + .title("OurWorld Mission Statement") + .description("Our vision for a better world") + .content("# OurWorld Mission\n\n## Vision\nTo create a more sustainable, equitable, and connected world through technology and collaboration.\n\n## Values\n- **Sustainability**: Every decision considers environmental impact\n- **Inclusivity**: Technology that serves everyone\n- **Transparency**: Open source and open governance\n- **Innovation**: Pushing boundaries for positive change\n\n## Goals\n1. Reduce global carbon footprint by 50% by 2030\n2. Provide internet access to 1 billion underserved people\n3. Create 10 million green jobs worldwide\n4. Establish 1000 sustainable communities")); + +let md2 = save_markdown(new_markdown() + .title("Getting Started Guide") + .description("How to join the OurWorld movement") + .content("# Getting Started with OurWorld\n\n## Welcome!\nThank you for joining our mission to create a better world.\n\n## First Steps\n1. **Explore**: Browse our projects and initiatives\n2. **Connect**: Join our community forums\n3. **Contribute**: Find ways to get involved\n4. **Learn**: Access our educational resources\n\n## Ways to Contribute\n- **Developers**: Contribute to open source projects\n- **Activists**: Organize local initiatives\n- **Educators**: Share knowledge and skills\n- **Investors**: Support sustainable ventures\n\n## Resources\n- [Community Forum](https://forum.ourworld.tf)\n- [Developer Portal](https://dev.ourworld.tf)\n- [Learning Hub](https://learn.ourworld.tf)")); + +let md3 = save_markdown(new_markdown() + .title("Technology Roadmap 2024") + .description("Our technical development plans") + .content("# Technology Roadmap 2024\n\n## Q1 Objectives\n- Launch decentralized identity system\n- Deploy carbon tracking blockchain\n- Release mobile app v2.0\n\n## Q2 Objectives\n- Implement AI-powered resource optimization\n- Launch peer-to-peer energy trading platform\n- Deploy IoT sensor network\n\n## Q3 Objectives\n- Release virtual collaboration spaces\n- Launch digital twin cities pilot\n- Implement quantum-safe encryption\n\n## Q4 Objectives\n- Deploy autonomous governance systems\n- Launch global impact measurement platform\n- Release AR/VR sustainability training")); + +let md4 = save_markdown(new_markdown() + .title("Community Guidelines") + .description("How we work together") + .content("# Community Guidelines\n\n## Our Principles\n- **Respect**: Treat everyone with dignity\n- **Collaboration**: Work together towards common goals\n- **Constructive**: Focus on solutions, not problems\n- **Inclusive**: Welcome diverse perspectives\n\n## Communication Standards\n- Use clear, respectful language\n- Listen actively to others\n- Provide constructive feedback\n- Share knowledge freely\n\n## Conflict Resolution\n1. Address issues directly and respectfully\n2. Seek to understand different viewpoints\n3. Involve mediators when needed\n4. Focus on solutions that benefit everyone")); + + +let investor = new_contact() + .name("Example Investor") + .save_contact(); + +let investors = new_group() + .name("Investors") + .description("A group for example inverstors of ourworld"); + +investors.add_contact(investor.id) + .save_group(); + +// === BOOKS === +print("Creating books..."); + +let sustainability_book = save_book(new_book() + .title("Sustainability Handbook") + .description("Complete guide to sustainable living and practices") + .add_page("# Introduction to Sustainability\n\nSustainability is about meeting our present needs without compromising the ability of future generations to meet their own needs.\n\n## Key Principles\n- Environmental stewardship\n- Social equity\n- Economic viability\n\n## Why It Matters\nOur planet faces unprecedented challenges from climate change, resource depletion, and environmental degradation.") + .add_page("# Energy Efficiency\n\n## Home Energy Savings\n- LED lighting reduces energy consumption by 75%\n- Smart thermostats can save 10-15% on heating/cooling\n- Energy-efficient appliances make a significant difference\n\n## Renewable Energy\n- Solar panels: Clean electricity from sunlight\n- Wind power: Harnessing natural wind currents\n- Hydroelectric: Using water flow for energy\n\n## Transportation\n- Electric vehicles reduce emissions\n- Public transit decreases individual carbon footprint\n- Cycling and walking for short distances") + .add_page("# Waste Reduction\n\n## The 5 R's\n1. **Refuse**: Say no to unnecessary items\n2. **Reduce**: Use less of what you need\n3. **Reuse**: Find new purposes for items\n4. **Recycle**: Process materials into new products\n5. **Rot**: Compost organic waste\n\n## Practical Tips\n- Use reusable bags and containers\n- Buy products with minimal packaging\n- Repair instead of replacing\n- Donate items you no longer need") + .add_page("# Sustainable Food\n\n## Local and Seasonal\n- Support local farmers and reduce transport emissions\n- Eat seasonal produce for better nutrition and taste\n- Visit farmers markets and join CSAs\n\n## Plant-Based Options\n- Reduce meat consumption for environmental benefits\n- Explore diverse plant proteins\n- Grow your own herbs and vegetables\n\n## Food Waste Prevention\n- Plan meals and make shopping lists\n- Store food properly to extend freshness\n- Use leftovers creatively") + .add_toc_entry(new_toc_entry().title("Introduction to Sustainability").page(0)) + .add_toc_entry(new_toc_entry().title("Energy Efficiency").page(1)) + .add_toc_entry(new_toc_entry().title("Waste Reduction").page(2)) + .add_toc_entry(new_toc_entry().title("Sustainable Food").page(3))); + +let tech_guide_book = save_book(new_book() + .title("Green Technology Guide") + .description("Understanding and implementing green technologies") + .add_page("# Green Technology Overview\n\nGreen technology, also known as clean technology, refers to the use of science and technology to create products and services that are environmentally friendly.\n\n## Categories\n- Renewable energy systems\n- Energy efficiency technologies\n- Pollution prevention and cleanup\n- Sustainable materials and manufacturing\n\n## Benefits\n- Reduced environmental impact\n- Lower operating costs\n- Improved public health\n- Economic opportunities") + .add_page("# Solar Technology\n\n## How Solar Works\nSolar panels convert sunlight directly into electricity using photovoltaic cells.\n\n## Types of Solar Systems\n- **Grid-tied**: Connected to the electrical grid\n- **Off-grid**: Standalone systems with battery storage\n- **Hybrid**: Combination of grid-tied and battery backup\n\n## Installation Considerations\n- Roof orientation and shading\n- Local climate and sun exposure\n- Energy consumption patterns\n- Available incentives and rebates") + .add_page("# Smart Home Technology\n\n## Automation Benefits\n- Optimized energy usage\n- Enhanced comfort and convenience\n- Remote monitoring and control\n- Predictive maintenance\n\n## Key Technologies\n- Smart thermostats\n- Automated lighting systems\n- Energy monitoring devices\n- Smart appliances\n- Home energy management systems") + .add_toc_entry(new_toc_entry().title("Green Technology Overview").page(0)) + .add_toc_entry(new_toc_entry().title("Solar Technology").page(1)) + .add_toc_entry(new_toc_entry().title("Smart Home Technology").page(2))); + +let community_book = save_book(new_book() + .title("Building Communities") + .description("Guide to creating sustainable and inclusive communities") + .add_page("# Community Building Fundamentals\n\n## What Makes a Strong Community?\n- Shared values and vision\n- Open communication channels\n- Mutual support and cooperation\n- Inclusive decision-making processes\n\n## Benefits of Strong Communities\n- Enhanced quality of life\n- Economic resilience\n- Social cohesion\n- Environmental stewardship") + .add_page("# Governance and Leadership\n\n## Collaborative Leadership\n- Distributed decision-making\n- Transparent processes\n- Accountability mechanisms\n- Conflict resolution systems\n\n## Community Engagement\n- Regular town halls and meetings\n- Digital participation platforms\n- Volunteer coordination\n- Feedback and improvement cycles") + .add_toc_entry(new_toc_entry().title("Community Building Fundamentals").page(0)) + .add_toc_entry(new_toc_entry().title("Governance and Leadership").page(1))); + +// === SLIDES === +print("Creating slides..."); + +let climate_slides = save_slides(new_slides() + .title("Climate Change Awareness") + .description("Visual presentation on climate change impacts and solutions") + .add_slide("https://images.unsplash.com/photo-1569163139394-de4e4f43e4e3?w=1200", "Global Temperature Rise") + .add_slide("https://images.unsplash.com/photo-1578662996442-48f60103fc96?w=1200", "Melting Ice Caps") + .add_slide("https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=1200", "Extreme Weather Events") + .add_slide("https://images.unsplash.com/photo-1473341304170-971dccb5ac1e?w=1200", "Renewable Energy Solutions") + .add_slide("https://images.unsplash.com/photo-1497436072909-f5e4be1dffea?w=1200", "Sustainable Transportation")); + +let innovation_slides = save_slides(new_slides() + .title("Innovation Showcase") + .description("Cutting-edge technologies for a sustainable future") + .add_slide("https://images.unsplash.com/photo-1518709268805-4e9042af2176?w=1200", "AI and Machine Learning") + .add_slide("https://images.unsplash.com/photo-1639322537228-f710d846310a?w=1200", "Blockchain Technology") + .add_slide("https://images.unsplash.com/photo-1581092160562-40aa08e78837?w=1200", "IoT and Smart Cities") + .add_slide("https://images.unsplash.com/photo-1581092918056-0c4c3acd3789?w=1200", "Quantum Computing") + .add_slide("https://images.unsplash.com/photo-1581092162384-8987c1d64718?w=1200", "Biotechnology Advances")); + +let nature_slides = save_slides(new_slides() + .title("Biodiversity Gallery") + .description("Celebrating Earth's incredible biodiversity") + .add_slide("https://images.unsplash.com/photo-1564349683136-77e08dba1ef7?w=1200", "Tropical Rainforest") + .add_slide("https://images.unsplash.com/photo-1559827260-dc66d52bef19?w=1200", "Coral Reef Ecosystem") + .add_slide("https://images.unsplash.com/photo-1551698618-1dfe5d97d256?w=1200", "Arctic Wildlife") + .add_slide("https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1200", "Mountain Ecosystems")); + +// === COLLECTIONS === +print("Creating collections..."); + +let nature_collection = save_collection(new_collection() + .title("Nature & Environment") + .description("Beautiful images and resources about our natural world") + .add_image(nature1.id) + .add_image(nature2.id) + .add_image(nature3.id) + .add_pdf(pdf1.id) + .add_markdown(md1.id) + .add_book(sustainability_book.id) + .add_slides(nature_slides.id)); + +let technology_collection = save_collection(new_collection() + .title("Sustainable Technology") + .description("Innovations driving positive change") + .add_image(tech1.id) + .add_image(tech2.id) + .add_pdf(pdf3.id) + .add_pdf(pdf4.id) + .add_markdown(md3.id) + .add_book(tech_guide_book.id) + .add_slides(innovation_slides.id)); + +let space_collection = save_collection(new_collection() + .title("Space & Cosmos") + .description("Exploring the universe and our place in it") + .add_image(space1.id) + .add_image(space2.id) + .add_pdf(pdf2.id) + .add_markdown(md2.id)); + +let community_collection = save_collection(new_collection() + .title("Community & Collaboration") + .description("Building better communities together") + .add_image(city1.id) + .add_pdf(pdf5.id) + .add_markdown(md4.id) + .add_book(community_book.id)); + +let climate_collection = save_collection(new_collection() + .title("Climate Action") + .description("Understanding and addressing climate change") + .add_slides(climate_slides.id) + .add_pdf(pdf1.id) + .add_markdown(md1.id)); + +print("✅ OurWorld library created successfully!"); +print("📚 Collections: 5"); +print("🖼️ Images: 8"); +print("📄 PDFs: 5"); +print("📝 Markdown docs: 4"); +print("📖 Books: 3"); +print("🎞️ Slide shows: 3"); \ No newline at end of file diff --git a/examples/ourworld/scripts/mbweni.rhai b/examples/ourworld/scripts/mbweni.rhai new file mode 100644 index 0000000..fe943a1 --- /dev/null +++ b/examples/ourworld/scripts/mbweni.rhai @@ -0,0 +1,249 @@ +// OurWorld Circle and Library Data + +new_circle() + .title("Mbweni Ruins & Gardens") + .description("Mbweni ruins and Gardens") + .ws_url("ws://localhost:8094/ws") + .logo("🌍") + .save_circle(); + +let circle = get_circle(); + +print("--- Creating OurWorld Library ---"); + +// === IMAGES === +print("Creating images..."); + +let nature1 = save_image(new_image() + .title("Mountain Sunrise") + .description("Breathtaking sunrise over mountain peaks") + .url("https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800") + .width(800).height(600)); + +let nature2 = save_image(new_image() + .title("Ocean Waves") + .description("Powerful ocean waves crashing on rocks") + .url("https://images.unsplash.com/photo-1505142468610-359e7d316be0?w=800") + .width(800).height(600)); + +let nature3 = save_image(new_image() + .title("Forest Path") + .description("Peaceful path through ancient forest") + .url("https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=800") + .width(800).height(600)); + +let tech1 = save_image(new_image() + .title("Solar Panels") + .description("Modern solar panel installation") + .url("https://images.unsplash.com/photo-1509391366360-2e959784a276?w=800") + .width(800).height(600)); + +let tech2 = save_image(new_image() + .title("Wind Turbines") + .description("Wind turbines generating clean energy") + .url("https://images.unsplash.com/photo-1466611653911-95081537e5b7?w=800") + .width(800).height(600)); + +let space1 = save_image(new_image() + .title("Earth from Space") + .description("Our beautiful planet from orbit") + .url("https://images.unsplash.com/photo-1446776877081-d282a0f896e2?w=800") + .width(800).height(600)); + +let space2 = save_image(new_image() + .title("Galaxy Spiral") + .description("Stunning spiral galaxy in deep space") + .url("https://images.unsplash.com/photo-1502134249126-9f3755a50d78?w=800") + .width(800).height(600)); + +let city1 = save_image(new_image() + .title("Smart City") + .description("Futuristic smart city at night") + .url("https://images.unsplash.com/photo-1480714378408-67cf0d13bc1f?w=800") + .width(800).height(600)); + +// === PDFs === +print("Creating PDFs..."); + +let pdf1 = save_pdf(new_pdf() + .title("Climate Action Report 2024") + .description("Comprehensive analysis of global climate initiatives") + .url("https://www.ipcc.ch/site/assets/uploads/2018/02/ipcc_wg3_ar5_summary-for-policymakers.pdf") + .page_count(42)); + +let pdf2 = save_pdf(new_pdf() + .title("Sustainable Development Goals") + .description("UN SDG implementation guide") + .url("https://sdgs.un.org/sites/default/files/publications/21252030%20Agenda%20for%20Sustainable%20Development%20web.pdf") + .page_count(35)); + +let pdf3 = save_pdf(new_pdf() + .title("Renewable Energy Handbook") + .description("Technical guide to renewable energy systems") + .url("https://www.irena.org/-/media/Files/IRENA/Agency/Publication/2019/Oct/IRENA_Renewable-Energy-Statistics-2019.pdf") + .page_count(280)); + +let pdf4 = save_pdf(new_pdf() + .title("Blockchain for Good") + .description("How blockchain technology can solve global challenges") + .url("https://www.weforum.org/whitepapers/blockchain-beyond-the-hype") + .page_count(24)); + +let pdf5 = save_pdf(new_pdf() + .title("Future of Work Report") + .description("Analysis of changing work patterns and remote collaboration") + .url("https://www.mckinsey.com/featured-insights/future-of-work") + .page_count(156)); + +// === MARKDOWN DOCUMENTS === +print("Creating markdown documents..."); + +let md1 = save_markdown(new_markdown() + .title("OurWorld Mission Statement") + .description("Our vision for a better world") + .content("# OurWorld Mission\n\n## Vision\nTo create a more sustainable, equitable, and connected world through technology and collaboration.\n\n## Values\n- **Sustainability**: Every decision considers environmental impact\n- **Inclusivity**: Technology that serves everyone\n- **Transparency**: Open source and open governance\n- **Innovation**: Pushing boundaries for positive change\n\n## Goals\n1. Reduce global carbon footprint by 50% by 2030\n2. Provide internet access to 1 billion underserved people\n3. Create 10 million green jobs worldwide\n4. Establish 1000 sustainable communities")); + +let md2 = save_markdown(new_markdown() + .title("Getting Started Guide") + .description("How to join the OurWorld movement") + .content("# Getting Started with OurWorld\n\n## Welcome!\nThank you for joining our mission to create a better world.\n\n## First Steps\n1. **Explore**: Browse our projects and initiatives\n2. **Connect**: Join our community forums\n3. **Contribute**: Find ways to get involved\n4. **Learn**: Access our educational resources\n\n## Ways to Contribute\n- **Developers**: Contribute to open source projects\n- **Activists**: Organize local initiatives\n- **Educators**: Share knowledge and skills\n- **Investors**: Support sustainable ventures\n\n## Resources\n- [Community Forum](https://forum.ourworld.tf)\n- [Developer Portal](https://dev.ourworld.tf)\n- [Learning Hub](https://learn.ourworld.tf)")); + +let md3 = save_markdown(new_markdown() + .title("Technology Roadmap 2024") + .description("Our technical development plans") + .content("# Technology Roadmap 2024\n\n## Q1 Objectives\n- Launch decentralized identity system\n- Deploy carbon tracking blockchain\n- Release mobile app v2.0\n\n## Q2 Objectives\n- Implement AI-powered resource optimization\n- Launch peer-to-peer energy trading platform\n- Deploy IoT sensor network\n\n## Q3 Objectives\n- Release virtual collaboration spaces\n- Launch digital twin cities pilot\n- Implement quantum-safe encryption\n\n## Q4 Objectives\n- Deploy autonomous governance systems\n- Launch global impact measurement platform\n- Release AR/VR sustainability training")); + +let md4 = save_markdown(new_markdown() + .title("Community Guidelines") + .description("How we work together") + .content("# Community Guidelines\n\n## Our Principles\n- **Respect**: Treat everyone with dignity\n- **Collaboration**: Work together towards common goals\n- **Constructive**: Focus on solutions, not problems\n- **Inclusive**: Welcome diverse perspectives\n\n## Communication Standards\n- Use clear, respectful language\n- Listen actively to others\n- Provide constructive feedback\n- Share knowledge freely\n\n## Conflict Resolution\n1. Address issues directly and respectfully\n2. Seek to understand different viewpoints\n3. Involve mediators when needed\n4. Focus on solutions that benefit everyone")); + + +let investor = new_contact() + .name("Example Investor") + .save_contact(); + +let investors = new_group() + .name("Investors") + .description("A group for example inverstors of ourworld"); + +investors.add_contact(investor.id) + .save_group(); + +// === BOOKS === +print("Creating books..."); + +let sustainability_book = save_book(new_book() + .title("Sustainability Handbook") + .description("Complete guide to sustainable living and practices") + .add_page("# Introduction to Sustainability\n\nSustainability is about meeting our present needs without compromising the ability of future generations to meet their own needs.\n\n## Key Principles\n- Environmental stewardship\n- Social equity\n- Economic viability\n\n## Why It Matters\nOur planet faces unprecedented challenges from climate change, resource depletion, and environmental degradation.") + .add_page("# Energy Efficiency\n\n## Home Energy Savings\n- LED lighting reduces energy consumption by 75%\n- Smart thermostats can save 10-15% on heating/cooling\n- Energy-efficient appliances make a significant difference\n\n## Renewable Energy\n- Solar panels: Clean electricity from sunlight\n- Wind power: Harnessing natural wind currents\n- Hydroelectric: Using water flow for energy\n\n## Transportation\n- Electric vehicles reduce emissions\n- Public transit decreases individual carbon footprint\n- Cycling and walking for short distances") + .add_page("# Waste Reduction\n\n## The 5 R's\n1. **Refuse**: Say no to unnecessary items\n2. **Reduce**: Use less of what you need\n3. **Reuse**: Find new purposes for items\n4. **Recycle**: Process materials into new products\n5. **Rot**: Compost organic waste\n\n## Practical Tips\n- Use reusable bags and containers\n- Buy products with minimal packaging\n- Repair instead of replacing\n- Donate items you no longer need") + .add_page("# Sustainable Food\n\n## Local and Seasonal\n- Support local farmers and reduce transport emissions\n- Eat seasonal produce for better nutrition and taste\n- Visit farmers markets and join CSAs\n\n## Plant-Based Options\n- Reduce meat consumption for environmental benefits\n- Explore diverse plant proteins\n- Grow your own herbs and vegetables\n\n## Food Waste Prevention\n- Plan meals and make shopping lists\n- Store food properly to extend freshness\n- Use leftovers creatively") + .add_toc_entry(new_toc_entry().title("Introduction to Sustainability").page(0)) + .add_toc_entry(new_toc_entry().title("Energy Efficiency").page(1)) + .add_toc_entry(new_toc_entry().title("Waste Reduction").page(2)) + .add_toc_entry(new_toc_entry().title("Sustainable Food").page(3))); + +let tech_guide_book = save_book(new_book() + .title("Green Technology Guide") + .description("Understanding and implementing green technologies") + .add_page("# Green Technology Overview\n\nGreen technology, also known as clean technology, refers to the use of science and technology to create products and services that are environmentally friendly.\n\n## Categories\n- Renewable energy systems\n- Energy efficiency technologies\n- Pollution prevention and cleanup\n- Sustainable materials and manufacturing\n\n## Benefits\n- Reduced environmental impact\n- Lower operating costs\n- Improved public health\n- Economic opportunities") + .add_page("# Solar Technology\n\n## How Solar Works\nSolar panels convert sunlight directly into electricity using photovoltaic cells.\n\n## Types of Solar Systems\n- **Grid-tied**: Connected to the electrical grid\n- **Off-grid**: Standalone systems with battery storage\n- **Hybrid**: Combination of grid-tied and battery backup\n\n## Installation Considerations\n- Roof orientation and shading\n- Local climate and sun exposure\n- Energy consumption patterns\n- Available incentives and rebates") + .add_page("# Smart Home Technology\n\n## Automation Benefits\n- Optimized energy usage\n- Enhanced comfort and convenience\n- Remote monitoring and control\n- Predictive maintenance\n\n## Key Technologies\n- Smart thermostats\n- Automated lighting systems\n- Energy monitoring devices\n- Smart appliances\n- Home energy management systems") + .add_toc_entry(new_toc_entry().title("Green Technology Overview").page(0)) + .add_toc_entry(new_toc_entry().title("Solar Technology").page(1)) + .add_toc_entry(new_toc_entry().title("Smart Home Technology").page(2))); + +let community_book = save_book(new_book() + .title("Building Communities") + .description("Guide to creating sustainable and inclusive communities") + .add_page("# Community Building Fundamentals\n\n## What Makes a Strong Community?\n- Shared values and vision\n- Open communication channels\n- Mutual support and cooperation\n- Inclusive decision-making processes\n\n## Benefits of Strong Communities\n- Enhanced quality of life\n- Economic resilience\n- Social cohesion\n- Environmental stewardship") + .add_page("# Governance and Leadership\n\n## Collaborative Leadership\n- Distributed decision-making\n- Transparent processes\n- Accountability mechanisms\n- Conflict resolution systems\n\n## Community Engagement\n- Regular town halls and meetings\n- Digital participation platforms\n- Volunteer coordination\n- Feedback and improvement cycles") + .add_toc_entry(new_toc_entry().title("Community Building Fundamentals").page(0)) + .add_toc_entry(new_toc_entry().title("Governance and Leadership").page(1))); + +// === SLIDES === +print("Creating slides..."); + +let climate_slides = save_slides(new_slides() + .title("Climate Change Awareness") + .description("Visual presentation on climate change impacts and solutions") + .add_slide("https://images.unsplash.com/photo-1569163139394-de4e4f43e4e3?w=1200", "Global Temperature Rise") + .add_slide("https://images.unsplash.com/photo-1578662996442-48f60103fc96?w=1200", "Melting Ice Caps") + .add_slide("https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=1200", "Extreme Weather Events") + .add_slide("https://images.unsplash.com/photo-1473341304170-971dccb5ac1e?w=1200", "Renewable Energy Solutions") + .add_slide("https://images.unsplash.com/photo-1497436072909-f5e4be1dffea?w=1200", "Sustainable Transportation")); + +let innovation_slides = save_slides(new_slides() + .title("Innovation Showcase") + .description("Cutting-edge technologies for a sustainable future") + .add_slide("https://images.unsplash.com/photo-1518709268805-4e9042af2176?w=1200", "AI and Machine Learning") + .add_slide("https://images.unsplash.com/photo-1639322537228-f710d846310a?w=1200", "Blockchain Technology") + .add_slide("https://images.unsplash.com/photo-1581092160562-40aa08e78837?w=1200", "IoT and Smart Cities") + .add_slide("https://images.unsplash.com/photo-1581092918056-0c4c3acd3789?w=1200", "Quantum Computing") + .add_slide("https://images.unsplash.com/photo-1581092162384-8987c1d64718?w=1200", "Biotechnology Advances")); + +let nature_slides = save_slides(new_slides() + .title("Biodiversity Gallery") + .description("Celebrating Earth's incredible biodiversity") + .add_slide("https://images.unsplash.com/photo-1564349683136-77e08dba1ef7?w=1200", "Tropical Rainforest") + .add_slide("https://images.unsplash.com/photo-1559827260-dc66d52bef19?w=1200", "Coral Reef Ecosystem") + .add_slide("https://images.unsplash.com/photo-1551698618-1dfe5d97d256?w=1200", "Arctic Wildlife") + .add_slide("https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1200", "Mountain Ecosystems")); + +// === COLLECTIONS === +print("Creating collections..."); + +let nature_collection = save_collection(new_collection() + .title("Nature & Environment") + .description("Beautiful images and resources about our natural world") + .add_image(nature1.id) + .add_image(nature2.id) + .add_image(nature3.id) + .add_pdf(pdf1.id) + .add_markdown(md1.id) + .add_book(sustainability_book.id) + .add_slides(nature_slides.id)); + +let technology_collection = save_collection(new_collection() + .title("Sustainable Technology") + .description("Innovations driving positive change") + .add_image(tech1.id) + .add_image(tech2.id) + .add_pdf(pdf3.id) + .add_pdf(pdf4.id) + .add_markdown(md3.id) + .add_book(tech_guide_book.id) + .add_slides(innovation_slides.id)); + +let space_collection = save_collection(new_collection() + .title("Space & Cosmos") + .description("Exploring the universe and our place in it") + .add_image(space1.id) + .add_image(space2.id) + .add_pdf(pdf2.id) + .add_markdown(md2.id)); + +let community_collection = save_collection(new_collection() + .title("Community & Collaboration") + .description("Building better communities together") + .add_image(city1.id) + .add_pdf(pdf5.id) + .add_markdown(md4.id) + .add_book(community_book.id)); + +let climate_collection = save_collection(new_collection() + .title("Climate Action") + .description("Understanding and addressing climate change") + .add_slides(climate_slides.id) + .add_pdf(pdf1.id) + .add_markdown(md1.id)); + +print("✅ OurWorld library created successfully!"); +print("📚 Collections: 5"); +print("🖼️ Images: 8"); +print("📄 PDFs: 5"); +print("📝 Markdown docs: 4"); +print("📖 Books: 3"); +print("🎞️ Slide shows: 3"); \ No newline at end of file diff --git a/examples/ourworld/scripts/ourworld.rhai b/examples/ourworld/scripts/ourworld.rhai new file mode 100644 index 0000000..02e9819 --- /dev/null +++ b/examples/ourworld/scripts/ourworld.rhai @@ -0,0 +1,255 @@ +// OurWorld Circle and Library Data + +new_circle() + .title("Ourworld") + .description("Creating a better world.") + .ws_url("ws://localhost:9000/ws") + .add_circle("ws://localhost:9001/ws") + .add_circle("ws://localhost:9002/ws") + .add_circle("ws://localhost:9003/ws") + .add_circle("ws://localhost:9004/ws") + .add_circle("ws://localhost:9005/ws") + .add_circle("ws://localhost:8096/ws") + .logo("🌍") + .save_circle(); + +let circle = get_circle(); + +print("--- Creating OurWorld Library ---"); + +// === IMAGES === +print("Creating images..."); + +let nature1 = save_image(new_image() + .title("Mountain Sunrise") + .description("Breathtaking sunrise over mountain peaks") + .url("https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800") + .width(800).height(600)); + +let nature2 = save_image(new_image() + .title("Ocean Waves") + .description("Powerful ocean waves crashing on rocks") + .url("https://images.unsplash.com/photo-1505142468610-359e7d316be0?w=800") + .width(800).height(600)); + +let nature3 = save_image(new_image() + .title("Forest Path") + .description("Peaceful path through ancient forest") + .url("https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=800") + .width(800).height(600)); + +let tech1 = save_image(new_image() + .title("Solar Panels") + .description("Modern solar panel installation") + .url("https://images.unsplash.com/photo-1509391366360-2e959784a276?w=800") + .width(800).height(600)); + +let tech2 = save_image(new_image() + .title("Wind Turbines") + .description("Wind turbines generating clean energy") + .url("https://images.unsplash.com/photo-1466611653911-95081537e5b7?w=800") + .width(800).height(600)); + +let space1 = save_image(new_image() + .title("Earth from Space") + .description("Our beautiful planet from orbit") + .url("https://images.unsplash.com/photo-1446776877081-d282a0f896e2?w=800") + .width(800).height(600)); + +let space2 = save_image(new_image() + .title("Galaxy Spiral") + .description("Stunning spiral galaxy in deep space") + .url("https://images.unsplash.com/photo-1502134249126-9f3755a50d78?w=800") + .width(800).height(600)); + +let city1 = save_image(new_image() + .title("Smart City") + .description("Futuristic smart city at night") + .url("https://images.unsplash.com/photo-1480714378408-67cf0d13bc1f?w=800") + .width(800).height(600)); + +// === PDFs === +print("Creating PDFs..."); + +let pdf1 = save_pdf(new_pdf() + .title("Climate Action Report 2024") + .description("Comprehensive analysis of global climate initiatives") + .url("https://www.ipcc.ch/site/assets/uploads/2018/02/ipcc_wg3_ar5_summary-for-policymakers.pdf") + .page_count(42)); + +let pdf2 = save_pdf(new_pdf() + .title("Sustainable Development Goals") + .description("UN SDG implementation guide") + .url("https://sdgs.un.org/sites/default/files/publications/21252030%20Agenda%20for%20Sustainable%20Development%20web.pdf") + .page_count(35)); + +let pdf3 = save_pdf(new_pdf() + .title("Renewable Energy Handbook") + .description("Technical guide to renewable energy systems") + .url("https://www.irena.org/-/media/Files/IRENA/Agency/Publication/2019/Oct/IRENA_Renewable-Energy-Statistics-2019.pdf") + .page_count(280)); + +let pdf4 = save_pdf(new_pdf() + .title("Blockchain for Good") + .description("How blockchain technology can solve global challenges") + .url("https://www.weforum.org/whitepapers/blockchain-beyond-the-hype") + .page_count(24)); + +let pdf5 = save_pdf(new_pdf() + .title("Future of Work Report") + .description("Analysis of changing work patterns and remote collaboration") + .url("https://www.mckinsey.com/featured-insights/future-of-work") + .page_count(156)); + +// === MARKDOWN DOCUMENTS === +print("Creating markdown documents..."); + +let md1 = save_markdown(new_markdown() + .title("OurWorld Mission Statement") + .description("Our vision for a better world") + .content("# OurWorld Mission\n\n## Vision\nTo create a more sustainable, equitable, and connected world through technology and collaboration.\n\n## Values\n- **Sustainability**: Every decision considers environmental impact\n- **Inclusivity**: Technology that serves everyone\n- **Transparency**: Open source and open governance\n- **Innovation**: Pushing boundaries for positive change\n\n## Goals\n1. Reduce global carbon footprint by 50% by 2030\n2. Provide internet access to 1 billion underserved people\n3. Create 10 million green jobs worldwide\n4. Establish 1000 sustainable communities")); + +let md2 = save_markdown(new_markdown() + .title("Getting Started Guide") + .description("How to join the OurWorld movement") + .content("# Getting Started with OurWorld\n\n## Welcome!\nThank you for joining our mission to create a better world.\n\n## First Steps\n1. **Explore**: Browse our projects and initiatives\n2. **Connect**: Join our community forums\n3. **Contribute**: Find ways to get involved\n4. **Learn**: Access our educational resources\n\n## Ways to Contribute\n- **Developers**: Contribute to open source projects\n- **Activists**: Organize local initiatives\n- **Educators**: Share knowledge and skills\n- **Investors**: Support sustainable ventures\n\n## Resources\n- [Community Forum](https://forum.ourworld.tf)\n- [Developer Portal](https://dev.ourworld.tf)\n- [Learning Hub](https://learn.ourworld.tf)")); + +let md3 = save_markdown(new_markdown() + .title("Technology Roadmap 2024") + .description("Our technical development plans") + .content("# Technology Roadmap 2024\n\n## Q1 Objectives\n- Launch decentralized identity system\n- Deploy carbon tracking blockchain\n- Release mobile app v2.0\n\n## Q2 Objectives\n- Implement AI-powered resource optimization\n- Launch peer-to-peer energy trading platform\n- Deploy IoT sensor network\n\n## Q3 Objectives\n- Release virtual collaboration spaces\n- Launch digital twin cities pilot\n- Implement quantum-safe encryption\n\n## Q4 Objectives\n- Deploy autonomous governance systems\n- Launch global impact measurement platform\n- Release AR/VR sustainability training")); + +let md4 = save_markdown(new_markdown() + .title("Community Guidelines") + .description("How we work together") + .content("# Community Guidelines\n\n## Our Principles\n- **Respect**: Treat everyone with dignity\n- **Collaboration**: Work together towards common goals\n- **Constructive**: Focus on solutions, not problems\n- **Inclusive**: Welcome diverse perspectives\n\n## Communication Standards\n- Use clear, respectful language\n- Listen actively to others\n- Provide constructive feedback\n- Share knowledge freely\n\n## Conflict Resolution\n1. Address issues directly and respectfully\n2. Seek to understand different viewpoints\n3. Involve mediators when needed\n4. Focus on solutions that benefit everyone")); + + +let investor = new_contact() + .name("Example Investor") + .save_contact(); + +let investors = new_group() + .name("Investors") + .description("A group for example inverstors of ourworld"); + +investors.add_contact(investor.id) + .save_group(); + +// === BOOKS === +print("Creating books..."); + +let sustainability_book = save_book(new_book() + .title("Sustainability Handbook") + .description("Complete guide to sustainable living and practices") + .add_page("# Introduction to Sustainability\n\nSustainability is about meeting our present needs without compromising the ability of future generations to meet their own needs.\n\n## Key Principles\n- Environmental stewardship\n- Social equity\n- Economic viability\n\n## Why It Matters\nOur planet faces unprecedented challenges from climate change, resource depletion, and environmental degradation.") + .add_page("# Energy Efficiency\n\n## Home Energy Savings\n- LED lighting reduces energy consumption by 75%\n- Smart thermostats can save 10-15% on heating/cooling\n- Energy-efficient appliances make a significant difference\n\n## Renewable Energy\n- Solar panels: Clean electricity from sunlight\n- Wind power: Harnessing natural wind currents\n- Hydroelectric: Using water flow for energy\n\n## Transportation\n- Electric vehicles reduce emissions\n- Public transit decreases individual carbon footprint\n- Cycling and walking for short distances") + .add_page("# Waste Reduction\n\n## The 5 R's\n1. **Refuse**: Say no to unnecessary items\n2. **Reduce**: Use less of what you need\n3. **Reuse**: Find new purposes for items\n4. **Recycle**: Process materials into new products\n5. **Rot**: Compost organic waste\n\n## Practical Tips\n- Use reusable bags and containers\n- Buy products with minimal packaging\n- Repair instead of replacing\n- Donate items you no longer need") + .add_page("# Sustainable Food\n\n## Local and Seasonal\n- Support local farmers and reduce transport emissions\n- Eat seasonal produce for better nutrition and taste\n- Visit farmers markets and join CSAs\n\n## Plant-Based Options\n- Reduce meat consumption for environmental benefits\n- Explore diverse plant proteins\n- Grow your own herbs and vegetables\n\n## Food Waste Prevention\n- Plan meals and make shopping lists\n- Store food properly to extend freshness\n- Use leftovers creatively") + .add_toc_entry(new_toc_entry().title("Introduction to Sustainability").page(0)) + .add_toc_entry(new_toc_entry().title("Energy Efficiency").page(1)) + .add_toc_entry(new_toc_entry().title("Waste Reduction").page(2)) + .add_toc_entry(new_toc_entry().title("Sustainable Food").page(3))); + +let tech_guide_book = save_book(new_book() + .title("Green Technology Guide") + .description("Understanding and implementing green technologies") + .add_page("# Green Technology Overview\n\nGreen technology, also known as clean technology, refers to the use of science and technology to create products and services that are environmentally friendly.\n\n## Categories\n- Renewable energy systems\n- Energy efficiency technologies\n- Pollution prevention and cleanup\n- Sustainable materials and manufacturing\n\n## Benefits\n- Reduced environmental impact\n- Lower operating costs\n- Improved public health\n- Economic opportunities") + .add_page("# Solar Technology\n\n## How Solar Works\nSolar panels convert sunlight directly into electricity using photovoltaic cells.\n\n## Types of Solar Systems\n- **Grid-tied**: Connected to the electrical grid\n- **Off-grid**: Standalone systems with battery storage\n- **Hybrid**: Combination of grid-tied and battery backup\n\n## Installation Considerations\n- Roof orientation and shading\n- Local climate and sun exposure\n- Energy consumption patterns\n- Available incentives and rebates") + .add_page("# Smart Home Technology\n\n## Automation Benefits\n- Optimized energy usage\n- Enhanced comfort and convenience\n- Remote monitoring and control\n- Predictive maintenance\n\n## Key Technologies\n- Smart thermostats\n- Automated lighting systems\n- Energy monitoring devices\n- Smart appliances\n- Home energy management systems") + .add_toc_entry(new_toc_entry().title("Green Technology Overview").page(0)) + .add_toc_entry(new_toc_entry().title("Solar Technology").page(1)) + .add_toc_entry(new_toc_entry().title("Smart Home Technology").page(2))); + +let community_book = save_book(new_book() + .title("Building Communities") + .description("Guide to creating sustainable and inclusive communities") + .add_page("# Community Building Fundamentals\n\n## What Makes a Strong Community?\n- Shared values and vision\n- Open communication channels\n- Mutual support and cooperation\n- Inclusive decision-making processes\n\n## Benefits of Strong Communities\n- Enhanced quality of life\n- Economic resilience\n- Social cohesion\n- Environmental stewardship") + .add_page("# Governance and Leadership\n\n## Collaborative Leadership\n- Distributed decision-making\n- Transparent processes\n- Accountability mechanisms\n- Conflict resolution systems\n\n## Community Engagement\n- Regular town halls and meetings\n- Digital participation platforms\n- Volunteer coordination\n- Feedback and improvement cycles") + .add_toc_entry(new_toc_entry().title("Community Building Fundamentals").page(0)) + .add_toc_entry(new_toc_entry().title("Governance and Leadership").page(1))); + +// === SLIDES === +print("Creating slides..."); + +let climate_slides = save_slides(new_slides() + .title("Climate Change Awareness") + .description("Visual presentation on climate change impacts and solutions") + .add_slide("https://images.unsplash.com/photo-1569163139394-de4e4f43e4e3?w=1200", "Global Temperature Rise") + .add_slide("https://images.unsplash.com/photo-1578662996442-48f60103fc96?w=1200", "Melting Ice Caps") + .add_slide("https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=1200", "Extreme Weather Events") + .add_slide("https://images.unsplash.com/photo-1473341304170-971dccb5ac1e?w=1200", "Renewable Energy Solutions") + .add_slide("https://images.unsplash.com/photo-1497436072909-f5e4be1dffea?w=1200", "Sustainable Transportation")); + +let innovation_slides = save_slides(new_slides() + .title("Innovation Showcase") + .description("Cutting-edge technologies for a sustainable future") + .add_slide("https://images.unsplash.com/photo-1518709268805-4e9042af2176?w=1200", "AI and Machine Learning") + .add_slide("https://images.unsplash.com/photo-1639322537228-f710d846310a?w=1200", "Blockchain Technology") + .add_slide("https://images.unsplash.com/photo-1581092160562-40aa08e78837?w=1200", "IoT and Smart Cities") + .add_slide("https://images.unsplash.com/photo-1581092918056-0c4c3acd3789?w=1200", "Quantum Computing") + .add_slide("https://images.unsplash.com/photo-1581092162384-8987c1d64718?w=1200", "Biotechnology Advances")); + +let nature_slides = save_slides(new_slides() + .title("Biodiversity Gallery") + .description("Celebrating Earth's incredible biodiversity") + .add_slide("https://images.unsplash.com/photo-1564349683136-77e08dba1ef7?w=1200", "Tropical Rainforest") + .add_slide("https://images.unsplash.com/photo-1559827260-dc66d52bef19?w=1200", "Coral Reef Ecosystem") + .add_slide("https://images.unsplash.com/photo-1551698618-1dfe5d97d256?w=1200", "Arctic Wildlife") + .add_slide("https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1200", "Mountain Ecosystems")); + +// === COLLECTIONS === +print("Creating collections..."); + +let nature_collection = save_collection(new_collection() + .title("Nature & Environment") + .description("Beautiful images and resources about our natural world") + .add_image(nature1.id) + .add_image(nature2.id) + .add_image(nature3.id) + .add_pdf(pdf1.id) + .add_markdown(md1.id) + .add_book(sustainability_book.id) + .add_slides(nature_slides.id)); + +let technology_collection = save_collection(new_collection() + .title("Sustainable Technology") + .description("Innovations driving positive change") + .add_image(tech1.id) + .add_image(tech2.id) + .add_pdf(pdf3.id) + .add_pdf(pdf4.id) + .add_markdown(md3.id) + .add_book(tech_guide_book.id) + .add_slides(innovation_slides.id)); + +let space_collection = save_collection(new_collection() + .title("Space & Cosmos") + .description("Exploring the universe and our place in it") + .add_image(space1.id) + .add_image(space2.id) + .add_pdf(pdf2.id) + .add_markdown(md2.id)); + +let community_collection = save_collection(new_collection() + .title("Community & Collaboration") + .description("Building better communities together") + .add_image(city1.id) + .add_pdf(pdf5.id) + .add_markdown(md4.id) + .add_book(community_book.id)); + +let climate_collection = save_collection(new_collection() + .title("Climate Action") + .description("Understanding and addressing climate change") + .add_slides(climate_slides.id) + .add_pdf(pdf1.id) + .add_markdown(md1.id)); + +print("✅ OurWorld library created successfully!"); +print("📚 Collections: 5"); +print("🖼️ Images: 8"); +print("📄 PDFs: 5"); +print("📝 Markdown docs: 4"); +print("📖 Books: 3"); +print("🎞️ Slide shows: 3"); \ No newline at end of file diff --git a/examples/ourworld/scripts/sikana.rhai b/examples/ourworld/scripts/sikana.rhai new file mode 100644 index 0000000..d68e549 --- /dev/null +++ b/examples/ourworld/scripts/sikana.rhai @@ -0,0 +1,249 @@ +// OurWorld Circle and Library Data + +new_circle() + .title("Sikana") + .description("Creating a better world.") + .ws_url("ws://localhost:8092/ws") + .logo("🌍") + .save_circle(); + +let circle = get_circle(); + +print("--- Creating OurWorld Library ---"); + +// === IMAGES === +print("Creating images..."); + +let nature1 = save_image(new_image() + .title("Mountain Sunrise") + .description("Breathtaking sunrise over mountain peaks") + .url("https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800") + .width(800).height(600)); + +let nature2 = save_image(new_image() + .title("Ocean Waves") + .description("Powerful ocean waves crashing on rocks") + .url("https://images.unsplash.com/photo-1505142468610-359e7d316be0?w=800") + .width(800).height(600)); + +let nature3 = save_image(new_image() + .title("Forest Path") + .description("Peaceful path through ancient forest") + .url("https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=800") + .width(800).height(600)); + +let tech1 = save_image(new_image() + .title("Solar Panels") + .description("Modern solar panel installation") + .url("https://images.unsplash.com/photo-1509391366360-2e959784a276?w=800") + .width(800).height(600)); + +let tech2 = save_image(new_image() + .title("Wind Turbines") + .description("Wind turbines generating clean energy") + .url("https://images.unsplash.com/photo-1466611653911-95081537e5b7?w=800") + .width(800).height(600)); + +let space1 = save_image(new_image() + .title("Earth from Space") + .description("Our beautiful planet from orbit") + .url("https://images.unsplash.com/photo-1446776877081-d282a0f896e2?w=800") + .width(800).height(600)); + +let space2 = save_image(new_image() + .title("Galaxy Spiral") + .description("Stunning spiral galaxy in deep space") + .url("https://images.unsplash.com/photo-1502134249126-9f3755a50d78?w=800") + .width(800).height(600)); + +let city1 = save_image(new_image() + .title("Smart City") + .description("Futuristic smart city at night") + .url("https://images.unsplash.com/photo-1480714378408-67cf0d13bc1f?w=800") + .width(800).height(600)); + +// === PDFs === +print("Creating PDFs..."); + +let pdf1 = save_pdf(new_pdf() + .title("Climate Action Report 2024") + .description("Comprehensive analysis of global climate initiatives") + .url("https://www.ipcc.ch/site/assets/uploads/2018/02/ipcc_wg3_ar5_summary-for-policymakers.pdf") + .page_count(42)); + +let pdf2 = save_pdf(new_pdf() + .title("Sustainable Development Goals") + .description("UN SDG implementation guide") + .url("https://sdgs.un.org/sites/default/files/publications/21252030%20Agenda%20for%20Sustainable%20Development%20web.pdf") + .page_count(35)); + +let pdf3 = save_pdf(new_pdf() + .title("Renewable Energy Handbook") + .description("Technical guide to renewable energy systems") + .url("https://www.irena.org/-/media/Files/IRENA/Agency/Publication/2019/Oct/IRENA_Renewable-Energy-Statistics-2019.pdf") + .page_count(280)); + +let pdf4 = save_pdf(new_pdf() + .title("Blockchain for Good") + .description("How blockchain technology can solve global challenges") + .url("https://www.weforum.org/whitepapers/blockchain-beyond-the-hype") + .page_count(24)); + +let pdf5 = save_pdf(new_pdf() + .title("Future of Work Report") + .description("Analysis of changing work patterns and remote collaboration") + .url("https://www.mckinsey.com/featured-insights/future-of-work") + .page_count(156)); + +// === MARKDOWN DOCUMENTS === +print("Creating markdown documents..."); + +let md1 = save_markdown(new_markdown() + .title("OurWorld Mission Statement") + .description("Our vision for a better world") + .content("# OurWorld Mission\n\n## Vision\nTo create a more sustainable, equitable, and connected world through technology and collaboration.\n\n## Values\n- **Sustainability**: Every decision considers environmental impact\n- **Inclusivity**: Technology that serves everyone\n- **Transparency**: Open source and open governance\n- **Innovation**: Pushing boundaries for positive change\n\n## Goals\n1. Reduce global carbon footprint by 50% by 2030\n2. Provide internet access to 1 billion underserved people\n3. Create 10 million green jobs worldwide\n4. Establish 1000 sustainable communities")); + +let md2 = save_markdown(new_markdown() + .title("Getting Started Guide") + .description("How to join the OurWorld movement") + .content("# Getting Started with OurWorld\n\n## Welcome!\nThank you for joining our mission to create a better world.\n\n## First Steps\n1. **Explore**: Browse our projects and initiatives\n2. **Connect**: Join our community forums\n3. **Contribute**: Find ways to get involved\n4. **Learn**: Access our educational resources\n\n## Ways to Contribute\n- **Developers**: Contribute to open source projects\n- **Activists**: Organize local initiatives\n- **Educators**: Share knowledge and skills\n- **Investors**: Support sustainable ventures\n\n## Resources\n- [Community Forum](https://forum.ourworld.tf)\n- [Developer Portal](https://dev.ourworld.tf)\n- [Learning Hub](https://learn.ourworld.tf)")); + +let md3 = save_markdown(new_markdown() + .title("Technology Roadmap 2024") + .description("Our technical development plans") + .content("# Technology Roadmap 2024\n\n## Q1 Objectives\n- Launch decentralized identity system\n- Deploy carbon tracking blockchain\n- Release mobile app v2.0\n\n## Q2 Objectives\n- Implement AI-powered resource optimization\n- Launch peer-to-peer energy trading platform\n- Deploy IoT sensor network\n\n## Q3 Objectives\n- Release virtual collaboration spaces\n- Launch digital twin cities pilot\n- Implement quantum-safe encryption\n\n## Q4 Objectives\n- Deploy autonomous governance systems\n- Launch global impact measurement platform\n- Release AR/VR sustainability training")); + +let md4 = save_markdown(new_markdown() + .title("Community Guidelines") + .description("How we work together") + .content("# Community Guidelines\n\n## Our Principles\n- **Respect**: Treat everyone with dignity\n- **Collaboration**: Work together towards common goals\n- **Constructive**: Focus on solutions, not problems\n- **Inclusive**: Welcome diverse perspectives\n\n## Communication Standards\n- Use clear, respectful language\n- Listen actively to others\n- Provide constructive feedback\n- Share knowledge freely\n\n## Conflict Resolution\n1. Address issues directly and respectfully\n2. Seek to understand different viewpoints\n3. Involve mediators when needed\n4. Focus on solutions that benefit everyone")); + + +let investor = new_contact() + .name("Example Investor") + .save_contact(); + +let investors = new_group() + .name("Investors") + .description("A group for example inverstors of ourworld"); + +investors.add_contact(investor.id) + .save_group(); + +// === BOOKS === +print("Creating books..."); + +let sustainability_book = save_book(new_book() + .title("Sustainability Handbook") + .description("Complete guide to sustainable living and practices") + .add_page("# Introduction to Sustainability\n\nSustainability is about meeting our present needs without compromising the ability of future generations to meet their own needs.\n\n## Key Principles\n- Environmental stewardship\n- Social equity\n- Economic viability\n\n## Why It Matters\nOur planet faces unprecedented challenges from climate change, resource depletion, and environmental degradation.") + .add_page("# Energy Efficiency\n\n## Home Energy Savings\n- LED lighting reduces energy consumption by 75%\n- Smart thermostats can save 10-15% on heating/cooling\n- Energy-efficient appliances make a significant difference\n\n## Renewable Energy\n- Solar panels: Clean electricity from sunlight\n- Wind power: Harnessing natural wind currents\n- Hydroelectric: Using water flow for energy\n\n## Transportation\n- Electric vehicles reduce emissions\n- Public transit decreases individual carbon footprint\n- Cycling and walking for short distances") + .add_page("# Waste Reduction\n\n## The 5 R's\n1. **Refuse**: Say no to unnecessary items\n2. **Reduce**: Use less of what you need\n3. **Reuse**: Find new purposes for items\n4. **Recycle**: Process materials into new products\n5. **Rot**: Compost organic waste\n\n## Practical Tips\n- Use reusable bags and containers\n- Buy products with minimal packaging\n- Repair instead of replacing\n- Donate items you no longer need") + .add_page("# Sustainable Food\n\n## Local and Seasonal\n- Support local farmers and reduce transport emissions\n- Eat seasonal produce for better nutrition and taste\n- Visit farmers markets and join CSAs\n\n## Plant-Based Options\n- Reduce meat consumption for environmental benefits\n- Explore diverse plant proteins\n- Grow your own herbs and vegetables\n\n## Food Waste Prevention\n- Plan meals and make shopping lists\n- Store food properly to extend freshness\n- Use leftovers creatively") + .add_toc_entry(new_toc_entry().title("Introduction to Sustainability").page(0)) + .add_toc_entry(new_toc_entry().title("Energy Efficiency").page(1)) + .add_toc_entry(new_toc_entry().title("Waste Reduction").page(2)) + .add_toc_entry(new_toc_entry().title("Sustainable Food").page(3))); + +let tech_guide_book = save_book(new_book() + .title("Green Technology Guide") + .description("Understanding and implementing green technologies") + .add_page("# Green Technology Overview\n\nGreen technology, also known as clean technology, refers to the use of science and technology to create products and services that are environmentally friendly.\n\n## Categories\n- Renewable energy systems\n- Energy efficiency technologies\n- Pollution prevention and cleanup\n- Sustainable materials and manufacturing\n\n## Benefits\n- Reduced environmental impact\n- Lower operating costs\n- Improved public health\n- Economic opportunities") + .add_page("# Solar Technology\n\n## How Solar Works\nSolar panels convert sunlight directly into electricity using photovoltaic cells.\n\n## Types of Solar Systems\n- **Grid-tied**: Connected to the electrical grid\n- **Off-grid**: Standalone systems with battery storage\n- **Hybrid**: Combination of grid-tied and battery backup\n\n## Installation Considerations\n- Roof orientation and shading\n- Local climate and sun exposure\n- Energy consumption patterns\n- Available incentives and rebates") + .add_page("# Smart Home Technology\n\n## Automation Benefits\n- Optimized energy usage\n- Enhanced comfort and convenience\n- Remote monitoring and control\n- Predictive maintenance\n\n## Key Technologies\n- Smart thermostats\n- Automated lighting systems\n- Energy monitoring devices\n- Smart appliances\n- Home energy management systems") + .add_toc_entry(new_toc_entry().title("Green Technology Overview").page(0)) + .add_toc_entry(new_toc_entry().title("Solar Technology").page(1)) + .add_toc_entry(new_toc_entry().title("Smart Home Technology").page(2))); + +let community_book = save_book(new_book() + .title("Building Communities") + .description("Guide to creating sustainable and inclusive communities") + .add_page("# Community Building Fundamentals\n\n## What Makes a Strong Community?\n- Shared values and vision\n- Open communication channels\n- Mutual support and cooperation\n- Inclusive decision-making processes\n\n## Benefits of Strong Communities\n- Enhanced quality of life\n- Economic resilience\n- Social cohesion\n- Environmental stewardship") + .add_page("# Governance and Leadership\n\n## Collaborative Leadership\n- Distributed decision-making\n- Transparent processes\n- Accountability mechanisms\n- Conflict resolution systems\n\n## Community Engagement\n- Regular town halls and meetings\n- Digital participation platforms\n- Volunteer coordination\n- Feedback and improvement cycles") + .add_toc_entry(new_toc_entry().title("Community Building Fundamentals").page(0)) + .add_toc_entry(new_toc_entry().title("Governance and Leadership").page(1))); + +// === SLIDES === +print("Creating slides..."); + +let climate_slides = save_slides(new_slides() + .title("Climate Change Awareness") + .description("Visual presentation on climate change impacts and solutions") + .add_slide("https://images.unsplash.com/photo-1569163139394-de4e4f43e4e3?w=1200", "Global Temperature Rise") + .add_slide("https://images.unsplash.com/photo-1578662996442-48f60103fc96?w=1200", "Melting Ice Caps") + .add_slide("https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=1200", "Extreme Weather Events") + .add_slide("https://images.unsplash.com/photo-1473341304170-971dccb5ac1e?w=1200", "Renewable Energy Solutions") + .add_slide("https://images.unsplash.com/photo-1497436072909-f5e4be1dffea?w=1200", "Sustainable Transportation")); + +let innovation_slides = save_slides(new_slides() + .title("Innovation Showcase") + .description("Cutting-edge technologies for a sustainable future") + .add_slide("https://images.unsplash.com/photo-1518709268805-4e9042af2176?w=1200", "AI and Machine Learning") + .add_slide("https://images.unsplash.com/photo-1639322537228-f710d846310a?w=1200", "Blockchain Technology") + .add_slide("https://images.unsplash.com/photo-1581092160562-40aa08e78837?w=1200", "IoT and Smart Cities") + .add_slide("https://images.unsplash.com/photo-1581092918056-0c4c3acd3789?w=1200", "Quantum Computing") + .add_slide("https://images.unsplash.com/photo-1581092162384-8987c1d64718?w=1200", "Biotechnology Advances")); + +let nature_slides = save_slides(new_slides() + .title("Biodiversity Gallery") + .description("Celebrating Earth's incredible biodiversity") + .add_slide("https://images.unsplash.com/photo-1564349683136-77e08dba1ef7?w=1200", "Tropical Rainforest") + .add_slide("https://images.unsplash.com/photo-1559827260-dc66d52bef19?w=1200", "Coral Reef Ecosystem") + .add_slide("https://images.unsplash.com/photo-1551698618-1dfe5d97d256?w=1200", "Arctic Wildlife") + .add_slide("https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1200", "Mountain Ecosystems")); + +// === COLLECTIONS === +print("Creating collections..."); + +let nature_collection = save_collection(new_collection() + .title("Nature & Environment") + .description("Beautiful images and resources about our natural world") + .add_image(nature1.id) + .add_image(nature2.id) + .add_image(nature3.id) + .add_pdf(pdf1.id) + .add_markdown(md1.id) + .add_book(sustainability_book.id) + .add_slides(nature_slides.id)); + +let technology_collection = save_collection(new_collection() + .title("Sustainable Technology") + .description("Innovations driving positive change") + .add_image(tech1.id) + .add_image(tech2.id) + .add_pdf(pdf3.id) + .add_pdf(pdf4.id) + .add_markdown(md3.id) + .add_book(tech_guide_book.id) + .add_slides(innovation_slides.id)); + +let space_collection = save_collection(new_collection() + .title("Space & Cosmos") + .description("Exploring the universe and our place in it") + .add_image(space1.id) + .add_image(space2.id) + .add_pdf(pdf2.id) + .add_markdown(md2.id)); + +let community_collection = save_collection(new_collection() + .title("Community & Collaboration") + .description("Building better communities together") + .add_image(city1.id) + .add_pdf(pdf5.id) + .add_markdown(md4.id) + .add_book(community_book.id)); + +let climate_collection = save_collection(new_collection() + .title("Climate Action") + .description("Understanding and addressing climate change") + .add_slides(climate_slides.id) + .add_pdf(pdf1.id) + .add_markdown(md1.id)); + +print("✅ OurWorld library created successfully!"); +print("📚 Collections: 5"); +print("🖼️ Images: 8"); +print("📄 PDFs: 5"); +print("📝 Markdown docs: 4"); +print("📖 Books: 3"); +print("🎞️ Slide shows: 3"); \ No newline at end of file diff --git a/examples/ourworld/scripts/threefold.rhai b/examples/ourworld/scripts/threefold.rhai new file mode 100644 index 0000000..0087319 --- /dev/null +++ b/examples/ourworld/scripts/threefold.rhai @@ -0,0 +1,249 @@ +// OurWorld Circle and Library Data + +new_circle() + .title("Threefold DMCC") + .description("Creating a better world.") + .ws_url("ws://localhost:8093/ws") + .logo("🌍") + .save_circle(); + +let circle = get_circle(); + +print("--- Creating OurWorld Library ---"); + +// === IMAGES === +print("Creating images..."); + +let nature1 = save_image(new_image() + .title("Mountain Sunrise") + .description("Breathtaking sunrise over mountain peaks") + .url("https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=800") + .width(800).height(600)); + +let nature2 = save_image(new_image() + .title("Ocean Waves") + .description("Powerful ocean waves crashing on rocks") + .url("https://images.unsplash.com/photo-1505142468610-359e7d316be0?w=800") + .width(800).height(600)); + +let nature3 = save_image(new_image() + .title("Forest Path") + .description("Peaceful path through ancient forest") + .url("https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=800") + .width(800).height(600)); + +let tech1 = save_image(new_image() + .title("Solar Panels") + .description("Modern solar panel installation") + .url("https://images.unsplash.com/photo-1509391366360-2e959784a276?w=800") + .width(800).height(600)); + +let tech2 = save_image(new_image() + .title("Wind Turbines") + .description("Wind turbines generating clean energy") + .url("https://images.unsplash.com/photo-1466611653911-95081537e5b7?w=800") + .width(800).height(600)); + +let space1 = save_image(new_image() + .title("Earth from Space") + .description("Our beautiful planet from orbit") + .url("https://images.unsplash.com/photo-1446776877081-d282a0f896e2?w=800") + .width(800).height(600)); + +let space2 = save_image(new_image() + .title("Galaxy Spiral") + .description("Stunning spiral galaxy in deep space") + .url("https://images.unsplash.com/photo-1502134249126-9f3755a50d78?w=800") + .width(800).height(600)); + +let city1 = save_image(new_image() + .title("Smart City") + .description("Futuristic smart city at night") + .url("https://images.unsplash.com/photo-1480714378408-67cf0d13bc1f?w=800") + .width(800).height(600)); + +// === PDFs === +print("Creating PDFs..."); + +let pdf1 = save_pdf(new_pdf() + .title("Climate Action Report 2024") + .description("Comprehensive analysis of global climate initiatives") + .url("https://www.ipcc.ch/site/assets/uploads/2018/02/ipcc_wg3_ar5_summary-for-policymakers.pdf") + .page_count(42)); + +let pdf2 = save_pdf(new_pdf() + .title("Sustainable Development Goals") + .description("UN SDG implementation guide") + .url("https://sdgs.un.org/sites/default/files/publications/21252030%20Agenda%20for%20Sustainable%20Development%20web.pdf") + .page_count(35)); + +let pdf3 = save_pdf(new_pdf() + .title("Renewable Energy Handbook") + .description("Technical guide to renewable energy systems") + .url("https://www.irena.org/-/media/Files/IRENA/Agency/Publication/2019/Oct/IRENA_Renewable-Energy-Statistics-2019.pdf") + .page_count(280)); + +let pdf4 = save_pdf(new_pdf() + .title("Blockchain for Good") + .description("How blockchain technology can solve global challenges") + .url("https://www.weforum.org/whitepapers/blockchain-beyond-the-hype") + .page_count(24)); + +let pdf5 = save_pdf(new_pdf() + .title("Future of Work Report") + .description("Analysis of changing work patterns and remote collaboration") + .url("https://www.mckinsey.com/featured-insights/future-of-work") + .page_count(156)); + +// === MARKDOWN DOCUMENTS === +print("Creating markdown documents..."); + +let md1 = save_markdown(new_markdown() + .title("OurWorld Mission Statement") + .description("Our vision for a better world") + .content("# OurWorld Mission\n\n## Vision\nTo create a more sustainable, equitable, and connected world through technology and collaboration.\n\n## Values\n- **Sustainability**: Every decision considers environmental impact\n- **Inclusivity**: Technology that serves everyone\n- **Transparency**: Open source and open governance\n- **Innovation**: Pushing boundaries for positive change\n\n## Goals\n1. Reduce global carbon footprint by 50% by 2030\n2. Provide internet access to 1 billion underserved people\n3. Create 10 million green jobs worldwide\n4. Establish 1000 sustainable communities")); + +let md2 = save_markdown(new_markdown() + .title("Getting Started Guide") + .description("How to join the OurWorld movement") + .content("# Getting Started with OurWorld\n\n## Welcome!\nThank you for joining our mission to create a better world.\n\n## First Steps\n1. **Explore**: Browse our projects and initiatives\n2. **Connect**: Join our community forums\n3. **Contribute**: Find ways to get involved\n4. **Learn**: Access our educational resources\n\n## Ways to Contribute\n- **Developers**: Contribute to open source projects\n- **Activists**: Organize local initiatives\n- **Educators**: Share knowledge and skills\n- **Investors**: Support sustainable ventures\n\n## Resources\n- [Community Forum](https://forum.ourworld.tf)\n- [Developer Portal](https://dev.ourworld.tf)\n- [Learning Hub](https://learn.ourworld.tf)")); + +let md3 = save_markdown(new_markdown() + .title("Technology Roadmap 2024") + .description("Our technical development plans") + .content("# Technology Roadmap 2024\n\n## Q1 Objectives\n- Launch decentralized identity system\n- Deploy carbon tracking blockchain\n- Release mobile app v2.0\n\n## Q2 Objectives\n- Implement AI-powered resource optimization\n- Launch peer-to-peer energy trading platform\n- Deploy IoT sensor network\n\n## Q3 Objectives\n- Release virtual collaboration spaces\n- Launch digital twin cities pilot\n- Implement quantum-safe encryption\n\n## Q4 Objectives\n- Deploy autonomous governance systems\n- Launch global impact measurement platform\n- Release AR/VR sustainability training")); + +let md4 = save_markdown(new_markdown() + .title("Community Guidelines") + .description("How we work together") + .content("# Community Guidelines\n\n## Our Principles\n- **Respect**: Treat everyone with dignity\n- **Collaboration**: Work together towards common goals\n- **Constructive**: Focus on solutions, not problems\n- **Inclusive**: Welcome diverse perspectives\n\n## Communication Standards\n- Use clear, respectful language\n- Listen actively to others\n- Provide constructive feedback\n- Share knowledge freely\n\n## Conflict Resolution\n1. Address issues directly and respectfully\n2. Seek to understand different viewpoints\n3. Involve mediators when needed\n4. Focus on solutions that benefit everyone")); + + +let investor = new_contact() + .name("Example Investor") + .save_contact(); + +let investors = new_group() + .name("Investors") + .description("A group for example inverstors of ourworld"); + +investors.add_contact(investor.id) + .save_group(); + +// === BOOKS === +print("Creating books..."); + +let sustainability_book = save_book(new_book() + .title("Sustainability Handbook") + .description("Complete guide to sustainable living and practices") + .add_page("# Introduction to Sustainability\n\nSustainability is about meeting our present needs without compromising the ability of future generations to meet their own needs.\n\n## Key Principles\n- Environmental stewardship\n- Social equity\n- Economic viability\n\n## Why It Matters\nOur planet faces unprecedented challenges from climate change, resource depletion, and environmental degradation.") + .add_page("# Energy Efficiency\n\n## Home Energy Savings\n- LED lighting reduces energy consumption by 75%\n- Smart thermostats can save 10-15% on heating/cooling\n- Energy-efficient appliances make a significant difference\n\n## Renewable Energy\n- Solar panels: Clean electricity from sunlight\n- Wind power: Harnessing natural wind currents\n- Hydroelectric: Using water flow for energy\n\n## Transportation\n- Electric vehicles reduce emissions\n- Public transit decreases individual carbon footprint\n- Cycling and walking for short distances") + .add_page("# Waste Reduction\n\n## The 5 R's\n1. **Refuse**: Say no to unnecessary items\n2. **Reduce**: Use less of what you need\n3. **Reuse**: Find new purposes for items\n4. **Recycle**: Process materials into new products\n5. **Rot**: Compost organic waste\n\n## Practical Tips\n- Use reusable bags and containers\n- Buy products with minimal packaging\n- Repair instead of replacing\n- Donate items you no longer need") + .add_page("# Sustainable Food\n\n## Local and Seasonal\n- Support local farmers and reduce transport emissions\n- Eat seasonal produce for better nutrition and taste\n- Visit farmers markets and join CSAs\n\n## Plant-Based Options\n- Reduce meat consumption for environmental benefits\n- Explore diverse plant proteins\n- Grow your own herbs and vegetables\n\n## Food Waste Prevention\n- Plan meals and make shopping lists\n- Store food properly to extend freshness\n- Use leftovers creatively") + .add_toc_entry(new_toc_entry().title("Introduction to Sustainability").page(0)) + .add_toc_entry(new_toc_entry().title("Energy Efficiency").page(1)) + .add_toc_entry(new_toc_entry().title("Waste Reduction").page(2)) + .add_toc_entry(new_toc_entry().title("Sustainable Food").page(3))); + +let tech_guide_book = save_book(new_book() + .title("Green Technology Guide") + .description("Understanding and implementing green technologies") + .add_page("# Green Technology Overview\n\nGreen technology, also known as clean technology, refers to the use of science and technology to create products and services that are environmentally friendly.\n\n## Categories\n- Renewable energy systems\n- Energy efficiency technologies\n- Pollution prevention and cleanup\n- Sustainable materials and manufacturing\n\n## Benefits\n- Reduced environmental impact\n- Lower operating costs\n- Improved public health\n- Economic opportunities") + .add_page("# Solar Technology\n\n## How Solar Works\nSolar panels convert sunlight directly into electricity using photovoltaic cells.\n\n## Types of Solar Systems\n- **Grid-tied**: Connected to the electrical grid\n- **Off-grid**: Standalone systems with battery storage\n- **Hybrid**: Combination of grid-tied and battery backup\n\n## Installation Considerations\n- Roof orientation and shading\n- Local climate and sun exposure\n- Energy consumption patterns\n- Available incentives and rebates") + .add_page("# Smart Home Technology\n\n## Automation Benefits\n- Optimized energy usage\n- Enhanced comfort and convenience\n- Remote monitoring and control\n- Predictive maintenance\n\n## Key Technologies\n- Smart thermostats\n- Automated lighting systems\n- Energy monitoring devices\n- Smart appliances\n- Home energy management systems") + .add_toc_entry(new_toc_entry().title("Green Technology Overview").page(0)) + .add_toc_entry(new_toc_entry().title("Solar Technology").page(1)) + .add_toc_entry(new_toc_entry().title("Smart Home Technology").page(2))); + +let community_book = save_book(new_book() + .title("Building Communities") + .description("Guide to creating sustainable and inclusive communities") + .add_page("# Community Building Fundamentals\n\n## What Makes a Strong Community?\n- Shared values and vision\n- Open communication channels\n- Mutual support and cooperation\n- Inclusive decision-making processes\n\n## Benefits of Strong Communities\n- Enhanced quality of life\n- Economic resilience\n- Social cohesion\n- Environmental stewardship") + .add_page("# Governance and Leadership\n\n## Collaborative Leadership\n- Distributed decision-making\n- Transparent processes\n- Accountability mechanisms\n- Conflict resolution systems\n\n## Community Engagement\n- Regular town halls and meetings\n- Digital participation platforms\n- Volunteer coordination\n- Feedback and improvement cycles") + .add_toc_entry(new_toc_entry().title("Community Building Fundamentals").page(0)) + .add_toc_entry(new_toc_entry().title("Governance and Leadership").page(1))); + +// === SLIDES === +print("Creating slides..."); + +let climate_slides = save_slides(new_slides() + .title("Climate Change Awareness") + .description("Visual presentation on climate change impacts and solutions") + .add_slide("https://images.unsplash.com/photo-1569163139394-de4e4f43e4e3?w=1200", "Global Temperature Rise") + .add_slide("https://images.unsplash.com/photo-1578662996442-48f60103fc96?w=1200", "Melting Ice Caps") + .add_slide("https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=1200", "Extreme Weather Events") + .add_slide("https://images.unsplash.com/photo-1473341304170-971dccb5ac1e?w=1200", "Renewable Energy Solutions") + .add_slide("https://images.unsplash.com/photo-1497436072909-f5e4be1dffea?w=1200", "Sustainable Transportation")); + +let innovation_slides = save_slides(new_slides() + .title("Innovation Showcase") + .description("Cutting-edge technologies for a sustainable future") + .add_slide("https://images.unsplash.com/photo-1518709268805-4e9042af2176?w=1200", "AI and Machine Learning") + .add_slide("https://images.unsplash.com/photo-1639322537228-f710d846310a?w=1200", "Blockchain Technology") + .add_slide("https://images.unsplash.com/photo-1581092160562-40aa08e78837?w=1200", "IoT and Smart Cities") + .add_slide("https://images.unsplash.com/photo-1581092918056-0c4c3acd3789?w=1200", "Quantum Computing") + .add_slide("https://images.unsplash.com/photo-1581092162384-8987c1d64718?w=1200", "Biotechnology Advances")); + +let nature_slides = save_slides(new_slides() + .title("Biodiversity Gallery") + .description("Celebrating Earth's incredible biodiversity") + .add_slide("https://images.unsplash.com/photo-1564349683136-77e08dba1ef7?w=1200", "Tropical Rainforest") + .add_slide("https://images.unsplash.com/photo-1559827260-dc66d52bef19?w=1200", "Coral Reef Ecosystem") + .add_slide("https://images.unsplash.com/photo-1551698618-1dfe5d97d256?w=1200", "Arctic Wildlife") + .add_slide("https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1200", "Mountain Ecosystems")); + +// === COLLECTIONS === +print("Creating collections..."); + +let nature_collection = save_collection(new_collection() + .title("Nature & Environment") + .description("Beautiful images and resources about our natural world") + .add_image(nature1.id) + .add_image(nature2.id) + .add_image(nature3.id) + .add_pdf(pdf1.id) + .add_markdown(md1.id) + .add_book(sustainability_book.id) + .add_slides(nature_slides.id)); + +let technology_collection = save_collection(new_collection() + .title("Sustainable Technology") + .description("Innovations driving positive change") + .add_image(tech1.id) + .add_image(tech2.id) + .add_pdf(pdf3.id) + .add_pdf(pdf4.id) + .add_markdown(md3.id) + .add_book(tech_guide_book.id) + .add_slides(innovation_slides.id)); + +let space_collection = save_collection(new_collection() + .title("Space & Cosmos") + .description("Exploring the universe and our place in it") + .add_image(space1.id) + .add_image(space2.id) + .add_pdf(pdf2.id) + .add_markdown(md2.id)); + +let community_collection = save_collection(new_collection() + .title("Community & Collaboration") + .description("Building better communities together") + .add_image(city1.id) + .add_pdf(pdf5.id) + .add_markdown(md4.id) + .add_book(community_book.id)); + +let climate_collection = save_collection(new_collection() + .title("Climate Action") + .description("Understanding and addressing climate change") + .add_slides(climate_slides.id) + .add_pdf(pdf1.id) + .add_markdown(md1.id)); + +print("✅ OurWorld library created successfully!"); +print("📚 Collections: 5"); +print("🖼️ Images: 8"); +print("📄 PDFs: 5"); +print("📝 Markdown docs: 4"); +print("📖 Books: 3"); +print("🎞️ Slide shows: 3"); \ No newline at end of file diff --git a/server_ws/examples/e2e_rhai_flow.rs b/examples/server_e2e_rhai_flow.rs similarity index 82% rename from server_ws/examples/e2e_rhai_flow.rs rename to examples/server_e2e_rhai_flow.rs index 39a03f5..ff9724a 100644 --- a/server_ws/examples/e2e_rhai_flow.rs +++ b/examples/server_e2e_rhai_flow.rs @@ -8,15 +8,15 @@ use tokio::time::sleep; // use serde_json::Value; // No longer needed as CircleWsClient::play takes String // Uuid is handled by CircleWsClient internally for requests. // use uuid::Uuid; -use circle_client_ws::CircleWsClient; +use circle_client_ws::CircleWsClientBuilder; // PlayResultClient and CircleWsClientError will be resolved via the client methods if needed, // or this indicates they were not actually needed in the scope of this file directly. // The compiler warning suggests they are unused from this specific import. const TEST_CIRCLE_NAME: &str = "e2e_test_circle"; const TEST_SERVER_PORT: u16 = 9876; // Choose a unique port for the test -const RHAI_WORKER_BIN_NAME: &str = "rhai_worker"; -const CIRCLE_SERVER_WS_BIN_NAME: &str = "circle_server_ws"; +const RHAI_WORKER_BIN_NAME: &str = "worker"; +const CIRCLE_SERVER_WS_BIN_NAME: &str = "server_ws"; // RAII guard for cleaning up child processes struct ChildProcessGuard { @@ -48,20 +48,9 @@ impl Drop for ChildProcessGuard { } fn find_target_dir() -> Result { - // Try to find the cargo target directory relative to current exe or manifest - let mut current_exe = std::env::current_exe().map_err(|e| format!("Failed to get current exe path: {}", e))?; - // current_exe is target/debug/examples/e2e_rhai_flow - // want target/debug/ - if current_exe.ends_with("examples/e2e_rhai_flow") { // Adjust if example name changes - current_exe.pop(); // remove e2e_rhai_flow - current_exe.pop(); // remove examples - Ok(current_exe) - } else { - // Fallback: Assume 'target/debug' relative to workspace root if CARGO_MANIFEST_DIR is set - let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").map_err(|_| "CARGO_MANIFEST_DIR not set".to_string())?; - let workspace_root = PathBuf::from(manifest_dir).parent().ok_or("Failed to get workspace root")?.to_path_buf(); - Ok(workspace_root.join("target").join("debug")) - } + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").map_err(|_| "CARGO_MANIFEST_DIR not set".to_string())?; + let workspace_root = PathBuf::from(manifest_dir).parent().ok_or("Failed to get workspace root")?.to_path_buf(); + Ok(workspace_root.join("target").join("debug")) } @@ -108,7 +97,7 @@ async fn main() -> Result<(), Box> { let ws_url_str = format!("ws://127.0.0.1:{}/ws", TEST_SERVER_PORT); log::info!("Creating CircleWsClient for {}...", ws_url_str); - let mut client = CircleWsClient::new(ws_url_str.clone()); + let mut client = CircleWsClientBuilder::new(ws_url_str.clone()).build(); log::info!("Connecting CircleWsClient..."); client.connect().await.map_err(|e| { diff --git a/server_ws/examples/timeout_demonstration.rs b/examples/server_timeout_demonstration.rs similarity index 75% rename from server_ws/examples/timeout_demonstration.rs rename to examples/server_timeout_demonstration.rs index 50743f5..a3a5a1a 100644 --- a/server_ws/examples/timeout_demonstration.rs +++ b/examples/server_timeout_demonstration.rs @@ -6,7 +6,7 @@ // This example will attempt to start its own instance of circle_server_ws. // Ensure circle_server_ws is compiled (cargo build --bin circle_server_ws). -use circle_client_ws::CircleWsClient; +use circle_client_ws::CircleWsClientBuilder; use tokio::time::{sleep, Duration}; use std::process::{Command, Child, Stdio}; use std::path::PathBuf; @@ -14,7 +14,7 @@ use std::path::PathBuf; const EXAMPLE_SERVER_PORT: u16 = 8089; // Using a specific port for this example const WS_URL: &str = "ws://127.0.0.1:8089/ws"; const CIRCLE_NAME_FOR_EXAMPLE: &str = "timeout_example_circle"; -const CIRCLE_SERVER_WS_BIN_NAME: &str = "circle_server_ws"; +const CIRCLE_SERVER_WS_BIN_NAME: &str = "server_ws"; const SCRIPT_TIMEOUT_SECONDS: u64 = 30; // This is the server-side timeout we expect to hit // RAII guard for cleaning up child processes @@ -47,28 +47,10 @@ impl Drop for ChildProcessGuard { } fn find_target_bin_path(bin_name: &str) -> Result { - let mut current_exe = std::env::current_exe().map_err(|e| format!("Failed to get current exe path: {}", e))?; - // current_exe is typically target/debug/examples/timeout_demonstration - // We want to find target/debug/[bin_name] - current_exe.pop(); // remove executable name - current_exe.pop(); // remove examples directory - let target_debug_dir = current_exe; - let bin_path = target_debug_dir.join(bin_name); + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").map_err(|_| "CARGO_MANIFEST_DIR not set".to_string())?; + let workspace_root = PathBuf::from(manifest_dir).parent().ok_or("Failed to get workspace root")?.to_path_buf(); + let bin_path = workspace_root.join("target").join("debug").join(bin_name); if !bin_path.exists() { - // Fallback: try CARGO_BIN_EXE_[bin_name] if running via `cargo run --example` which sets these - if let Ok(cargo_bin_path_str) = std::env::var(format!("CARGO_BIN_EXE_{}", bin_name.to_uppercase())) { - let cargo_bin_path = PathBuf::from(cargo_bin_path_str); - if cargo_bin_path.exists() { - return Ok(cargo_bin_path); - } - } - // Fallback: try target/debug/[bin_name] relative to CARGO_MANIFEST_DIR (crate root) - if let Ok(manifest_dir_str) = std::env::var("CARGO_MANIFEST_DIR") { - let bin_path_rel_manifest = PathBuf::from(manifest_dir_str).join("target").join("debug").join(bin_name); - if bin_path_rel_manifest.exists() { - return Ok(bin_path_rel_manifest); - } - } return Err(format!("Binary '{}' not found at {:?}. Ensure it's built.", bin_name, bin_path)); } Ok(bin_path) @@ -98,7 +80,7 @@ async fn main() -> Result<(), Box> { sleep(Duration::from_secs(3)).await; // Wait for server to initialize log::info!("Attempting to connect to WebSocket server at: {}", WS_URL); - let mut client = CircleWsClient::new(WS_URL.to_string()); + let mut client = CircleWsClientBuilder::new(WS_URL.to_string()).build(); log::info!("Connecting client..."); if let Err(e) = client.connect().await { @@ -110,16 +92,11 @@ async fn main() -> Result<(), Box> { // This Rhai script is designed to run for much longer than the typical server timeout. let long_running_script = " - log(\"Rhai: Starting long-running script...\"); let mut x = 0; for i in 0..9999999999 { // Extremely large loop x = x + i; - if i % 100000000 == 0 { - // log(\"Rhai: Loop iteration \" + i); - } } // This part should not be reached if timeout works correctly. - log(\"Rhai: Long-running script finished calculation (x = \" + x + \").\"); print(x); x ".to_string(); @@ -135,7 +112,7 @@ async fn main() -> Result<(), Box> { log::info!("Received expected error from play request: {}", e); log::info!("This demonstrates the server timing out the script execution."); // You can further inspect the error details if CircleWsClientError provides them. - // For example, if e.to_string() contains 'code: -32002' or 'timed out'. + // For example, if e.to_string().contains('code: -32002' or 'timed out'. if e.to_string().contains("timed out") || e.to_string().contains("-32002") { log::info!("Successfully received timeout error from the server!"); } else { @@ -150,4 +127,4 @@ async fn main() -> Result<(), Box> { log::info!("Timeout demonstration example finished."); Ok(()) -} +} \ No newline at end of file diff --git a/rhai_repl_cli/Cargo.toml b/rhai_repl_cli/Cargo.toml deleted file mode 100644 index 7759083..0000000 --- a/rhai_repl_cli/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "rhai_repl_cli" -version = "0.1.0" -edition = "2024" # Keep 2024 unless issues arise - -[dependencies] -tokio = { version = "1", features = ["macros", "rt-multi-thread", "time"] } # Added "time" for potential timeouts -tokio-tungstenite = { version = "0.21", features = ["native-tls"] } # May be removed if client_ws handles all -futures-util = "0.3" -url = "2" -tracing = "0.1" # For logging -tracing-subscriber = { version = "0.3", features = ["env-filter"] } -log = "0.4" # circle_client_ws uses log crate -rustyline = { version = "13.0.0", features = ["derive"] } # For enhanced REPL input -tempfile = "3.8" # For creating temporary files for editing - -circle_client_ws = { path = "../client_ws" } diff --git a/server_ws/Cargo.toml b/server_ws/Cargo.toml deleted file mode 100644 index 9b860df..0000000 --- a/server_ws/Cargo.toml +++ /dev/null @@ -1,32 +0,0 @@ -[package] -name = "circle_ws_lib" # Renamed to reflect library nature -version = "0.1.0" -edition = "2021" - -[lib] -name = "circle_ws_lib" -path = "src/lib.rs" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -actix-web = "4" -actix-web-actors = "4" -actix = "0.13" -env_logger = "0.10" # Keep for logging within the lib -log = "0.4" -# clap is removed as CLI parsing moves to the orchestrator bin -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -redis = { version = "0.25.0", features = ["tokio-comp"] } # For async Redis with Actix -uuid = { version = "1.6", features = ["v4", "serde"] } # Still used by RhaiClient or for task details -tokio = { version = "1", features = ["macros", "rt-multi-thread", "time"] } # Added "time" for Duration -chrono = { version = "0.4", features = ["serde"] } # For timestamps -rhai_client = { path = "../../rhailib/src/client" } # Corrected relative path - -[dev-dependencies] -tokio-tungstenite = { version = "0.23.0", features = ["native-tls"] } -futures-util = "0.3" # For StreamExt and SinkExt on WebSocket stream -url = "2.5.0" # For parsing WebSocket URL -# circle_client_ws = { path = "../client_ws" } # This might need adjustment if it's a test client for the old binary -# uuid = { version = "1.6", features = ["v4", "serde"] } # Already in dependencies diff --git a/server_ws/README.md b/server_ws/README.md deleted file mode 100644 index 065abe8..0000000 --- a/server_ws/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# Circle Server WebSocket (`server_ws`) - -## Overview - -The `server_ws` component is an Actix-based WebSocket server designed to handle client connections and execute Rhai scripts. It acts as a bridge between WebSocket clients and a Rhai scripting engine, facilitating remote script execution and result retrieval. - -## Key Features - -* **WebSocket Communication:** Establishes and manages WebSocket connections with clients. -* **Rhai Script Execution:** Receives Rhai scripts from clients, submits them for execution via `rhai_client`, and returns the results. -* **Timeout Management:** Implements timeouts for Rhai script execution to prevent indefinite blocking, returning specific error codes on timeout. -* **Asynchronous Processing:** Leverages Actix actors for concurrent handling of multiple client connections and script executions. - -## Core Components - -* **`CircleWs` Actor:** The primary Actix actor responsible for handling individual WebSocket sessions. It manages the lifecycle of a client connection, processes incoming messages (Rhai scripts), and sends back results or errors. -* **`rhai_client` Integration:** Utilizes the `rhai_client` crate to submit scripts to a shared Rhai processing service (likely Redis-backed for task queuing and result storage) and await their completion. - -## Dependencies - -* `actix`: Actor framework for building concurrent applications. -* `actix-web-actors`: WebSocket support for Actix. -* `rhai_client`: Client library for interacting with the Rhai scripting service. -* `serde_json`: For serializing and deserializing JSON messages exchanged over WebSockets. -* `uuid`: For generating unique task identifiers. - -## Workflow - -1. A client establishes a WebSocket connection to the `/ws/` endpoint. -2. The server upgrades the connection and spawns a `CircleWs` actor instance for that session. -3. The client sends a JSON-RPC formatted message containing the Rhai script to be executed. -4. The `CircleWs` actor parses the message and uses `rhai_client::RhaiClient::submit_script_and_await_result` to send the script for execution. This method handles the interaction with the underlying task queue (e.g., Redis) and waits for the script's outcome. -5. The `rhai_client` will return the script's result or an error (e.g., timeout, script error). -6. `CircleWs` formats the result/error into a JSON-RPC response and sends it back to the client over the WebSocket. - -## Configuration - -* **`REDIS_URL`**: The `rhai_client` component (and thus `server_ws` indirectly) relies on a Redis instance. The connection URL for this Redis instance is typically configured via an environment variable or a constant that `rhai_client` uses. -* **Timeout Durations**: - * `TASK_TIMEOUT_DURATION` (e.g., 30 seconds): The maximum time the server will wait for a Rhai script to complete execution. - * `TASK_POLL_INTERVAL_DURATION` (e.g., 200 milliseconds): The interval at which the `rhai_client` polls for task completion (this is an internal detail of `rhai_client` but relevant to understanding its behavior). - -## Error Handling - -The server implements specific JSON-RPC error responses for various scenarios: -* **Script Execution Timeout:** If a script exceeds `TASK_TIMEOUT_DURATION`, a specific error (e.g., code -32002) is returned. -* **Other `RhaiClientError`s:** Other errors originating from `rhai_client` (e.g., issues with the Redis connection, script compilation errors detected by the remote Rhai engine) are also translated into appropriate JSON-RPC error responses. -* **Message Parsing Errors:** Invalid incoming messages will result in error responses. - -## How to Run - -(Instructions on how to build and run the server would typically go here, e.g., `cargo run --bin circle_server_ws`) diff --git a/server_ws/src/lib.rs b/server_ws/src/lib.rs deleted file mode 100644 index 26ff3c6..0000000 --- a/server_ws/src/lib.rs +++ /dev/null @@ -1,302 +0,0 @@ -use actix_web::{web, App, HttpRequest, HttpServer, HttpResponse, Error}; -use actix_web_actors::ws; -use actix::{Actor, ActorContext, StreamHandler, AsyncContext, WrapFuture, ActorFutureExt}; -// clap::Parser removed -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use std::time::Duration; -use rhai_client::RhaiClientError; -use rhai_client::RhaiClient; -use tokio::task::JoinHandle; -use tokio::sync::oneshot; // For sending the server handle back - -// Newtype wrappers for distinct app_data types -#[derive(Clone)] -struct AppCircleName(String); - -#[derive(Clone)] -struct AppRedisUrl(String); - -// JSON-RPC 2.0 Structures (remain the same) -#[derive(Serialize, Deserialize, Debug, Clone)] -struct JsonRpcRequest { - jsonrpc: String, - method: String, - params: Value, - id: Option, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -struct JsonRpcResponse { - jsonrpc: String, - result: Option, - error: Option, - id: Value, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -struct JsonRpcError { - code: i32, - message: String, - data: Option, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -struct PlayParams { - script: String, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -struct PlayResult { - output: String, -} - -// WebSocket Actor -struct CircleWs { - server_circle_name: String, - redis_url_for_client: String, -} - -const TASK_TIMEOUT_DURATION: Duration = Duration::from_secs(30); -const TASK_POLL_INTERVAL_DURATION: Duration = Duration::from_millis(200); - -impl CircleWs { - fn new(name: String, redis_url: String) -> Self { - Self { - server_circle_name: name, - redis_url_for_client: redis_url, - } - } -} - -impl Actor for CircleWs { - type Context = ws::WebsocketContext; - - fn started(&mut self, _ctx: &mut Self::Context) { - log::info!("WebSocket session started for server dedicated to: {}", self.server_circle_name); - } - - fn stopping(&mut self, _ctx: &mut Self::Context) -> actix::Running { - log::info!("WebSocket session stopping for server dedicated to: {}", self.server_circle_name); - actix::Running::Stop - } -} - -impl StreamHandler> for CircleWs { - fn handle(&mut self, msg: Result, ctx: &mut Self::Context) { - match msg { - Ok(ws::Message::Text(text)) => { - log::debug!("WS Text for {}: {}", self.server_circle_name, text); // Changed to debug for less noise - match serde_json::from_str::(&text) { - Ok(req) => { - let client_rpc_id = req.id.clone().unwrap_or(Value::Null); - if req.method == "play" { - match serde_json::from_value::(req.params.clone()) { - Ok(play_params) => { - let script_content = play_params.script; - // Use the server_circle_name which should be correctly set now - let current_circle_name_for_rhai_client = self.server_circle_name.clone(); - let rpc_id_for_client = client_rpc_id.clone(); - let redis_url_clone = self.redis_url_for_client.clone(); - - log::info!("Circle '{}' WS: Received 'play' request, ID: {:?}, for RhaiClient target circle: '{}'", self.server_circle_name, rpc_id_for_client, current_circle_name_for_rhai_client); - - let fut = async move { - match RhaiClient::new(&redis_url_clone) { - Ok(rhai_task_client) => { - rhai_task_client.submit_script_and_await_result( - ¤t_circle_name_for_rhai_client, // This name is used for Redis queue - script_content, - Some(rpc_id_for_client.clone()), - TASK_TIMEOUT_DURATION, - TASK_POLL_INTERVAL_DURATION, - ).await - } - Err(e) => { - log::error!("Circle '{}' WS: Failed to create RhaiClient for Redis URL {}: {}", current_circle_name_for_rhai_client, redis_url_clone, e); - Err(e) - } - } - }; - - ctx.spawn(fut.into_actor(self).map(move |result, _act, ws_ctx| { - let response = match result { - Ok(task_details) => { - if task_details.status == "completed" { - // task_details itself doesn't have a task_id field. - // The task_id is known by the client that initiated the poll. - // We log with client_rpc_id which is the JSON-RPC request ID. - log::info!("Circle '{}' WS: Request ID {:?} completed successfully. Output: {:?}", _act.server_circle_name, client_rpc_id, task_details.output); - JsonRpcResponse { - jsonrpc: "2.0".to_string(), - result: Some(serde_json::to_value(PlayResult { - output: task_details.output.unwrap_or_default() - }).unwrap()), - error: None, - id: client_rpc_id, - } - } else { // status == "error" - log::warn!("Circle '{}' WS: Request ID {:?} execution failed. Error: {:?}", _act.server_circle_name, client_rpc_id, task_details.error); - JsonRpcResponse { - jsonrpc: "2.0".to_string(), - result: None, - error: Some(JsonRpcError { - code: -32004, - message: task_details.error.unwrap_or_else(|| "Script execution failed".to_string()), - data: None, - }), - id: client_rpc_id, - } - } - } - Err(rhai_err) => { - log::error!("Circle '{}' WS: RhaiClient operation failed for req ID {:?}: {}", _act.server_circle_name, client_rpc_id, rhai_err); - let (code, message) = match rhai_err { - RhaiClientError::Timeout(task_id) => (-32002, format!("Timeout: {}", task_id)), - RhaiClientError::RedisError(e) => (-32003, format!("Redis error: {}", e)), - RhaiClientError::SerializationError(e) => (-32003, format!("Serialization error: {}", e)), - RhaiClientError::TaskNotFound(task_id) => (-32005, format!("Task not found: {}", task_id)), - }; - JsonRpcResponse { - jsonrpc: "2.0".to_string(), - result: None, - id: client_rpc_id, - error: Some(JsonRpcError { code, message, data: None }), - } - } - }; - ws_ctx.text(serde_json::to_string(&response).unwrap()); - })); - } - Err(e) => { - log::error!("Circle '{}' WS: Invalid params for 'play' method: {}", self.server_circle_name, e); - let err_resp = JsonRpcResponse { - jsonrpc: "2.0".to_string(), result: None, id: client_rpc_id, - error: Some(JsonRpcError { code: -32602, message: "Invalid params".to_string(), data: Some(Value::String(e.to_string())) }), - }; - ctx.text(serde_json::to_string(&err_resp).unwrap()); - } - } - } else { - log::warn!("Circle '{}' WS: Method not found: {}", self.server_circle_name, req.method); - let err_resp = JsonRpcResponse { - jsonrpc: "2.0".to_string(), result: None, id: client_rpc_id, - error: Some(JsonRpcError { code: -32601, message: "Method not found".to_string(), data: None }), - }; - ctx.text(serde_json::to_string(&err_resp).unwrap()); - } - } - Err(e) => { - log::error!("Circle '{}' WS: Failed to parse JSON-RPC request: {}", self.server_circle_name, e); - let err_resp = JsonRpcResponse { - jsonrpc: "2.0".to_string(), result: None, id: Value::Null, - error: Some(JsonRpcError { code: -32700, message: "Parse error".to_string(), data: Some(Value::String(e.to_string())) }), - }; - ctx.text(serde_json::to_string(&err_resp).unwrap()); - } - } - } - Ok(ws::Message::Ping(msg)) => ctx.pong(&msg), - Ok(ws::Message::Pong(_)) => {}, - Ok(ws::Message::Binary(_bin)) => log::warn!("Circle '{}' WS: Binary messages not supported.", self.server_circle_name), - Ok(ws::Message::Close(reason)) => { - log::info!("Circle '{}' WS: Close message received. Reason: {:?}", self.server_circle_name, reason); - ctx.close(reason); - ctx.stop(); - } - Ok(ws::Message::Continuation(_)) => ctx.stop(), - Ok(ws::Message::Nop) => (), - Err(e) => { - log::error!("Circle '{}' WS: Error: {:?}", self.server_circle_name, e); - ctx.stop(); - } - } - } -} - -// Modified ws_handler to accept newtype wrapped app_data -async fn ws_handler_modified( - req: HttpRequest, - stream: web::Payload, - app_circle_name: web::Data, // Use wrapped type - app_redis_url: web::Data, // Use wrapped type -) -> Result { - let circle_name_str = app_circle_name.0.clone(); - let redis_url_str = app_redis_url.0.clone(); - - log::info!("WebSocket handshake attempt for server: '{}' with redis: '{}'", circle_name_str, redis_url_str); - let resp = ws::start( - CircleWs::new(circle_name_str, redis_url_str), // Pass unwrapped strings - &req, - stream - )?; - Ok(resp) -} - -// Public factory function to spawn the server -pub fn spawn_circle_ws_server( - _circle_id: u32, - circle_name: String, - port: u16, - redis_url: String, - // Sender to send the server handle back to the orchestrator - server_handle_tx: oneshot::Sender, -) -> JoinHandle> { - let circle_name_for_log = circle_name.clone(); - // redis_url_for_log is not used, but kept for consistency if needed later - - tokio::spawn(async move { - let circle_name_outer = circle_name; - let redis_url_outer = redis_url; - - let app_factory = move || { - App::new() - .app_data(web::Data::new(AppCircleName(circle_name_outer.clone()))) - .app_data(web::Data::new(AppRedisUrl(redis_url_outer.clone()))) - .route("/ws", web::get().to(ws_handler_modified)) - .default_service(web::route().to(|| async { HttpResponse::NotFound().body("404 Not Found") })) - }; - - let server_builder = HttpServer::new(app_factory); - - let bound_server = match server_builder.bind(("127.0.0.1", port)) { - Ok(srv) => { - log::info!( - "Successfully bound WebSocket server for Circle: '{}' on port {}. Starting...", - circle_name_for_log, port - ); - srv - } - Err(e) => { - log::error!( - "Failed to bind WebSocket server for Circle '{}' on port {}: {}", - circle_name_for_log, port, e - ); - // If binding fails, we can't send a server handle. - // The orchestrator will see the JoinHandle error out or the oneshot::Sender drop. - return Err(e); - } - }; - - let server_runnable: actix_web::dev::Server = bound_server.run(); - - // Send the server handle back to the orchestrator - if server_handle_tx.send(server_runnable.clone()).is_err() { - log::error!( - "Failed to send server handle back to orchestrator for Circle '{}'. Orchestrator might have shut down.", - circle_name_for_log - ); - // Server might still run, but orchestrator can't stop it gracefully via this handle. - // Consider stopping it here if sending the handle is critical. - // For now, let it proceed, but log the error. - } - - // Now await the server_runnable (which is the Server handle itself) - if let Err(e) = server_runnable.await { - log::error!("WebSocket server for Circle '{}' on port {} failed during run: {}", circle_name_for_log, port, e); - return Err(e); - } - log::info!("WebSocket server for Circle '{}' on port {} shut down gracefully.", circle_name_for_log, port); - Ok(()) - }) -} \ No newline at end of file diff --git a/server_ws/src/main.rs b/server_ws/src/main.rs deleted file mode 100644 index 9de61f2..0000000 --- a/server_ws/src/main.rs +++ /dev/null @@ -1,260 +0,0 @@ -use actix_web::{web, App, HttpRequest, HttpServer, HttpResponse, Error}; -use actix_web_actors::ws; -use actix::{Actor, ActorContext, StreamHandler, AsyncContext, WrapFuture, ActorFutureExt}; -// HashMap no longer needed -use clap::Parser; -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use std::time::Duration; -// AsyncCommands no longer directly used here -use rhai_client::RhaiClientError; // Import RhaiClientError for matching -// Uuid is not directly used here anymore for task_id generation, RhaiClient handles it. -// Utc no longer directly used here -// RhaiClientError is not directly handled here, errors from RhaiClient are strings or RhaiClient's own error type. -use rhai_client::RhaiClient; // ClientRhaiTaskDetails is used via rhai_client::RhaiTaskDetails - -const REDIS_URL: &str = "redis://127.0.0.1/"; // Make this configurable if needed - -// JSON-RPC 2.0 Structures -#[derive(Serialize, Deserialize, Debug, Clone)] // Added Clone -struct JsonRpcRequest { - jsonrpc: String, - method: String, - params: Value, - id: Option, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] // Added Clone -struct JsonRpcResponse { - jsonrpc: String, - result: Option, - error: Option, - id: Value, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] // Added Clone -struct JsonRpcError { - code: i32, - message: String, - data: Option, -} - -// Specific params and result for "play" method -#[derive(Serialize, Deserialize, Debug, Clone)] // Added Clone -struct PlayParams { - script: String, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] // Added Clone -struct PlayResult { - output: String, -} - -// Local RhaiTaskDetails struct is removed, will use ClientRhaiTaskDetails from rhai_client crate. -// Ensure field names used in polling logic (e.g. error_message) are updated if they differ. -// rhai_client::RhaiTaskDetails uses 'error' and 'client_rpc_id'. - -#[derive(Parser, Debug)] -#[clap(author, version, about, long_about = None)] -struct Args { - #[clap(short, long, value_parser, default_value_t = 8080)] - port: u16, - - #[clap(short, long, value_parser)] - circle_name: String, -} - -// WebSocket Actor -struct CircleWs { - server_circle_name: String, - // redis_client field removed as RhaiClient handles its own connection -} - -const TASK_TIMEOUT_DURATION: Duration = Duration::from_secs(30); // 30 seconds timeout -const TASK_POLL_INTERVAL_DURATION: Duration = Duration::from_millis(200); // 200 ms poll interval - -impl CircleWs { - fn new(name: String) -> Self { - Self { - server_circle_name: name, - } - } -} - -impl Actor for CircleWs { - type Context = ws::WebsocketContext; - - fn started(&mut self, _ctx: &mut Self::Context) { - log::info!("WebSocket session started for server dedicated to: {}", self.server_circle_name); - } - - fn stopping(&mut self, _ctx: &mut Self::Context) -> actix::Running { - log::info!("WebSocket session stopping for server dedicated to: {}", self.server_circle_name); - actix::Running::Stop - } -} - -// WebSocket message handler -impl StreamHandler> for CircleWs { - fn handle(&mut self, msg: Result, ctx: &mut Self::Context) { - match msg { - Ok(ws::Message::Text(text)) => { - log::info!("WS Text for {}: {}", self.server_circle_name, text); - match serde_json::from_str::(&text) { - Ok(req) => { - let client_rpc_id = req.id.clone().unwrap_or(Value::Null); - if req.method == "play" { - match serde_json::from_value::(req.params.clone()) { - Ok(play_params) => { - // Use RhaiClient to submit the script - let script_content = play_params.script; - let current_circle_name = self.server_circle_name.clone(); - let rpc_id_for_client = client_rpc_id.clone(); // client_rpc_id is already Value - - let fut = async move { - match RhaiClient::new(REDIS_URL) { - Ok(rhai_task_client) => { - rhai_task_client.submit_script_and_await_result( - ¤t_circle_name, - script_content, - Some(rpc_id_for_client.clone()), - TASK_TIMEOUT_DURATION, - TASK_POLL_INTERVAL_DURATION, - ).await // This returns Result - } - Err(e) => { - log::error!("Failed to create RhaiClient: {}", e); - Err(e) // Convert the error from RhaiClient::new into the type expected by the map function's error path. - } - } - }; - - ctx.spawn(fut.into_actor(self).map(move |result, _act, ws_ctx| { - let response = match result { - Ok(task_details) => { // ClientRhaiTaskDetails - if task_details.status == "completed" { - log::info!("Task completed successfully. Client RPC ID: {:?}, Output: {:?}", task_details.client_rpc_id, task_details.output); - JsonRpcResponse { - jsonrpc: "2.0".to_string(), - result: Some(serde_json::to_value(PlayResult { - output: task_details.output.unwrap_or_default() - }).unwrap()), - error: None, - id: client_rpc_id, // Use the original client_rpc_id from the request - } - } else { // status == "error" - log::warn!("Task execution failed. Client RPC ID: {:?}, Error: {:?}", task_details.client_rpc_id, task_details.error); - JsonRpcResponse { - jsonrpc: "2.0".to_string(), - result: None, - error: Some(JsonRpcError { - code: -32004, // Script execution error - message: task_details.error.unwrap_or_else(|| "Script execution failed with no specific error message".to_string()), - data: None, - }), - id: client_rpc_id, - } - } - } - Err(rhai_err) => { // RhaiClientError - log::error!("RhaiClient operation failed: {}", rhai_err); - let (code, message) = match rhai_err { - RhaiClientError::Timeout(task_id) => (-32002, format!("Timeout waiting for task {} to complete", task_id)), - RhaiClientError::RedisError(e) => (-32003, format!("Redis communication error: {}", e)), - RhaiClientError::SerializationError(e) => (-32003, format!("Serialization error: {}", e)), - RhaiClientError::TaskNotFound(task_id) => (-32005, format!("Task {} not found after submission", task_id)), - }; - JsonRpcResponse { - jsonrpc: "2.0".to_string(), - result: None, - id: client_rpc_id, - error: Some(JsonRpcError { code, message, data: None }), - } - } - }; - ws_ctx.text(serde_json::to_string(&response).unwrap()); - })); - } - Err(e) => { // Invalid params for 'play' - log::error!("Invalid params for 'play' method: {}", e); - let err_resp = JsonRpcResponse { - jsonrpc: "2.0".to_string(), result: None, id: client_rpc_id, - error: Some(JsonRpcError { code: -32602, message: "Invalid params".to_string(), data: Some(Value::String(e.to_string())) }), - }; - ctx.text(serde_json::to_string(&err_resp).unwrap()); - } - } - } else { // Method not found - log::warn!("Method not found: {}", req.method); - let err_resp = JsonRpcResponse { - jsonrpc: "2.0".to_string(), result: None, id: client_rpc_id, - error: Some(JsonRpcError { code: -32601, message: "Method not found".to_string(), data: None }), - }; - ctx.text(serde_json::to_string(&err_resp).unwrap()); - } - } - Err(e) => { // Parse error - log::error!("Failed to parse JSON-RPC request: {}", e); - let err_resp = JsonRpcResponse { - jsonrpc: "2.0".to_string(), result: None, id: Value::Null, // No ID if request couldn't be parsed - error: Some(JsonRpcError { code: -32700, message: "Parse error".to_string(), data: Some(Value::String(e.to_string())) }), - }; - ctx.text(serde_json::to_string(&err_resp).unwrap()); - } - } - } - Ok(ws::Message::Ping(msg)) => ctx.pong(&msg), - Ok(ws::Message::Pong(_)) => {}, - Ok(ws::Message::Binary(_bin)) => log::warn!("Binary messages not supported."), - Ok(ws::Message::Close(reason)) => { - ctx.close(reason); - ctx.stop(); - } - Ok(ws::Message::Continuation(_)) => ctx.stop(), - Ok(ws::Message::Nop) => (), - Err(e) => { - log::error!("WS Error: {:?}", e); - ctx.stop(); - } - } - } -} - -// WebSocket handshake and actor start -async fn ws_handler( - req: HttpRequest, - stream: web::Payload, - server_name: web::Data, - // redis_client: web::Data, // No longer passed to CircleWs actor directly -) -> Result { - log::info!("WebSocket handshake attempt for server: {}", server_name.get_ref()); - let resp = ws::start( - CircleWs::new(server_name.get_ref().clone()), // Pass only the server name - &req, - stream - )?; - Ok(resp) -} - -#[actix_web::main] -async fn main() -> std::io::Result<()> { - let args = Args::parse(); - - std::env::set_var("RUST_LOG", "info,circle_server_ws=debug"); - env_logger::init(); - - log::info!( - "Starting WebSocket server for Circle: '{}' on port {}...", - args.circle_name, args.port - ); - - HttpServer::new(move || { - App::new() - .app_data(web::Data::new(args.circle_name.clone())) - .route("/ws", web::get().to(ws_handler)) - .default_service(web::route().to(|| async { HttpResponse::NotFound().body("404 Not Found - This is a WebSocket-only server for a specific circle.") })) - }) - .bind(("127.0.0.1", args.port))? - .run() - .await -} diff --git a/server_ws/tests/timeout_integration_test.rs b/server_ws/tests/timeout_integration_test.rs deleted file mode 100644 index e3d3ec6..0000000 --- a/server_ws/tests/timeout_integration_test.rs +++ /dev/null @@ -1,135 +0,0 @@ -use tokio::time::Duration; // Removed unused sleep -use futures_util::{sink::SinkExt, stream::StreamExt}; -use tokio_tungstenite::{connect_async, tungstenite::protocol::Message}; -use serde_json::Value; // Removed unused json macro import -use std::process::Command; -use std::thread; -use std::sync::Once; - -// Define a simple JSON-RPC request structure for sending scripts -#[derive(serde::Serialize, Debug)] -struct JsonRpcRequest { - jsonrpc: String, - method: String, - params: ScriptParams, - id: u64, -} - -#[derive(serde::Serialize, Debug)] -struct ScriptParams { - script: String, -} - -// Define a simple JSON-RPC error response structure for assertion -#[derive(serde::Deserialize, Debug)] -struct JsonRpcErrorResponse { - _jsonrpc: String, // Field is present in response, but not used in assert - error: JsonRpcErrorDetails, - _id: Option, // Field is present in response, but not used in assert -} - -#[derive(serde::Deserialize, Debug)] -struct JsonRpcErrorDetails { - code: i32, - message: String, -} - -const SERVER_ADDRESS: &str = "ws://127.0.0.1:8088/ws"; // Match port in main.rs or make configurable -const TEST_CIRCLE_NAME: &str = "test_timeout_circle"; -const SERVER_STARTUP_TIME: Duration = Duration::from_secs(5); // Time to wait for server to start -const RHAI_TIMEOUT_SECONDS: u64 = 30; // Should match TASK_TIMEOUT_DURATION in circle_server_ws - -static START_SERVER: Once = Once::new(); - -fn ensure_server_is_running() { - START_SERVER.call_once(|| { - println!("Attempting to start circle_server_ws for integration tests..."); - // The server executable will be in target/debug relative to the crate root - let server_executable = "target/debug/circle_server_ws"; - - thread::spawn(move || { - let mut child = Command::new(server_executable) - .arg("--port=8088") // Use a specific port for testing - .arg(format!("--circle-name={}", TEST_CIRCLE_NAME)) - .spawn() - .expect("Failed to start circle_server_ws. Make sure it's compiled (cargo build)."); - - let status = child.wait().expect("Failed to wait on server process."); - println!("Server process exited with status: {}", status); - }); - println!("Server start command issued. Waiting for {}s...", SERVER_STARTUP_TIME.as_secs()); - thread::sleep(SERVER_STARTUP_TIME); - println!("Presumed server started."); - }); -} - -#[tokio::test] -async fn test_rhai_script_timeout() { - ensure_server_is_running(); - - println!("Connecting to WebSocket server: {}", SERVER_ADDRESS); - let (mut ws_stream, _response) = connect_async(SERVER_ADDRESS) - .await - .expect("Failed to connect to WebSocket server"); - println!("Connected to WebSocket server."); - - // Rhai script designed to run longer than RHAI_TIMEOUT_SECONDS - // A large loop should cause a timeout. - let long_running_script = format!(" - let mut x = 0; - for i in 0..999999999 {{ - x = x + i; - if i % 10000000 == 0 {{ - // debug(\"Looping: \" + i); // Optional: for server-side logging if enabled - }} - }} - print(x); // This line will likely not be reached due to timeout - "); - - let request = JsonRpcRequest { - jsonrpc: "2.0".to_string(), - method: "execute_script".to_string(), - params: ScriptParams { script: long_running_script }, - id: 1, - }; - - let request_json = serde_json::to_string(&request).expect("Failed to serialize request"); - println!("Sending long-running script request: {}", request_json); - ws_stream.send(Message::Text(request_json)).await.expect("Failed to send message"); - - println!("Waiting for response (expecting timeout after ~{}s)..", RHAI_TIMEOUT_SECONDS); - - // Wait for a response, expecting a timeout error - // The server's timeout is RHAI_TIMEOUT_SECONDS, client should wait a bit longer. - match tokio::time::timeout(Duration::from_secs(RHAI_TIMEOUT_SECONDS + 15), ws_stream.next()).await { - Ok(Some(Ok(Message::Text(text)))) => { - println!("Received response: {}", text); - let response: Result = serde_json::from_str(&text); - match response { - Ok(err_resp) => { - assert_eq!(err_resp.error.code, -32002, "Error code should indicate timeout."); - assert!(err_resp.error.message.contains("timed out"), "Error message should indicate timeout."); - println!("Timeout test passed! Received correct timeout error."); - } - Err(e) => { - panic!("Failed to deserialize error response: {}. Raw: {}", e, text); - } - } - } - Ok(Some(Ok(other_msg))) => { - panic!("Received unexpected message type: {:?}", other_msg); - } - Ok(Some(Err(e))) => { - panic!("WebSocket error: {}", e); - } - Ok(None) => { - panic!("WebSocket stream closed unexpectedly."); - } - Err(_) => { - panic!("Test timed out waiting for server response. Server might not have sent timeout error or took too long."); - } - } - - ws_stream.close(None).await.ok(); - println!("Test finished, WebSocket closed."); -} diff --git a/src/app/.cargo/config.toml b/src/app/.cargo/config.toml new file mode 100644 index 0000000..a57f6be --- /dev/null +++ b/src/app/.cargo/config.toml @@ -0,0 +1,5 @@ +# This configuration is picked up by Cargo when building the `circles-app` crate. +# It sets the required RUSTFLAGS to enable the JavaScript backend for the `getrandom` crate, +# which is necessary for WebAssembly compilation. +[target.wasm32-unknown-unknown] +rustflags = ['--cfg', 'getrandom_backend="wasm_js"'] diff --git a/src/app/.gitignore b/src/app/.gitignore new file mode 100644 index 0000000..adb443d --- /dev/null +++ b/src/app/.gitignore @@ -0,0 +1,2 @@ +/dist/ +/target/ diff --git a/src/app/Cargo.lock b/src/app/Cargo.lock new file mode 100644 index 0000000..c9266dc --- /dev/null +++ b/src/app/Cargo.lock @@ -0,0 +1,1387 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anymap2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "boolinator" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfa8873f51c92e232f9bac4065cddef41b714152812bfc5f7672ba16d6ef8cd9" + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cc" +version = "1.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16595d3be041c03b09d08d0858631facccee9221e579704070e6e9e4915d3bc7" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "circles-app" +version = "0.1.0" +dependencies = [ + "chrono", + "gloo-net 0.4.0", + "gloo-timers 0.3.0", + "log", + "serde", + "serde_json", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-logger", + "web-sys", + "yew", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "gloo" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28999cda5ef6916ffd33fb4a7b87e1de633c47c0dc6d97905fee1cdaa142b94d" +dependencies = [ + "gloo-console 0.2.3", + "gloo-dialogs 0.1.1", + "gloo-events 0.1.2", + "gloo-file 0.2.3", + "gloo-history 0.1.5", + "gloo-net 0.3.1", + "gloo-render 0.1.1", + "gloo-storage 0.2.2", + "gloo-timers 0.2.6", + "gloo-utils 0.1.7", + "gloo-worker 0.2.1", +] + +[[package]] +name = "gloo" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd35526c28cc55c1db77aed6296de58677dbab863b118483a27845631d870249" +dependencies = [ + "gloo-console 0.3.0", + "gloo-dialogs 0.2.0", + "gloo-events 0.2.0", + "gloo-file 0.3.0", + "gloo-history 0.2.2", + "gloo-net 0.4.0", + "gloo-render 0.2.0", + "gloo-storage 0.3.0", + "gloo-timers 0.3.0", + "gloo-utils 0.2.0", + "gloo-worker 0.4.0", +] + +[[package]] +name = "gloo-console" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b7ce3c05debe147233596904981848862b068862e9ec3e34be446077190d3f" +dependencies = [ + "gloo-utils 0.1.7", + "js-sys", + "serde", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-console" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a17868f56b4a24f677b17c8cb69958385102fa879418052d60b50bc1727e261" +dependencies = [ + "gloo-utils 0.2.0", + "js-sys", + "serde", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-dialogs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67062364ac72d27f08445a46cab428188e2e224ec9e37efdba48ae8c289002e6" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-dialogs" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4748e10122b01435750ff530095b1217cf6546173459448b83913ebe7815df" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-events" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b107f8abed8105e4182de63845afcc7b69c098b7852a813ea7462a320992fc" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-events" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c26fb45f7c385ba980f5fa87ac677e363949e065a083722697ef1b2cc91e41" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-file" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d5564e570a38b43d78bdc063374a0c3098c4f0d64005b12f9bbe87e869b6d7" +dependencies = [ + "gloo-events 0.1.2", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-file" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97563d71863fb2824b2e974e754a81d19c4a7ec47b09ced8a0e6656b6d54bd1f" +dependencies = [ + "gloo-events 0.2.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-history" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85725d90bf0ed47063b3930ef28e863658a7905989e9929a8708aab74a1d5e7f" +dependencies = [ + "gloo-events 0.1.2", + "gloo-utils 0.1.7", + "serde", + "serde-wasm-bindgen 0.5.0", + "serde_urlencoded", + "thiserror", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-history" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "903f432be5ba34427eac5e16048ef65604a82061fe93789f2212afc73d8617d6" +dependencies = [ + "getrandom", + "gloo-events 0.2.0", + "gloo-utils 0.2.0", + "serde", + "serde-wasm-bindgen 0.6.5", + "serde_urlencoded", + "thiserror", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-net" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a66b4e3c7d9ed8d315fd6b97c8b1f74a7c6ecbbc2320e65ae7ed38b7068cc620" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils 0.1.7", + "http", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-net" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ac9e8288ae2c632fa9f8657ac70bfe38a1530f345282d7ba66a1f70b72b7dc4" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils 0.2.0", + "http", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-render" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd9306aef67cfd4449823aadcd14e3958e0800aa2183955a309112a84ec7764" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-render" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56008b6744713a8e8d98ac3dcb7d06543d5662358c9c805b4ce2167ad4649833" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-storage" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6ab60bf5dbfd6f0ed1f7843da31b41010515c745735c970e821945ca91e480" +dependencies = [ + "gloo-utils 0.1.7", + "js-sys", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-storage" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc8031e8c92758af912f9bc08fbbadd3c6f3cfcbf6b64cdf3d6a81f0139277a" +dependencies = [ + "gloo-utils 0.2.0", + "js-sys", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gloo-utils" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-utils" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-worker" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13471584da78061a28306d1359dd0178d8d6fc1c7c80e5e35d27260346e0516a" +dependencies = [ + "anymap2", + "bincode", + "gloo-console 0.2.3", + "gloo-utils 0.1.7", + "js-sys", + "serde", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-worker" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76495d3dd87de51da268fa3a593da118ab43eb7f8809e17eb38d3319b424e400" +dependencies = [ + "bincode", + "futures", + "gloo-utils 0.2.0", + "gloo-worker-macros", + "js-sys", + "pinned", + "serde", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-worker-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956caa58d4857bc9941749d55e4bd3000032d8212762586fa5705632967140e7" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "implicit-clone" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8a9aa791c7b5a71b636b7a68207fdebf171ddfc593d9c8506ec4cbc527b6a84" +dependencies = [ + "implicit-clone-derive", + "indexmap", +] + +[[package]] +name = "implicit-clone-derive" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "699c1b6d335e63d0ba5c1e1c7f647371ce989c3bcbe1f7ed2b85fa56e3bd1a21" +dependencies = [ + "quote", + "syn 2.0.100", +] + +[[package]] +name = "indexmap" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" + +[[package]] +name = "log" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "miniz_oxide" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +dependencies = [ + "adler2", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pinned" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a829027bd95e54cfe13e3e258a1ae7b645960553fb82b75ff852c29688ee595b" +dependencies = [ + "futures", + "rustversion", + "thiserror", +] + +[[package]] +name = "prettyplease" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5316f57387668042f561aae71480de936257848f9c43ce528e311d89a07cadeb" +dependencies = [ + "proc-macro2", + "syn 2.0.100", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prokio" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b55e106e5791fa5a13abd13c85d6127312e8e09098059ca2bc9b03ca4cf488" +dependencies = [ + "futures", + "gloo 0.8.1", + "num_cpus", + "once_cell", + "pin-project", + "pinned", + "tokio", + "tokio-stream", + "wasm-bindgen-futures", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "tokio" +version = "1.44.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a" +dependencies = [ + "backtrace", + "pin-project-lite", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.100", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-logger" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "074649a66bb306c8f2068c9016395fa65d8e08d2affcbf95acf3c24c3ab19718" +dependencies = [ + "log", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "yew" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f1a03f255c70c7aa3e9c62e15292f142ede0564123543c1cc0c7a4f31660cac" +dependencies = [ + "console_error_panic_hook", + "futures", + "gloo 0.10.0", + "implicit-clone", + "indexmap", + "js-sys", + "prokio", + "rustversion", + "serde", + "slab", + "thiserror", + "tokio", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "yew-macro", +] + +[[package]] +name = "yew-macro" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02fd8ca5166d69e59f796500a2ce432ff751edecbbb308ca59fd3fe4d0343de2" +dependencies = [ + "boolinator", + "once_cell", + "prettyplease", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.100", +] diff --git a/src/app/Cargo.toml b/src/app/Cargo.toml new file mode 100644 index 0000000..954a488 --- /dev/null +++ b/src/app/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "circles-app" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +heromodels = { path = "/Users/timurgordon/code/git.ourworld.tf/herocode/db/heromodels" } +circle_client_ws = { path = "../client_ws" } + +futures = "0.3" +yew-router = "0.18" +yew = { version = "0.21", features = ["csr"] } +wasm-bindgen = "0.2" +log = "0.4" +wasm-logger = "0.2" +serde = { version = "1.0", features = ["derive", "rc"] } +serde_json = "1.0" +web-sys = { version = "0.3", features = ["MouseEvent", "Element", "HtmlElement", "SvgElement", "Window", "Document", "CssStyleDeclaration"] } +gloo-timers = "0.3.0" +chrono = { version = "0.4", features = ["serde"] } +gloo-net = "0.4" +wasm-bindgen-futures = "0.4" +gloo-console = "0.3" # For console logging +futures-util = { version = "0.3", default-features = false, features = ["sink", "std"] } # For StreamExt +futures-channel = "0.3" # For MPSC channels +rand = "0.8" # For random traffic simulation +common_models = { path = "/Users/timurgordon/code/playground/yew/common_models" } +engine = { path = "/Users/timurgordon/code/git.ourworld.tf/herocode/rhailib/src/engine" } +rhai = "1.17" +js-sys = "0.3" +getrandom = { version = "0.3", features = ["wasm_js"] } + +# Authentication dependencies +secp256k1 = { workspace = true, features = ["rand", "recovery", "hashes"] } +hex = "0.4" +sha3 = "0.10" +gloo-storage = "0.3" +urlencoding = "2.1" +thiserror = "1.0" + +[dev-dependencies] +wasm-bindgen-test = "0.3" diff --git a/src/app/LICENSE-APACHE b/src/app/LICENSE-APACHE new file mode 100644 index 0000000..08017da --- /dev/null +++ b/src/app/LICENSE-APACHE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/src/app/LICENSE-MIT b/src/app/LICENSE-MIT new file mode 100644 index 0000000..8a3942f --- /dev/null +++ b/src/app/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) timurgordon + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/src/app/README.md b/src/app/README.md new file mode 100644 index 0000000..777c71d --- /dev/null +++ b/src/app/README.md @@ -0,0 +1,75 @@ +# Yew Trunk Template + +This is a fairly minimal template for a Yew app that's built with [Trunk]. + +## Usage + +For a more thorough explanation of Trunk and its features, please head over to the [repository][trunk]. + +### Installation + +If you don't already have it installed, it's time to install Rust: . +The rest of this guide assumes a typical Rust installation which contains both `rustup` and Cargo. + +To compile Rust to WASM, we need to have the `wasm32-unknown-unknown` target installed. +If you don't already have it, install it with the following command: + +```bash +rustup target add wasm32-unknown-unknown +``` + +Now that we have our basics covered, it's time to install the star of the show: [Trunk]. +Simply run the following command to install it: + +```bash +cargo install trunk wasm-bindgen-cli +``` + +That's it, we're done! + +### Running + +```bash +trunk serve +``` + +Rebuilds the app whenever a change is detected and runs a local server to host it. + +There's also the `trunk watch` command which does the same thing but without hosting it. + +### Release + +```bash +trunk build --release +``` + +This builds the app in release mode similar to `cargo build --release`. +You can also pass the `--release` flag to `trunk serve` if you need to get every last drop of performance. + +Unless overwritten, the output will be located in the `dist` directory. + +## Using this template + +There are a few things you have to adjust when adopting this template. + +### Remove example code + +The code in [src/main.rs](src/main.rs) specific to the example is limited to only the `view` method. +There is, however, a fair bit of Sass in [index.scss](index.scss) you can remove. + +### Update metadata + +Update the `version`, `description` and `repository` fields in the [Cargo.toml](Cargo.toml) file. +The [index.html](index.html) file also contains a `` tag that needs updating. + + +Finally, you should update this very `README` file to be about your app. + +### License + +The template ships with both the Apache and MIT license. +If you don't want to have your app dual licensed, just remove one (or both) of the files and update the `license` field in `Cargo.toml`. + +There are two empty spaces in the MIT license you need to fill out: `` and `timurgordon <timurgordon@gmail.com>`. + +[trunk]: https://github.com/thedodd/trunk \ No newline at end of file diff --git a/src/app/Trunk.toml b/src/app/Trunk.toml new file mode 100644 index 0000000..8d8f852 --- /dev/null +++ b/src/app/Trunk.toml @@ -0,0 +1,2 @@ +[build] +target = "index.html" diff --git a/src/app/auth_system_plan.md b/src/app/auth_system_plan.md new file mode 100644 index 0000000..4419f21 --- /dev/null +++ b/src/app/auth_system_plan.md @@ -0,0 +1,215 @@ +# Authentication System Architecture Plan (Clean Separation) + +## Overview +A comprehensive authentication system for a standalone WASM Yew application that uses the `client_ws` library for WebSocket authentication with secp256k1 cryptographic signatures. The system maintains clear separation between generic WebSocket/crypto functionality and app-specific user management. + +## 🏗️ Clean System Architecture + +```mermaid +graph TB + subgraph "Demo App (Application Layer)" + A[Login Component] --> B[Auth Manager] + B --> C[Email Store - Hardcoded Mapping] + B --> D[App-Specific Auth State] + C --> E[Email-to-Private-Key Lookup] + end + + subgraph "client_ws Library (Generic Layer)" + F[CircleWsClient] --> G[Crypto Utils] + F --> H[Nonce Client] + G --> I[secp256k1 Signing] + H --> J[REST Nonce Requests] + end + + subgraph "External Services" + K[WebSocket Server] --> L[Auth Middleware] + M[Auth Server] --> N[Nonce Endpoint] + L --> O[Signature Verification] + end + + B --> F + F --> K + H --> M + + subgraph "Authentication Flow" + P[1. Email/Private Key Input] --> Q[2. App Layer Lookup] + Q --> R[3. Create Authenticated Client] + R --> S[4. Client Fetches Nonce] + S --> T[5. Client Signs Nonce] + T --> U[6. WebSocket Connection] + U --> V[7. Server Verification] + end +``` + +## 📁 Clean File Structure + +``` +app/src/auth/ +├── mod.rs # App auth module exports + client_ws re-exports +├── auth_manager.rs # App-specific auth coordination +├── email_store.rs # Hardcoded email-to-key mappings (app-specific) +└── types.rs # App-specific auth types + client_ws re-exports + +client_ws/src/auth/ +├── mod.rs # Generic auth module +├── crypto_utils.rs # secp256k1 operations (generic) +├── nonce_client.rs # REST nonce client (generic) +└── types.rs # Core auth types (generic) + +client_ws/src/ +└── lib.rs # WebSocket client with auth support +``` + +## 🔐 Clean Authentication Flow + +### 1. Email Authentication (App-Specific) +1. **User enters email** in app app login component +2. **App looks up email** in hardcoded email_store.rs +3. **App retrieves private key** from hardcoded mapping +4. **App creates CircleWsClient** with private key +5. **Client library handles** nonce fetching, signing, WebSocket connection + +### 2. Private Key Authentication (Generic) +1. **User enters private key** directly +2. **App creates CircleWsClient** with private key +3. **Client library handles** nonce fetching, signing, WebSocket connection + +## 🛠️ Key Separation Principles + +### App Layer Responsibilities (app/src/auth/) +- ✅ **Email-to-private-key mappings** (email_store.rs) +- ✅ **User interface logic** (login components) +- ✅ **App-specific auth state** (AuthState, AuthMethod with Email) +- ✅ **Session management** (local storage, UI state) +- ✅ **Business logic** (user management, app data) + +### Client Library Responsibilities (client_ws/) +- ✅ **WebSocket connection management** +- ✅ **Cryptographic operations** (secp256k1 signing/verification) +- ✅ **Nonce fetching** from REST endpoints +- ✅ **Private key authentication** (generic) +- ✅ **Cross-platform support** (WASM + Native) + +### What Was Removed (Duplicated Code) +- ❌ **crypto_utils.rs** from app (use client_ws instead) +- ❌ **nonce_client.rs** from app (use client_ws instead) +- ❌ **Duplicated auth types** (use client_ws types) +- ❌ **Email authentication** from client_ws (app-specific) + +## 📋 Implementation Status + +### ✅ Completed +1. **Cleaned client_ws library** + - Removed app-specific email authentication + - Kept only private key authentication + - Updated documentation with clear separation + +2. **Updated app app** + - Removed duplicated crypto_utils.rs + - Removed duplicated nonce_client.rs + - Updated auth_manager.rs to use client_ws + - Updated types.rs to re-export client_ws types + - Kept app-specific email_store.rs + +3. **Clear integration pattern** + - App handles email-to-key lookup + - App creates CircleWsClient with private key + - Client library handles all WebSocket/crypto operations + +## 🔧 Usage Examples + +### App-Level Authentication +```rust +// In app app auth_manager.rs +impl AuthManager { + pub async fn authenticate_with_email(&self, email: String) -> AuthResult<()> { + // 1. App-specific: Look up email in hardcoded store + let key_pair = get_key_pair_for_email(&email)?; + + // 2. Generic: Validate using client_ws + validate_private_key(&key_pair.private_key)?; + + // 3. App-specific: Update app auth state + self.set_state(AuthState::Authenticated { + public_key: key_pair.public_key, + private_key: key_pair.private_key, + method: AuthMethod::Email(email), + }); + + Ok(()) + } + + pub async fn create_authenticated_client(&self, ws_url: &str, auth_server_url: &str) -> Result<CircleWsClient, CircleWsClientError> { + // 1. App-specific: Get private key from app state + let private_key = match self.state.borrow().clone() { + AuthState::Authenticated { private_key, .. } => private_key, + _ => return Err(CircleWsClientError::NotConnected), + }; + + // 2. Generic: Create and authenticate client using client_ws + let mut client = CircleWsClient::new_with_auth( + ws_url.to_string(), + auth_server_url.to_string(), + private_key + ); + + client.authenticate().await?; + Ok(client) + } +} +``` + +### Client Library Usage +```rust +// Using client_ws directly (no app-specific logic) +use circle_client_ws::CircleWsClient; + +let mut client = CircleWsClient::new_with_auth( + "ws://localhost:8080/ws".to_string(), + "http://localhost:8080".to_string(), + private_key +); + +client.authenticate().await?; +client.connect().await?; + +let result = client.play("console.log('Hello, authenticated world!');".to_string()).await?; +``` + +## 🎯 Benefits of Clean Separation + +1. **Reusability**: client_ws can be used by any Rust application +2. **Maintainability**: Clear boundaries between WebSocket/crypto and user management +3. **Testability**: Each layer can be tested independently +4. **Security**: Consistent crypto handling at the library level +5. **Flexibility**: Apps can implement any authentication UX they need + +## 🔒 Security Considerations + +### Client Library (Generic) +- ✅ **Secure crypto operations** using secp256k1 +- ✅ **Proper nonce handling** with expiration +- ✅ **Ethereum-compatible signing** (eth_sign style) +- ✅ **Cross-platform security** (WASM + Native) + +### Demo App (App-Specific) +- ✅ **Hardcoded keys for app** (easy to rotate) +- ✅ **No sensitive server storage** needed +- ✅ **Local storage** (non-sensitive state only) +- ✅ **Clear separation** of concerns + +## 🚀 Migration Benefits + +### Before (Mixed Concerns) +- Duplicated crypto code in both client_ws and app +- Email authentication mixed into generic library +- Hard to reuse client_ws in other projects +- Unclear separation of responsibilities + +### After (Clean Separation) +- Single source of truth for crypto operations (client_ws) +- App-specific logic clearly separated (app/auth/) +- client_ws is reusable by any application +- Clear integration patterns and documentation + +This clean architecture ensures that the `client_ws` library remains focused on its core responsibility (secure WebSocket client with private key authentication) while the app app handles all user-facing and business logic appropriately. \ No newline at end of file diff --git a/src/app/csslint.sh b/src/app/csslint.sh new file mode 100644 index 0000000..01038be --- /dev/null +++ b/src/app/csslint.sh @@ -0,0 +1,91 @@ +#!/bin/bash + +# Parse arguments +CLEAN=false +ARGS=() + +for arg in "$@"; do + if [[ "$arg" == "--clean" ]]; then + CLEAN=true + else + ARGS+=("$arg") + fi +done + +CSS_DIR="${ARGS[0]:-static}" +PROJECT_DIR="${ARGS[1]:-.}" + +echo "🔍 Scanning CSS directory: $CSS_DIR" +echo "📁 Project source directory: $PROJECT_DIR" +echo "🧹 Clean mode: $CLEAN" + +USED_CLASSES=$(mktemp) +CLASS_NAMES=$(mktemp) + +# Step 1: collect all class names used in Rust/Yew +grep -rho --include="*.rs" 'class\s*=\s*["'"'"'][^"'"'"']*["'"'"']' "$PROJECT_DIR" \ + | grep -o '[a-zA-Z0-9_-]\+' \ + | sort -u > "$USED_CLASSES" + +# Step 2: extract class selectors from CSS +grep -rho '^\s*\.[a-zA-Z_-][a-zA-Z0-9_-]*\s*{' "$CSS_DIR" \ + | sed -E 's/^\s*//; s/\s*\{.*$//' \ + | sort -u > "$CLASS_NAMES" + +# Step 3: clean or list unused classes +find "$CSS_DIR" -type f -name "*.css" | while read -r css_file; do + if $CLEAN; then + TMP_CLEANED=$(mktemp) + awk -v used_classes="$USED_CLASSES" ' + BEGIN { + while ((getline line < used_classes) > 0) { + used[line] = 1 + } + in_block = 0 + brace_depth = 0 + current_class = "" + } + { + # Start of a class rule + if (!in_block && $0 ~ /^[ \t]*\.[a-zA-Z_-][a-zA-Z0-9_-]*[ \t]*\{/) { + line = $0 + gsub(/^[ \t]*\./, "", line) + sub(/[ \t]*\{.*/, "", line) + current_class = line + + if (!(current_class in used)) { + in_block = 1 + brace_depth = gsub(/\{/, "{") - gsub(/\}/, "}") + next + } + } else if (in_block) { + brace_depth += gsub(/\{/, "{") + brace_depth -= gsub(/\}/, "}") + if (brace_depth <= 0) { + in_block = 0 + } + next + } + + print $0 + } + ' "$css_file" > "$TMP_CLEANED" + + mv "$TMP_CLEANED" "$css_file" + echo "✅ Cleaned: $css_file" + else + while read -r class_selector; do + class_name=$(echo "$class_selector" | sed 's/^\.//') + if ! grep -qx "$class_name" "$USED_CLASSES"; then + echo "⚠️ Unused CSS class: $class_selector" + fi + done < "$CLASS_NAMES" + break + fi +done + +rm "$USED_CLASSES" "$CLASS_NAMES" + +if $CLEAN; then + echo "🧹 Done: multi-line unused class blocks removed safely." +fi \ No newline at end of file diff --git a/src/app/index.html b/src/app/index.html new file mode 100644 index 0000000..972b827 --- /dev/null +++ b/src/app/index.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8" /> + <title>Circles + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/app/index.scss b/src/app/index.scss new file mode 100644 index 0000000..710545f --- /dev/null +++ b/src/app/index.scss @@ -0,0 +1,35 @@ +html, +body { + height: 100%; + margin: 0; +} + +body { + align-items: center; + display: flex; + justify-content: center; + + background: linear-gradient(to bottom right, #444444, #009a5b); + font-size: 1.5rem; +} + +main { + color: #fff6d5; + font-family: sans-serif; + text-align: center; +} + +.logo { + height: 20em; +} + +.heart:after { + content: "❤️"; + + font-size: 1.75em; +} + +h1 + .subtitle { + display: block; + margin-top: -1em; +} diff --git a/src/app/src/app.rs b/src/app/src/app.rs new file mode 100644 index 0000000..39a6357 --- /dev/null +++ b/src/app/src/app.rs @@ -0,0 +1,250 @@ +use yew::prelude::*; +use std::rc::Rc; +use std::collections::HashMap; + +use crate::components::circles_view::CirclesView; +use crate::components::nav_island::NavIsland; +use crate::components::library_view::LibraryView; +use crate::components::intelligence_view::IntelligenceView; +use crate::components::inspector_view::InspectorView; +use crate::components::publishing_view::PublishingView; +use crate::components::customize_view::CustomizeViewComponent; +use crate::components::login_component::LoginComponent; +use crate::auth::{AuthManager, AuthState}; +use crate::components::auth_view::AuthView; + +// Props for the App component +#[derive(Properties, PartialEq, Clone)] +pub struct AppProps { + pub start_circle_ws_url: String, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum AppView { + Login, + Circles, + Library, + Intelligence, + Publishing, + Customize, + Inspector, // Added Inspector +} + +#[derive(Clone, Debug)] +pub enum Msg { + SwitchView(AppView), + UpdateCirclesContext(Vec), // Context URLs from CirclesView + AuthStateChanged(AuthState), + AuthenticationSuccessful, + AuthenticationFailed(String), + Logout, +} + +pub struct App { + current_view: AppView, + active_context_urls: Vec, // Only context URLs from CirclesView + start_circle_ws_url: String, // Initial WebSocket URL for CirclesView + auth_manager: AuthManager, + auth_state: AuthState, +} + +impl Component for App { + type Message = Msg; + type Properties = AppProps; + + fn create(ctx: &Context) -> Self { + wasm_logger::init(wasm_logger::Config::default()); + log::info!("App created with authentication support."); + + let start_circle_ws_url = ctx.props().start_circle_ws_url.clone(); + let auth_manager = AuthManager::new(); + let auth_state = auth_manager.get_state(); + + // Set up auth state change callback + let link = ctx.link().clone(); + auth_manager.set_on_state_change(link.callback(Msg::AuthStateChanged)); + + // Determine initial view based on authentication state + let initial_view = match auth_state { + AuthState::Authenticated { .. } => AppView::Circles, + _ => AppView::Login, + }; + + Self { + current_view: initial_view, + active_context_urls: Vec::new(), + start_circle_ws_url, + auth_manager, + auth_state, + } + } + + fn update(&mut self, _ctx: &Context, msg: Self::Message) -> bool { + match msg { + Msg::UpdateCirclesContext(context_urls) => { + log::info!("App: Received context update from CirclesView: {:?}", context_urls); + self.active_context_urls = context_urls; + true + } + Msg::SwitchView(view) => { + // Check if authentication is required for certain views + match view { + AppView::Login => { + self.current_view = view; + true + } + _ => { + if self.auth_manager.is_authenticated() { + self.current_view = view; + true + } else { + log::warn!("Attempted to access {} view without authentication", format!("{:?}", view)); + self.current_view = AppView::Login; + true + } + } + } + } + Msg::AuthStateChanged(state) => { + log::info!("App: Auth state changed: {:?}", state); + self.auth_state = state.clone(); + + match state { + AuthState::Authenticated { .. } => { + // Switch to main app view when authenticated + if self.current_view == AppView::Login { + self.current_view = AppView::Circles; + } + } + AuthState::NotAuthenticated | AuthState::Failed(_) => { + // Switch to login view when not authenticated + self.current_view = AppView::Login; + } + _ => {} + } + true + } + Msg::AuthenticationSuccessful => { + log::info!("App: Authentication successful"); + self.current_view = AppView::Circles; + true + } + Msg::AuthenticationFailed(error) => { + log::error!("App: Authentication failed: {}", error); + self.current_view = AppView::Login; + true + } + Msg::Logout => { + log::info!("App: User logout"); + self.auth_manager.logout(); + self.current_view = AppView::Login; + true + } + } + } + + fn view(&self, ctx: &Context) -> Html { + let link = ctx.link(); + + // If not authenticated and not on login view, show login + if !self.auth_manager.is_authenticated() && self.current_view != AppView::Login { + return html! { + + }; + } + + html! { +
+ { self.render_header(link) } + + { match self.current_view { + AppView::Login => { + html! { + + } + }, + AppView::Circles => { + html!{ + + } + }, + AppView::Library => { + html! { + + } + }, + AppView::Intelligence => html! { + + }, + AppView::Publishing => html! { + + }, + AppView::Inspector => { + html! { + + } + }, + AppView::Customize => html! { + + }, + }} + + { if self.current_view != AppView::Login { + html! { + + } + } else { + html! {} + }} +
+ } + } +} + +impl App { + fn render_header(&self, link: &html::Scope) -> Html { + if self.current_view == AppView::Login { + return html! {}; + } + + html! { +
+
+ { "Circles" } +
+ +
+ } + } +} diff --git a/src/app/src/auth/auth_manager.rs b/src/app/src/auth/auth_manager.rs new file mode 100644 index 0000000..a91cb40 --- /dev/null +++ b/src/app/src/auth/auth_manager.rs @@ -0,0 +1,348 @@ +//! Authentication manager for coordinating authentication flows +//! +//! This module provides the main AuthManager struct that coordinates +//! the entire authentication process, including email lookup and +//! integration with the client_ws library for WebSocket connections. + +use std::rc::Rc; +use std::cell::RefCell; +use yew::Callback; +use gloo_storage::{LocalStorage, SessionStorage, Storage}; +use circle_client_ws::{CircleWsClient, CircleWsClientError, CircleWsClientBuilder}; +use circle_client_ws::auth::{validate_private_key, derive_public_key}; +use crate::auth::types::{AuthResult, AuthError, AuthState, AuthMethod}; +use crate::auth::email_store::{get_key_pair_for_email, is_email_available}; + +/// Key for storing authentication state in local storage +const AUTH_STATE_STORAGE_KEY: &str = "circles_auth_state_marker"; +const PRIVATE_KEY_SESSION_STORAGE_KEY: &str = "circles_private_key"; + +/// Authentication manager that coordinates the auth flow +#[derive(Clone)] +pub struct AuthManager { + state: Rc>, + on_state_change: Rc>>>, +} + +impl PartialEq for AuthManager { + fn eq(&self, other: &Self) -> bool { + // Compare based on the current auth state + self.get_state() == other.get_state() + } +} + +impl AuthManager { + /// Create a new authentication manager + pub fn new() -> Self { + let initial_state = Self::load_auth_state().unwrap_or(AuthState::NotAuthenticated); + + Self { + state: Rc::new(RefCell::new(initial_state)), + on_state_change: Rc::new(RefCell::new(None)), + } + } + + /// Set callback for authentication state changes + pub fn set_on_state_change(&self, callback: Callback) { + *self.on_state_change.borrow_mut() = Some(callback); + } + + /// Get current authentication state + pub fn get_state(&self) -> AuthState { + self.state.borrow().clone() + } + + /// Check if currently authenticated + pub fn is_authenticated(&self) -> bool { + matches!(*self.state.borrow(), AuthState::Authenticated { .. }) + } + + /// Authenticate using email + pub async fn authenticate_with_email(&self, email: String) -> AuthResult<()> { + self.set_state(AuthState::Authenticating); + + // Look up the email in the hardcoded store + let key_pair = get_key_pair_for_email(&email)?; + + // Validate the private key using client_ws + validate_private_key(&key_pair.private_key) + .map_err(|e| AuthError::from(e))?; + + // Set authenticated state + let auth_state = AuthState::Authenticated { + public_key: key_pair.public_key, + private_key: key_pair.private_key, + method: AuthMethod::Email(email), + }; + + self.set_state(auth_state); + Ok(()) + } + + /// Authenticate using private key + pub async fn authenticate_with_private_key(&self, private_key: String) -> AuthResult<()> { + self.set_state(AuthState::Authenticating); + + // Validate the private key using client_ws + validate_private_key(&private_key) + .map_err(|e| AuthError::from(e))?; + + // Derive public key using client_ws + let public_key = derive_public_key(&private_key) + .map_err(|e| AuthError::from(e))?; + + // Set authenticated state + let auth_state = AuthState::Authenticated { + public_key, + private_key: private_key.clone(), + method: AuthMethod::PrivateKey, + }; + + self.set_state(auth_state); + Ok(()) + } + + /// Create an authenticated WebSocket client using message-based authentication + pub async fn create_authenticated_client(&self, ws_url: &str) -> Result { + let auth_state = self.state.borrow().clone(); + + let private_key = match auth_state { + AuthState::Authenticated { private_key, .. } => private_key, + _ => return Err(CircleWsClientError::AuthNoKeyPair), + }; + + let mut client = CircleWsClientBuilder::new(ws_url.to_string()) + .with_keypair(private_key) + .build(); + + client.connect().await?; + client.authenticate().await?; + + Ok(client) + } + + /// Check if an email is available for authentication + pub fn is_email_available(&self, email: &str) -> bool { + is_email_available(email) + } + + /// Get list of available emails for app purposes + pub fn get_available_emails(&self) -> Vec { + crate::auth::email_store::get_available_emails() + } + + /// Logout and clear authentication state + pub fn logout(&self) { + self.set_state(AuthState::NotAuthenticated); + self.clear_stored_auth_state(); + } + + /// Set authentication state and notify listeners + fn set_state(&self, new_state: AuthState) { + *self.state.borrow_mut() = new_state.clone(); + + // Save to local storage (excluding sensitive data) + self.save_auth_state(&new_state); + + // Notify listeners + if let Some(callback) = &*self.on_state_change.borrow() { + callback.emit(new_state); + } + } + + /// Save authentication state to storage. + /// Private keys are stored in sessionStorage, method hints in localStorage. + fn save_auth_state(&self, state: &AuthState) { + match state { + AuthState::Authenticated { public_key: _, private_key, method } => { + match method { + AuthMethod::Email(email) => { + let marker = format!("email:{}", email); + let _ = LocalStorage::set(AUTH_STATE_STORAGE_KEY, marker); + // Clear private key from session storage if user switched to email auth + let _ = SessionStorage::delete(PRIVATE_KEY_SESSION_STORAGE_KEY); + } + AuthMethod::PrivateKey => { + // Store the actual private key in sessionStorage + let _ = SessionStorage::set(PRIVATE_KEY_SESSION_STORAGE_KEY, private_key.clone()); + // Store a marker in localStorage + let _ = LocalStorage::set(AUTH_STATE_STORAGE_KEY, "private_key_auth_marker".to_string()); + } + } + } + AuthState::NotAuthenticated => { + let _ = LocalStorage::set(AUTH_STATE_STORAGE_KEY, "not_authenticated".to_string()); + let _ = SessionStorage::delete(PRIVATE_KEY_SESSION_STORAGE_KEY); + } + AuthState::Authenticating | AuthState::Failed(_) => { + // Transient states, typically don't need to be persisted or can clear storage. + // For now, let's clear localStorage for these, session might still be loading. + let _ = LocalStorage::delete(AUTH_STATE_STORAGE_KEY); + // Optionally, keep session storage if an auth attempt fails but might be retried. + // However, a full logout or switch to NotAuthenticated should clear it. + } + } + } + + /// Load authentication state from storage. + fn load_auth_state() -> Option { + if let Ok(marker) = LocalStorage::get::(AUTH_STATE_STORAGE_KEY) { + if marker == "private_key_auth_marker" { + if let Ok(private_key) = SessionStorage::get::(PRIVATE_KEY_SESSION_STORAGE_KEY) { + if validate_private_key(&private_key).is_ok() { + if let Ok(public_key) = derive_public_key(&private_key) { + return Some(AuthState::Authenticated { + public_key, + private_key, + method: AuthMethod::PrivateKey, + }); + } + } + // Invalid key in session, clear it + let _ = SessionStorage::delete(PRIVATE_KEY_SESSION_STORAGE_KEY); + } + // Marker present but key missing/invalid, treat as not authenticated + let _ = LocalStorage::set(AUTH_STATE_STORAGE_KEY, "not_authenticated".to_string()); + return Some(AuthState::NotAuthenticated); + } else if let Some(email) = marker.strip_prefix("email:") { + if let Ok(key_pair) = get_key_pair_for_email(email) { + // Ensure session storage is clear if we are in email mode + let _ = SessionStorage::delete(PRIVATE_KEY_SESSION_STORAGE_KEY); + return Some(AuthState::Authenticated { + public_key: key_pair.public_key, + private_key: key_pair.private_key, // This is from email_store, not user input + method: AuthMethod::Email(email.to_string()), + }); + } + // Email re-auth failed + let _ = LocalStorage::set(AUTH_STATE_STORAGE_KEY, "not_authenticated".to_string()); + return Some(AuthState::NotAuthenticated); + } else if marker == "not_authenticated" { + return Some(AuthState::NotAuthenticated); + } + } + // No valid marker or key found + None // Defaults to NotAuthenticated in AuthManager::new() + } + + /// Clear stored authentication state from both localStorage and sessionStorage + fn clear_stored_auth_state(&self) { + let _ = LocalStorage::delete(AUTH_STATE_STORAGE_KEY); + let _ = SessionStorage::delete(PRIVATE_KEY_SESSION_STORAGE_KEY); + } + + /// Get current authentication method if authenticated + pub fn get_auth_method(&self) -> Option { + match &*self.state.borrow() { + AuthState::Authenticated { method, .. } => Some(method.clone()), + _ => None, + } + } + + /// Get current public key if authenticated + pub fn get_public_key(&self) -> Option { + match &*self.state.borrow() { + AuthState::Authenticated { public_key, .. } => Some(public_key.clone()), + _ => None, + } + } + + /// Validate current authentication state + pub fn validate_current_auth(&self) -> AuthResult<()> { + match &*self.state.borrow() { + AuthState::Authenticated { private_key, .. } => { + validate_private_key(private_key) + .map_err(|e| AuthError::from(e)) + } + _ => Err(AuthError::AuthFailed("Not authenticated".to_string())), + } + } +} + +impl Default for AuthManager { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use wasm_bindgen_test::*; + + wasm_bindgen_test_configure!(run_in_browser); + + #[wasm_bindgen_test] + async fn test_email_authentication() { + let auth_manager = AuthManager::new(); + + // Test with valid email + let result = auth_manager.authenticate_with_email("alice@example.com".to_string()).await; + assert!(result.is_ok()); + assert!(auth_manager.is_authenticated()); + + // Check that we can get the public key + assert!(auth_manager.get_public_key().is_some()); + + // Check auth method + match auth_manager.get_auth_method() { + Some(AuthMethod::Email(email)) => assert_eq!(email, "alice@example.com"), + _ => panic!("Expected email auth method"), + } + } + + #[wasm_bindgen_test] + async fn test_private_key_authentication() { + let auth_manager = AuthManager::new(); + + // Test with valid private key + let private_key = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"; + let result = auth_manager.authenticate_with_private_key(private_key.to_string()).await; + assert!(result.is_ok()); + assert!(auth_manager.is_authenticated()); + + // Check that we can get the public key + assert!(auth_manager.get_public_key().is_some()); + } + + #[wasm_bindgen_test] + async fn test_invalid_email() { + let auth_manager = AuthManager::new(); + + let result = auth_manager.authenticate_with_email("nonexistent@example.com".to_string()).await; + assert!(result.is_err()); + assert!(!auth_manager.is_authenticated()); + } + + #[wasm_bindgen_test] + async fn test_invalid_private_key() { + let auth_manager = AuthManager::new(); + + let result = auth_manager.authenticate_with_private_key("invalid_key".to_string()).await; + assert!(result.is_err()); + assert!(!auth_manager.is_authenticated()); + } + + #[wasm_bindgen_test] + async fn test_logout() { + let auth_manager = AuthManager::new(); + + // Authenticate first + let _ = auth_manager.authenticate_with_email("alice@example.com".to_string()).await; + assert!(auth_manager.is_authenticated()); + + // Logout + auth_manager.logout(); + assert!(!auth_manager.is_authenticated()); + assert!(auth_manager.get_public_key().is_none()); + } + + #[wasm_bindgen_test] + fn test_email_availability() { + let auth_manager = AuthManager::new(); + + assert!(auth_manager.is_email_available("alice@example.com")); + assert!(auth_manager.is_email_available("admin@circles.com")); + assert!(!auth_manager.is_email_available("nonexistent@example.com")); + } +} \ No newline at end of file diff --git a/src/app/src/auth/email_store.rs b/src/app/src/auth/email_store.rs new file mode 100644 index 0000000..5c61e4c --- /dev/null +++ b/src/app/src/auth/email_store.rs @@ -0,0 +1,180 @@ +//! Hardcoded email-to-private-key mappings +//! +//! This module provides a static mapping of email addresses to their corresponding +//! private and public key pairs. This is designed for development and app purposes +//! where users can authenticate using known email addresses. + +use std::collections::HashMap; +use crate::auth::types::{AuthResult, AuthError}; +use circle_client_ws::auth::derive_public_key; + +/// A key pair consisting of private and public keys +#[derive(Debug, Clone)] +pub struct KeyPair { + pub private_key: String, + pub public_key: String, +} + +/// Get the hardcoded email-to-key mappings +/// +/// Returns a HashMap where: +/// - Key: email address (String) +/// - Value: KeyPair with private and public keys +pub fn get_email_key_mappings() -> HashMap { + let mut mappings = HashMap::new(); + + // Demo users with their private keys + // Note: These are for demonstration purposes only + let demo_keys = vec![ + ( + "alice@example.com", + "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + ), + ( + "bob@example.com", + "0xfedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321" + ), + ( + "charlie@example.com", + "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890" + ), + ( + "diana@example.com", + "0x9876543210fedcba9876543210fedcba9876543210fedcba9876543210fedcba" + ), + ( + "eve@example.com", + "0x1111222233334444555566667777888899990000aaaabbbbccccddddeeeeffff" + ), + ( + "admin@circles.com", + "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + ), + ( + "app@circles.com", + "0xdeadbeefcafebabe1234567890abcdef1234567890abcdef1234567890abcdef" + ), + ( + "test@circles.com", + "0xbaadf00dcafebabe9876543210fedcba9876543210fedcba9876543210fedcba" + ), + ]; + + // Generate key pairs for each app user + for (email, private_key) in demo_keys { + if let Ok(public_key) = derive_public_key(private_key) { + mappings.insert( + email.to_string(), + KeyPair { + private_key: private_key.to_string(), + public_key, + } + ); + } else { + log::error!("Failed to derive public key for email: {}", email); + } + } + + mappings +} + +/// Look up a key pair by email address +pub fn get_key_pair_for_email(email: &str) -> AuthResult { + let mappings = get_email_key_mappings(); + + mappings.get(email) + .cloned() + .ok_or_else(|| AuthError::EmailNotFound(email.to_string())) +} + +/// Get all available email addresses +pub fn get_available_emails() -> Vec { + get_email_key_mappings().keys().cloned().collect() +} + +/// Check if an email address is available in the store +pub fn is_email_available(email: &str) -> bool { + get_email_key_mappings().contains_key(email) +} + +/// Add a new email-key mapping (for runtime additions) +/// Note: This will only persist for the current session +pub fn add_email_key_mapping(email: String, private_key: String) -> AuthResult<()> { + // Validate the private key first + let public_key = derive_public_key(&private_key)?; + + // In a real implementation, you might want to persist this + // For now, we just validate that it would work + log::info!("Would add mapping for email: {} with public key: {}", email, public_key); + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use circle_client_ws::auth::{validate_private_key, verify_signature, sign_message}; + + #[test] + fn test_email_mappings_exist() { + let mappings = get_email_key_mappings(); + assert!(!mappings.is_empty()); + + // Check that alice@example.com exists + assert!(mappings.contains_key("alice@example.com")); + assert!(mappings.contains_key("admin@circles.com")); + } + + #[test] + fn test_key_pair_lookup() { + let key_pair = get_key_pair_for_email("alice@example.com").unwrap(); + + // Validate that the private key is valid + assert!(validate_private_key(&key_pair.private_key).is_ok()); + + // Validate that the public key matches the private key + let derived_public = derive_public_key(&key_pair.private_key).unwrap(); + assert_eq!(key_pair.public_key, derived_public); + } + + #[test] + fn test_signing_with_stored_keys() { + let key_pair = get_key_pair_for_email("bob@example.com").unwrap(); + let message = "Test message"; + + // Sign a message with the stored private key + let signature = sign_message(&key_pair.private_key, message).unwrap(); + + // Verify the signature with the stored public key + let is_valid = verify_signature(&key_pair.public_key, message, &signature).unwrap(); + assert!(is_valid); + } + + #[test] + fn test_email_not_found() { + let result = get_key_pair_for_email("nonexistent@example.com"); + assert!(result.is_err()); + + match result { + Err(AuthError::EmailNotFound(email)) => { + assert_eq!(email, "nonexistent@example.com"); + } + _ => panic!("Expected EmailNotFound error"), + } + } + + #[test] + fn test_available_emails() { + let emails = get_available_emails(); + assert!(!emails.is_empty()); + assert!(emails.contains(&"alice@example.com".to_string())); + assert!(emails.contains(&"admin@circles.com".to_string())); + } + + #[test] + fn test_is_email_available() { + assert!(is_email_available("alice@example.com")); + assert!(is_email_available("admin@circles.com")); + assert!(!is_email_available("nonexistent@example.com")); + } +} \ No newline at end of file diff --git a/src/app/src/auth/mod.rs b/src/app/src/auth/mod.rs new file mode 100644 index 0000000..871b1f8 --- /dev/null +++ b/src/app/src/auth/mod.rs @@ -0,0 +1,17 @@ +//! Authentication module for the Circles app +//! +//! This module provides application-specific authentication functionality including: +//! - Email-to-private-key mappings (hardcoded for app) +//! - Authentication manager for coordinating auth flows +//! - Integration with the client_ws library for WebSocket authentication +//! +//! Core cryptographic functionality is provided by the client_ws library. + +pub mod auth_manager; +pub mod email_store; +pub mod types; + +pub use auth_manager::AuthManager; +pub use types::*; + +// Re-export commonly used items from client_ws for convenience diff --git a/src/app/src/auth/types.rs b/src/app/src/auth/types.rs new file mode 100644 index 0000000..4fe10a2 --- /dev/null +++ b/src/app/src/auth/types.rs @@ -0,0 +1,72 @@ +//! Application-specific authentication types +//! +//! This module defines app-specific authentication types that extend +//! the core types from the client_ws library. + +// Re-export core types from client_ws + +// Define app-specific AuthResult that uses our local AuthError +pub type AuthResult = Result; + +// Extend AuthError with app-specific variants +use thiserror::Error; + +#[derive(Debug, Clone, PartialEq, Error)] +pub enum AuthError { + #[error("Client error: {0}")] + ClientError(String), + + #[error("Authentication failed: {0}")] + AuthFailed(String), + + // App-specific errors + #[error("Email not found: {0}")] + EmailNotFound(String), + + #[error("Generic error: {0}")] + Generic(String), + + #[error("Not authenticated")] + NotAuthenticated, +} + +impl From for AuthError { + fn from(err: circle_client_ws::CircleWsClientError) -> Self { + AuthError::ClientError(err.to_string()) + } +} + +impl From for AuthError { + fn from(err: circle_client_ws::auth::AuthError) -> Self { + AuthError::AuthFailed(err.to_string()) + } +} + +/// Authentication method chosen by the user (app-specific) +#[derive(Debug, Clone, PartialEq)] +pub enum AuthMethod { + PrivateKey, // Direct private key input + Email(String), // Email-based lookup (app-specific) +} + +impl std::fmt::Display for AuthMethod { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AuthMethod::PrivateKey => write!(f, "Private Key"), + AuthMethod::Email(email) => write!(f, "Email ({})", email), + } + } +} + +/// Application-specific authentication state +#[derive(Debug, Clone, PartialEq)] +pub enum AuthState { + NotAuthenticated, + Authenticating, + Authenticated { + public_key: String, + private_key: String, + method: AuthMethod, + }, + Failed(String), // Error message +} \ No newline at end of file diff --git a/src/app/src/components/asset_details_card.rs b/src/app/src/components/asset_details_card.rs new file mode 100644 index 0000000..07daaef --- /dev/null +++ b/src/app/src/components/asset_details_card.rs @@ -0,0 +1,175 @@ +use yew::prelude::*; +use heromodels::models::library::items::TocEntry; +use crate::components::library_view::DisplayLibraryItem; + +#[derive(Clone, PartialEq, Properties)] +pub struct AssetDetailsCardProps { + pub item: DisplayLibraryItem, + pub on_back: Callback<()>, + pub on_toc_click: Callback, + pub current_slide_index: Option, +} + +pub struct AssetDetailsCard; + +impl Component for AssetDetailsCard { + type Message = (); + type Properties = AssetDetailsCardProps; + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + let props = ctx.props(); + + let back_handler = { + let on_back = props.on_back.clone(); + Callback::from(move |_: MouseEvent| { + on_back.emit(()); + }) + }; + + match &props.item { + DisplayLibraryItem::Image(img) => html! { +
+ +
+ {img.title.clone()} +
+
+

{ &img.title }

+ { if let Some(desc) = &img.description { + html! {

{ desc }

} + } else { html! {} }} + +
+
+ }, + DisplayLibraryItem::Pdf(pdf) => html! { +
+ +
+ +
+
+

{ &pdf.title }

+ { if let Some(desc) = &pdf.description { + html! {

{ desc }

} + } else { html! {} }} + + + {"Open in new tab ↗"} + +
+
+ }, + DisplayLibraryItem::Markdown(md) => html! { +
+ +
+ +
+
+

{ &md.title }

+ { if let Some(desc) = &md.description { + html! {

{ desc }

} + } else { html! {} }} + +
+
+ }, + DisplayLibraryItem::Book(book) => html! { +
+ +
+ +
+
+

{ &book.title }

+ { if let Some(desc) = &book.description { + html! {

{ desc }

} + } else { html! {} }} + + { if !book.table_of_contents.is_empty() { + html! { +
+

{"Table of Contents"}

+ { self.render_toc(ctx, &book.table_of_contents) } +
+ } + } else { html! {} }} +
+
+ }, + DisplayLibraryItem::Slides(slides) => html! { +
+ +
+ +
+
+

{ &slides.title }

+ { if let Some(desc) = &slides.description { + html! {

{ desc }

} + } else { html! {} }} + +
+
+ }, + } + } +} + +impl AssetDetailsCard { + fn render_toc(&self, ctx: &Context, toc: &[TocEntry]) -> Html { + let props = ctx.props(); + html! { +
    + { toc.iter().map(|entry| { + let page = entry.page as usize; + let on_toc_click = props.on_toc_click.clone(); + let onclick = Callback::from(move |_: MouseEvent| { + on_toc_click.emit(page); + }); + html! { +
  • + + { if !entry.subsections.is_empty() { + self.render_toc(ctx, &entry.subsections) + } else { html! {} }} +
  • + } + }).collect::() } +
+ } + } +} \ No newline at end of file diff --git a/src/app/src/components/auth_view.rs b/src/app/src/components/auth_view.rs new file mode 100644 index 0000000..023e537 --- /dev/null +++ b/src/app/src/components/auth_view.rs @@ -0,0 +1,67 @@ +use crate::auth::types::AuthState; +use yew::prelude::*; + +#[derive(Properties, PartialEq, Clone)] +pub struct AuthViewProps { + pub auth_state: AuthState, + pub on_logout: Callback<()>, + pub on_login: Callback<()>, // New callback for login +} + +#[function_component(AuthView)] +pub fn auth_view(props: &AuthViewProps) -> Html { + match &props.auth_state { + AuthState::Authenticated { public_key, .. } => { + let on_logout = props.on_logout.clone(); + let logout_onclick = Callback::from(move |_| { + on_logout.emit(()); + }); + + // Truncate the public key for display + let pk_short = if public_key.len() > 10 { + format!("{}...{}", &public_key[..4], &public_key[public_key.len()-4..]) + } else { + public_key.clone() + }; + + html! { +
+ { format!("PK: {}", pk_short) } + +
+ } + } + AuthState::NotAuthenticated | AuthState::Failed(_) => { + let on_login = props.on_login.clone(); + let login_onclick = Callback::from(move |_| { + on_login.emit(()); + }); + + html! { +
+ { "Not Authenticated" } + +
+ } + } + AuthState::Authenticating => { + html! { +
+ { "Authenticating..." } +
+ } + } + } +} diff --git a/src/app/src/components/book_viewer.rs b/src/app/src/components/book_viewer.rs new file mode 100644 index 0000000..59c382d --- /dev/null +++ b/src/app/src/components/book_viewer.rs @@ -0,0 +1,155 @@ +use yew::prelude::*; +use heromodels::models::library::items::{Book, TocEntry}; + +#[derive(Clone, PartialEq, Properties)] +pub struct BookViewerProps { + pub book: Book, + pub on_back: Callback<()>, +} + +pub enum BookViewerMsg { + GoToPage(usize), + NextPage, + PrevPage, +} + +pub struct BookViewer { + current_page: usize, +} + +impl Component for BookViewer { + type Message = BookViewerMsg; + type Properties = BookViewerProps; + + fn create(_ctx: &Context) -> Self { + Self { + current_page: 0, + } + } + + fn update(&mut self, _ctx: &Context, msg: Self::Message) -> bool { + match msg { + BookViewerMsg::GoToPage(page) => { + self.current_page = page; + true + } + BookViewerMsg::NextPage => { + let props = _ctx.props(); + if self.current_page < props.book.pages.len().saturating_sub(1) { + self.current_page += 1; + } + true + } + BookViewerMsg::PrevPage => { + if self.current_page > 0 { + self.current_page -= 1; + } + true + } + } + } + + fn view(&self, ctx: &Context) -> Html { + let props = ctx.props(); + let total_pages = props.book.pages.len(); + + let back_handler = { + let on_back = props.on_back.clone(); + Callback::from(move |_: MouseEvent| { + on_back.emit(()); + }) + }; + + let prev_handler = ctx.link().callback(|_: MouseEvent| BookViewerMsg::PrevPage); + let next_handler = ctx.link().callback(|_: MouseEvent| BookViewerMsg::NextPage); + + html! { +
+ +
+

{ &props.book.title }

+
+ + + { format!("Page {} of {}", self.current_page + 1, total_pages) } + + +
+
+
+
+ { if let Some(page_content) = props.book.pages.get(self.current_page) { + self.render_markdown(page_content) + } else { + html! {

{"Page not found"}

} + }} +
+
+
+ } + } +} + +impl BookViewer { + fn render_markdown(&self, content: &str) -> Html { + // Simple markdown rendering - convert basic markdown to HTML + let lines: Vec<&str> = content.lines().collect(); + let mut html_content = Vec::new(); + + for line in lines { + if line.starts_with("# ") { + html_content.push(html! {

{ &line[2..] }

}); + } else if line.starts_with("## ") { + html_content.push(html! {

{ &line[3..] }

}); + } else if line.starts_with("### ") { + html_content.push(html! {

{ &line[4..] }

}); + } else if line.starts_with("- ") { + html_content.push(html! {
  • { &line[2..] }
  • }); + } else if line.starts_with("**") && line.ends_with("**") { + let text = &line[2..line.len()-2]; + html_content.push(html! {

    { text }

    }); + } else if !line.trim().is_empty() { + html_content.push(html! {

    { line }

    }); + } else { + html_content.push(html! {
    }); + } + } + + html! {
    { for html_content }
    } + } + + pub fn render_toc(&self, ctx: &Context, toc: &[TocEntry]) -> Html { + html! { +
      + { toc.iter().map(|entry| { + let page = entry.page as usize; + let onclick = ctx.link().callback(move |_: MouseEvent| BookViewerMsg::GoToPage(page)); + html! { +
    • + + { if !entry.subsections.is_empty() { + self.render_toc(ctx, &entry.subsections) + } else { html! {} }} +
    • + } + }).collect::() } +
    + } + } +} \ No newline at end of file diff --git a/src/app/src/components/chat.rs b/src/app/src/components/chat.rs new file mode 100644 index 0000000..19a24b0 --- /dev/null +++ b/src/app/src/components/chat.rs @@ -0,0 +1,665 @@ +use yew::prelude::*; +use chrono::{DateTime, Utc}; +use wasm_bindgen::JsCast; +use std::collections::HashMap; + +#[derive(Clone, Debug, PartialEq)] +pub struct ChatMessage { + pub id: usize, + pub content: String, + pub sender: ChatSender, + pub timestamp: String, + pub title: Option, + pub description: Option, + pub status: Option, + pub format: String, + pub source: Option, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum ChatSender { + User, + Assistant, + System, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum InputType { + Text, + Code, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct ChatResponse { + pub data: Vec, + pub format: String, + pub source: String, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Conversation { + pub id: u32, + pub title: String, + pub messages: Vec, + pub created_at: String, + pub last_updated: String, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct ConversationSummary { + pub id: u32, + pub title: String, + pub last_message_preview: Option, +} + +#[derive(Properties, PartialEq)] +pub struct ChatInterfaceProps { + pub on_process_message: Callback<(Vec, String, Callback)>, // (data, format, response_callback) + pub placeholder: String, + pub show_title_description: bool, + pub conversation_title: Option, + pub input_type: Option, + pub input_format: Option, + #[prop_or_default] + pub on_conversations_updated: Option>>, + #[prop_or_default] + pub active_conversation_id: Option, + #[prop_or_default] + pub on_conversation_selected: Option>, + #[prop_or_default] + pub external_conversation_selection: Option, + #[prop_or_default] + pub external_new_conversation_trigger: Option, +} + +pub struct ChatInterface { + conversations: HashMap, + active_conversation_id: Option, + current_input: String, + current_title: Option, + current_description: Option, + next_message_id: usize, + next_conversation_id: u32, +} + +pub enum ChatMsg { + UpdateInput(String), + UpdateTitle(String), + UpdateDescription(String), + SendMessage, + AddResponse(ChatResponse), + NewConversation, + SelectConversation(u32), + LoadConversation(u32), +} + +impl Component for ChatInterface { + type Message = ChatMsg; + type Properties = ChatInterfaceProps; + + fn create(ctx: &Context) -> Self { + let mut chat_interface = Self { + conversations: HashMap::new(), + active_conversation_id: ctx.props().active_conversation_id, + current_input: String::new(), + current_title: None, + current_description: None, + next_message_id: 0, + next_conversation_id: 1, + }; + + // Create initial conversation if none exists + if chat_interface.active_conversation_id.is_none() { + chat_interface.create_new_conversation(); + // Notify parent immediately of the new conversation + if let Some(callback) = &ctx.props().on_conversations_updated { + let summaries = chat_interface.get_conversation_summaries(); + callback.emit(summaries); + } + } + + chat_interface + } + + fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { + match msg { + ChatMsg::UpdateInput(input) => { + self.current_input = input; + false + } + ChatMsg::UpdateTitle(title) => { + self.current_title = Some(title); + false + } + ChatMsg::UpdateDescription(description) => { + self.current_description = Some(description); + false + } + ChatMsg::SendMessage => { + if !self.current_input.trim().is_empty() { + // Ensure we have an active conversation + if self.active_conversation_id.is_none() { + self.create_new_conversation(); + } + + let conversation_id = self.active_conversation_id.unwrap(); + + // Add user message to active conversation + let input_format = ctx.props().input_format.clone().unwrap_or_else(|| "text".to_string()); + let user_message = ChatMessage { + id: self.next_message_id, + content: self.current_input.clone(), + sender: ChatSender::User, + timestamp: chrono::Utc::now().to_rfc3339(), + title: self.current_title.clone(), + description: self.current_description.clone(), + status: None, + format: input_format.clone(), + source: None, + }; + + if let Some(conversation) = self.conversations.get_mut(&conversation_id) { + conversation.messages.push(user_message); + conversation.last_updated = chrono::Utc::now().to_rfc3339(); + + // Update conversation title if it's the first message + if conversation.messages.len() == 1 { + let title = if self.current_input.len() > 50 { + format!("{}...", &self.current_input[..47]) + } else { + self.current_input.clone() + }; + conversation.title = title; + } + } + + self.next_message_id += 1; + + // Process message through callback with response handler + let input_data = self.current_input.as_bytes().to_vec(); + + // Create response callback that adds responses to chat + let link = ctx.link().clone(); + let response_callback = Callback::from(move |response: ChatResponse| { + link.send_message(ChatMsg::AddResponse(response)); + }); + + // Trigger processing with response callback + ctx.props().on_process_message.emit((input_data, input_format, response_callback)); + + // Clear inputs + self.current_input.clear(); + self.current_title = None; + self.current_description = None; + + // Notify parent of conversation updates + self.notify_conversations_updated(ctx); + } + true + } + ChatMsg::AddResponse(response) => { + if let Some(conversation_id) = self.active_conversation_id { + // Add response from async callback to active conversation + let response_content = String::from_utf8_lossy(&response.data).to_string(); + + // Use the format provided by the response to determine status + let status = match response.format.as_str() { + "error" => "Error".to_string(), + _ => "Ok".to_string(), + }; + + let response_message = ChatMessage { + id: self.next_message_id, + content: response_content, + sender: ChatSender::Assistant, + timestamp: chrono::Utc::now().to_rfc3339(), + title: None, + description: None, + status: Some(status), + format: response.format.clone(), + source: Some(response.source.clone()), + }; + + if let Some(conversation) = self.conversations.get_mut(&conversation_id) { + conversation.messages.push(response_message); + conversation.last_updated = chrono::Utc::now().to_rfc3339(); + } + + self.next_message_id += 1; + + // Notify parent of conversation updates + self.notify_conversations_updated(ctx); + } + true + } + ChatMsg::NewConversation => { + self.create_new_conversation(); + self.notify_conversations_updated(ctx); + if let Some(callback) = &ctx.props().on_conversation_selected { + if let Some(id) = self.active_conversation_id { + callback.emit(id); + } + } + true + } + ChatMsg::SelectConversation(conversation_id) => { + if self.conversations.contains_key(&conversation_id) { + self.active_conversation_id = Some(conversation_id); + true + } else { + false + } + } + ChatMsg::LoadConversation(conversation_id) => { + self.active_conversation_id = Some(conversation_id); + true + } + } + } + + fn changed(&mut self, ctx: &Context, old_props: &Self::Properties) -> bool { + let mut should_update = false; + + // Handle external conversation selection + if let Some(new_active_id) = ctx.props().external_conversation_selection { + if old_props.external_conversation_selection != Some(new_active_id) { + if self.conversations.contains_key(&new_active_id) { + self.active_conversation_id = Some(new_active_id); + should_update = true; + } + } + } + + // Handle external new conversation trigger + if let Some(trigger) = ctx.props().external_new_conversation_trigger { + if old_props.external_new_conversation_trigger != Some(trigger) && trigger { + self.create_new_conversation(); + self.notify_conversations_updated(ctx); + should_update = true; + } + } + + should_update + } + + fn view(&self, ctx: &Context) -> Html { + let props = ctx.props(); + + let on_input = { + let link = ctx.link().clone(); + Callback::from(move |e: InputEvent| { + let target = e.target().unwrap(); + let value = if let Ok(input) = target.clone().dyn_into::() { + input.value() + } else if let Ok(textarea) = target.dyn_into::() { + textarea.value() + } else { + String::new() + }; + link.send_message(ChatMsg::UpdateInput(value)); + }) + }; + + let on_title = { + let link = ctx.link().clone(); + Callback::from(move |e: InputEvent| { + let input: web_sys::HtmlInputElement = e.target_unchecked_into(); + link.send_message(ChatMsg::UpdateTitle(input.value())); + }) + }; + + let on_description = { + let link = ctx.link().clone(); + Callback::from(move |e: InputEvent| { + let input: web_sys::HtmlInputElement = e.target_unchecked_into(); + link.send_message(ChatMsg::UpdateDescription(input.value())); + }) + }; + + let on_submit = { + let link = ctx.link().clone(); + Callback::from(move |e: SubmitEvent| { + e.prevent_default(); + link.send_message(ChatMsg::SendMessage); + }) + }; + + // Get current conversation messages + let empty_messages = Vec::new(); + let current_messages = if let Some(conversation_id) = self.active_conversation_id { + self.conversations.get(&conversation_id) + .map(|conv| &conv.messages) + .unwrap_or(&empty_messages) + } else { + &empty_messages + }; + + // Get conversation title + let conversation_title = if let Some(conversation_id) = self.active_conversation_id { + self.conversations.get(&conversation_id) + .map(|conv| conv.title.clone()) + .or_else(|| props.conversation_title.clone()) + } else { + props.conversation_title.clone() + }; + + html! { +
    +
    + { + if let Some(title) = &conversation_title { + html! {

    { title }

    } + } else { + html! {} + } + } + { + if current_messages.is_empty() { + html! {

    { "No messages yet. Start the conversation!" }

    } + } else { + html! { + <> + { for current_messages.iter().map(|msg| view_chat_message(msg)) } + + } + } + } +
    +
    + { + if props.show_title_description { + html! { + <> + + + + } + } else { + html! {} + } + } + { + match props.input_type.as_ref().unwrap_or(&InputType::Text) { + InputType::Code => { + let mut class = "input-base chat-input code-input".to_string(); + if let Some(format) = &props.input_format { + class.push_str(&format!(" format-{}", format)); + } + html! { +