Download cpp-cli/bexcli.cpp from krystv/pluginengine01: direct link, hf CLI and curl.
- Browser
- Download file 16.8 kB
-
https://huggingface.co/krystv/pluginengine01/resolve/main/cpp-cli/bexcli.cpp
- Command line
-
hf download hf://krystv/pluginengine01/cpp-cli/bexcli.cpp
-
curl -L -o bexcli.cpp https://huggingface.co/krystv/pluginengine01/resolve/main/cpp-cli/bexcli.cpp
16.8 kB
| // BEX C++ CLI Tool β Pure C ABI + Callback Architecture | |
| // | |
| // Full-featured CLI demonstrating the pure C FFI with async callback pattern. | |
| // No bridge crate dependency β just bex_engine.h and the Rust static/shared library. | |
| // | |
| // Features: | |
| // - Plugin management: install, uninstall, list, info, enable, disable | |
| // - API key management: set-key, get-key, delete-key, list-keys | |
| // - Media browsing: home, search, info, servers, stream | |
| // - Async operations use std::promise/future for blocking wait | |
| // | |
| // Build with CMake: | |
| // mkdir build && cd build | |
| // cmake .. -DCMAKE_BUILD_TYPE=Release | |
| // make -j$(nproc) | |
| // ββ Callback handler for async operations ββββββββββββββββββββββββββββββ | |
| /// The C-callback triggered by Rust's Tokio thread when an async task finishes. | |
| /// It casts user_data back to a std::promise and fulfills it. | |
| extern "C" void on_bex_result(void* user_data, uint64_t req_id, | |
| bool success, const uint8_t* payload, size_t len) { | |
| auto* promise = static_cast<std::promise<std::string>*>(user_data); | |
| if (success) { | |
| std::string result(reinterpret_cast<const char*>(payload), len); | |
| promise->set_value(std::move(result)); | |
| } else { | |
| std::string error_msg(reinterpret_cast<const char*>(payload), len); | |
| promise->set_exception(std::make_exception_ptr(std::runtime_error(error_msg))); | |
| } | |
| } | |
| /// Submit an async request and block until the result arrives. | |
| /// Uses std::promise/future to bridge the async callback to sync CLI. | |
| std::string await_request(BexEngine* engine, uint64_t request_id, | |
| std::promise<std::string>& promise, uint32_t timeout_ms = 30000) { | |
| auto future = promise.get_future(); | |
| auto status = future.wait_for(std::chrono::milliseconds(timeout_ms)); | |
| if (status == std::future_status::timeout) { | |
| bex_cancel_request(engine, request_id); | |
| throw std::runtime_error("Request timed out after " + std::to_string(timeout_ms) + "ms"); | |
| } | |
| return future.get(); | |
| } | |
| // ββ Pretty-print JSON ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| void print_json(const std::string& json_str) { | |
| bool already_pretty = json_str.find('\n') != std::string::npos; | |
| if (already_pretty) { | |
| std::cout << json_str << std::endl; | |
| return; | |
| } | |
| int indent = 0; | |
| bool in_string = false; | |
| bool escape = false; | |
| for (size_t i = 0; i < json_str.size(); i++) { | |
| char c = json_str[i]; | |
| if (escape) { | |
| std::cout << c; | |
| escape = false; | |
| continue; | |
| } | |
| if (c == '\\' && in_string) { | |
| std::cout << c; | |
| escape = true; | |
| continue; | |
| } | |
| if (c == '"') { | |
| in_string = !in_string; | |
| std::cout << c; | |
| continue; | |
| } | |
| if (in_string) { | |
| std::cout << c; | |
| continue; | |
| } | |
| switch (c) { | |
| case '{': | |
| case '[': | |
| std::cout << c << "\n"; | |
| indent += 2; | |
| std::cout << std::string(indent, ' '); | |
| break; | |
| case '}': | |
| case ']': | |
| std::cout << "\n"; | |
| indent -= 2; | |
| std::cout << std::string(indent, ' ') << c; | |
| break; | |
| case ',': | |
| std::cout << c << "\n" << std::string(indent, ' '); | |
| break; | |
| case ':': | |
| std::cout << c << " "; | |
| break; | |
| case ' ': | |
| case '\n': | |
| case '\r': | |
| case '\t': | |
| break; | |
| default: | |
| std::cout << c; | |
| } | |
| } | |
| std::cout << std::endl; | |
| } | |
| /// Decode capability bits into a string | |
| std::string capabilities_str(uint32_t caps) { | |
| std::string result; | |
| if (caps & (1 << 0)) result += "HOME "; | |
| if (caps & (1 << 1)) result += "CATEGORY "; | |
| if (caps & (1 << 2)) result += "SEARCH "; | |
| if (caps & (1 << 3)) result += "INFO "; | |
| if (caps & (1 << 4)) result += "SERVERS "; | |
| if (caps & (1 << 5)) result += "STREAM "; | |
| if (caps & (1 << 6)) result += "SUBTITLES "; | |
| if (caps & (1 << 7)) result += "ARTICLES "; | |
| if (!result.empty()) result.pop_back(); | |
| return result; | |
| } | |
| /// RAII wrapper for BexPluginInfoList | |
| struct PluginListGuard { | |
| BexPluginInfoList list; | |
| explicit PluginListGuard(BexPluginInfoList l) : list(l) {} | |
| ~PluginListGuard() { bex_plugin_info_list_free(list); } | |
| BexPluginInfo* begin() { return list.items; } | |
| BexPluginInfo* end() { return list.items + list.count; } | |
| size_t size() const { return list.count; } | |
| }; | |
| /// RAII wrapper for C strings returned by the engine | |
| struct CStrGuard { | |
| char* ptr; | |
| explicit CStrGuard(char* p) : ptr(p) {} | |
| ~CStrGuard() { if (ptr) bex_string_free(ptr); } | |
| std::string str() const { return ptr ? std::string(ptr) : std::string(); } | |
| }; | |
| // ββ Usage ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| void print_usage(const char* prog) { | |
| std::cerr | |
| << "BEX C++ CLI v4.0 β WASM Plugin Engine (Pure C ABI + Callbacks)\n" | |
| << "\n" | |
| << "Usage:\n" | |
| << " " << prog << " install <path> Install a .bex plugin package\n" | |
| << " " << prog << " uninstall <id> Uninstall a plugin by ID\n" | |
| << " " << prog << " list List installed plugins\n" | |
| << " " << prog << " info-plugin <id> Show detailed plugin information\n" | |
| << " " << prog << " enable <id> Enable a plugin\n" | |
| << " " << prog << " disable <id> Disable a plugin\n" | |
| << "\n" | |
| << " API Key / Secret Management:\n" | |
| << " " << prog << " set-key <plugin_id> <key> <val> Set an API key for a plugin\n" | |
| << " " << prog << " get-key <plugin_id> <key> Get an API key value\n" | |
| << " " << prog << " delete-key <plugin_id> <key> Delete an API key\n" | |
| << " " << prog << " list-keys <plugin_id> List all keys for a plugin\n" | |
| << "\n" | |
| << " Media Browsing (async with callbacks):\n" | |
| << " " << prog << " home <plugin_id> Get home sections\n" | |
| << " " << prog << " search <plugin_id> <query> Search media\n" | |
| << " " << prog << " info <plugin_id> <id> Get media info\n" | |
| << " " << prog << " servers <plugin_id> <id> Get servers (id is self-describing)\n" | |
| << " " << prog << " stream <plugin_id> <json> Resolve stream from server JSON\n" | |
| << "\n" | |
| << " Debug:\n" | |
| << " " << prog << " stats Show engine stats\n" | |
| << "\n" | |
| << "Design: IDs are self-describing. The plugin knows how to parse its own IDs.\n" | |
| << "Example: bexcli servers com.gogoanime 'one-piece$ep=1$sub=1$dub=0'\n" | |
| << std::endl; | |
| } | |
| // ββ Main βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| int main(int argc, char* argv[]) { | |
| if (argc < 2) { | |
| print_usage(argv[0]); | |
| return 1; | |
| } | |
| std::string data_dir = std::string(getenv("HOME") ? getenv("HOME") : ".") + "/.bex-data"; | |
| if (getenv("BEX_DATA_DIR")) { | |
| data_dir = getenv("BEX_DATA_DIR"); | |
| } | |
| std::string cmd = argv[1]; | |
| // Create the engine | |
| BexEngine* engine = bex_engine_new(data_dir.c_str()); | |
| if (!engine) { | |
| std::cerr << "Error: Failed to create BexEngine" << std::endl; | |
| return 1; | |
| } | |
| try { | |
| // ββ Plugin Management ββββββββββββββββββββββββββββββββββββββ | |
| if (cmd == "install" && argc >= 3) { | |
| int rc = bex_engine_install(engine, argv[2]); | |
| if (rc != 0) { | |
| CStrGuard err(bex_engine_last_error(engine)); | |
| throw std::runtime_error("Install failed: " + err.str()); | |
| } | |
| std::cout << "Plugin installed from: " << argv[2] << std::endl; | |
| } | |
| else if (cmd == "uninstall" && argc >= 3) { | |
| int rc = bex_engine_uninstall(engine, argv[2]); | |
| if (rc != 0) { | |
| CStrGuard err(bex_engine_last_error(engine)); | |
| throw std::runtime_error("Uninstall failed: " + err.str()); | |
| } | |
| std::cout << "Plugin uninstalled: " << argv[2] << std::endl; | |
| } | |
| else if (cmd == "list") { | |
| PluginListGuard list(bex_engine_list_plugins(engine)); | |
| if (list.size() == 0) { | |
| std::cout << "No plugins installed." << std::endl; | |
| } else { | |
| std::cout << std::left | |
| << std::setw(40) << "ID" | |
| << std::setw(20) << "NAME" | |
| << std::setw(10) << "VERSION" | |
| << std::setw(10) << "STATUS" | |
| << "CAPABILITIES" << std::endl; | |
| std::cout << std::string(100, '-') << std::endl; | |
| for (size_t i = 0; i < list.size(); i++) { | |
| auto& p = list.list.items[i]; | |
| std::cout << std::left | |
| << std::setw(40) << (p.id ? p.id : "") | |
| << std::setw(20) << (p.name ? p.name : "") | |
| << std::setw(10) << (p.version ? p.version : "") | |
| << std::setw(10) << (p.enabled ? "enabled" : "disabled") | |
| << capabilities_str(p.capabilities) << std::endl; | |
| } | |
| } | |
| } | |
| else if (cmd == "info-plugin" && argc >= 3) { | |
| BexPluginInfo info; | |
| int rc = bex_engine_plugin_info(engine, argv[2], &info); | |
| if (rc != 0) { | |
| CStrGuard err(bex_engine_last_error(engine)); | |
| throw std::runtime_error("Plugin info failed: " + err.str()); | |
| } | |
| std::cout << "ID: " << (info.id ? info.id : "") << std::endl; | |
| std::cout << "Name: " << (info.name ? info.name : "") << std::endl; | |
| std::cout << "Version: " << (info.version ? info.version : "") << std::endl; | |
| std::cout << "Enabled: " << (info.enabled ? "yes" : "no") << std::endl; | |
| std::cout << "Capabilities: " << capabilities_str(info.capabilities) << std::endl; | |
| // Show API keys for this plugin | |
| CStrGuard keys(bex_engine_secret_keys(engine, argv[2])); | |
| if (keys.ptr && strlen(keys.ptr) > 0) { | |
| std::cout << "API Keys: " << keys.str() << std::endl; | |
| } else { | |
| std::cout << "API Keys: (none)" << std::endl; | |
| } | |
| bex_plugin_info_free(info); | |
| } | |
| else if (cmd == "enable" && argc >= 3) { | |
| int rc = bex_engine_enable(engine, argv[2]); | |
| if (rc != 0) { | |
| CStrGuard err(bex_engine_last_error(engine)); | |
| throw std::runtime_error("Enable failed: " + err.str()); | |
| } | |
| std::cout << "Enabled: " << argv[2] << std::endl; | |
| } | |
| else if (cmd == "disable" && argc >= 3) { | |
| int rc = bex_engine_disable(engine, argv[2]); | |
| if (rc != 0) { | |
| CStrGuard err(bex_engine_last_error(engine)); | |
| throw std::runtime_error("Disable failed: " + err.str()); | |
| } | |
| std::cout << "Disabled: " << argv[2] << std::endl; | |
| } | |
| // ββ API Key / Secret Management βββββββββββββββββββββββββββ | |
| else if (cmd == "set-key" && argc >= 5) { | |
| int rc = bex_engine_secret_set(engine, argv[2], argv[3], argv[4]); | |
| if (rc != 0) { | |
| CStrGuard err(bex_engine_last_error(engine)); | |
| throw std::runtime_error("Set key failed: " + err.str()); | |
| } | |
| std::cout << "Key '" << argv[3] << "' set for plugin '" << argv[2] << "'" << std::endl; | |
| } | |
| else if (cmd == "get-key" && argc >= 4) { | |
| char buf[4096]; | |
| size_t buf_len = sizeof(buf); | |
| int rc = bex_engine_secret_get(engine, argv[2], argv[3], buf, &buf_len); | |
| if (rc == 0) { | |
| std::cout << buf << std::endl; | |
| } else { | |
| std::cout << "Key '" << argv[3] << "' not found for plugin '" << argv[2] << "'" << std::endl; | |
| } | |
| } | |
| else if (cmd == "delete-key" && argc >= 4) { | |
| int rc = bex_engine_secret_delete(engine, argv[2], argv[3]); | |
| if (rc == 0) { | |
| std::cout << "Key '" << argv[3] << "' deleted from plugin '" << argv[2] << "'" << std::endl; | |
| } else if (rc == 1) { | |
| std::cout << "Key '" << argv[3] << "' not found for plugin '" << argv[2] << "'" << std::endl; | |
| } else { | |
| CStrGuard err(bex_engine_last_error(engine)); | |
| throw std::runtime_error("Delete key failed: " + err.str()); | |
| } | |
| } | |
| else if (cmd == "list-keys" && argc >= 3) { | |
| CStrGuard keys(bex_engine_secret_keys(engine, argv[2])); | |
| if (keys.ptr && strlen(keys.ptr) > 0) { | |
| std::cout << "Keys for plugin '" << argv[2] << "': " << keys.str() << std::endl; | |
| } else { | |
| std::cout << "No keys found for plugin '" << argv[2] << "'" << std::endl; | |
| } | |
| } | |
| // ββ Media Browsing (async with callbacks) ββββββββββββββββ | |
| else if (cmd == "home" && argc >= 3) { | |
| std::promise<std::string> promise; | |
| uint64_t req_id = bex_submit_home(engine, argv[2], on_bex_result, &promise); | |
| std::string result = await_request(engine, req_id, promise); | |
| print_json(result); | |
| } | |
| else if (cmd == "search" && argc >= 4) { | |
| std::promise<std::string> promise; | |
| uint64_t req_id = bex_submit_search(engine, argv[2], argv[3], on_bex_result, &promise); | |
| std::string result = await_request(engine, req_id, promise); | |
| print_json(result); | |
| } | |
| else if (cmd == "info" && argc >= 4) { | |
| std::promise<std::string> promise; | |
| uint64_t req_id = bex_submit_info(engine, argv[2], argv[3], on_bex_result, &promise); | |
| std::string result = await_request(engine, req_id, promise); | |
| print_json(result); | |
| } | |
| else if (cmd == "servers" && argc >= 4) { | |
| // The ID is self-describing β the plugin knows how to parse its own IDs. | |
| // Example: bexcli servers com.gogoanime 'one-piece$ep=1$sub=1$dub=0' | |
| std::promise<std::string> promise; | |
| uint64_t req_id = bex_submit_servers(engine, argv[2], argv[3], on_bex_result, &promise); | |
| std::string result = await_request(engine, req_id, promise); | |
| print_json(result); | |
| } | |
| else if (cmd == "stream" && argc >= 4) { | |
| std::promise<std::string> promise; | |
| uint64_t req_id = bex_submit_stream(engine, argv[2], argv[3], on_bex_result, &promise); | |
| std::string result = await_request(engine, req_id, promise); | |
| print_json(result); | |
| } | |
| // ββ Debug βββββββββββββββββββββββββββββββββββββββββββββββββ | |
| else if (cmd == "stats") { | |
| CStrGuard stats(bex_engine_stats(engine)); | |
| if (stats.ptr) { | |
| print_json(stats.str()); | |
| } else { | |
| std::cout << "Unable to get stats" << std::endl; | |
| } | |
| } | |
| else { | |
| print_usage(argv[0]); | |
| bex_engine_free(engine); | |
| return 1; | |
| } | |
| bex_engine_free(engine); | |
| } | |
| catch (const std::exception& e) { | |
| std::cerr << "Error: " << e.what() << std::endl; | |
| bex_engine_free(engine); | |
| return 1; | |
| } | |
| return 0; | |
| } | |