diff --git a/packages/kbot/cpp/CMakeLists.txt b/packages/kbot/cpp/CMakeLists.txt index b2c4db99..95958824 100644 --- a/packages/kbot/cpp/CMakeLists.txt +++ b/packages/kbot/cpp/CMakeLists.txt @@ -130,9 +130,14 @@ else() endif() # ── Install ────────────────────────────────────────────────────────────────── +# Library + headers: see packages/kbot/CMakeLists.txt and packages/ipc/CMakeLists.txt +# Optional DLL/so: configure with -DIPC_BUILD_SHARED=ON -DPOLYMECH_KBOT_SHARED=ON install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin ) +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/src/cmd_kbot.h + DESTINATION include/polymech +) # ── Tests ──────────────────────────────────────────────────────────────────── enable_testing() diff --git a/packages/kbot/cpp/CMakePresets.json b/packages/kbot/cpp/CMakePresets.json index d295b663..b8b380d4 100644 --- a/packages/kbot/cpp/CMakePresets.json +++ b/packages/kbot/cpp/CMakePresets.json @@ -21,6 +21,16 @@ "cacheVariables": { "CMAKE_BUILD_TYPE": "Release" } + }, + { + "name": "dev-dll", + "displayName": "Dev (Debug, ipc + kbot as DLL)", + "binaryDir": "${sourceDir}/build/dev-dll", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "IPC_BUILD_SHARED": "ON", + "POLYMECH_KBOT_SHARED": "ON" + } } ], "buildPresets": [ @@ -31,6 +41,10 @@ { "name": "release", "configurePreset": "release" + }, + { + "name": "dev-dll", + "configurePreset": "dev-dll" } ] } \ No newline at end of file diff --git a/packages/kbot/cpp/README.md b/packages/kbot/cpp/README.md index 2db5c2eb..f833ebcd 100644 --- a/packages/kbot/cpp/README.md +++ b/packages/kbot/cpp/README.md @@ -1,40 +1,251 @@ -# polymech-cli +# kbot (C++) -Cross-platform C++ CLI built with CMake. +CMake-based C++ toolchain for **kbot**: HTML/HTTP/JSON utilities, **length-prefixed JSON IPC**, optional **UDS/TCP worker** for Node orchestrators, and **LLM chat** via liboai (OpenRouter, OpenAI, Ollama-compatible servers, etc.). The main binary is **`kbot`** (`kbot.exe` on Windows). ## Prerequisites -| Tool | Version | -|------|---------| +| Requirement | Notes | +|-------------|--------| | CMake | ≥ 3.20 | -| C++ compiler | C++17 (MSVC, GCC, or Clang) | +| C++ compiler | C++17 (MSVC, GCC, Clang) | +| Git | For `FetchContent` dependencies | +| Node.js | Optional; for `orchestrator/` IPC integration tests (`npm run test:ipc`) | -## Build +On Windows, use a **Developer Command Prompt** or **PowerShell** with MSVC in `PATH`. **Git Bash** helps if you use shell scripts under `scripts/`. + +## Quick start (build) + +From this directory (`packages/kbot/cpp`): + +```bash +npm install # optional; only needed if you use npm scripts +npm run build +``` + +Artifacts go to **`dist/`** (e.g. `dist/kbot.exe`, test tools). + +Equivalent CMake: ```bash -# Debug cmake --preset dev cmake --build --preset dev - -# Release -cmake --preset release -cmake --build --preset release ``` -## Usage +### Presets + +| Preset | Role | +|--------|------| +| `dev` | Debug, static `ipc` + `kbot` libraries (default) | +| `release` | Release build | +| `dev-dll` | Debug with **`ipc.dll`** and **`kbot.dll`** (`IPC_BUILD_SHARED=ON`, `POLYMECH_KBOT_SHARED=ON`) | ```bash -polymech-cli --help -polymech-cli --version +cmake --preset dev-dll +cmake --build --preset dev-dll --config Debug ``` +Place **`ipc.dll`** and **`kbot.dll`** next to **`kbot.exe`** (or on `PATH`) when using the DLL configuration. + +### npm scripts (reference) + +| Script | Purpose | +|--------|---------| +| `npm run build` | Configure `dev` + build | +| `npm run build:release` | Release preset | +| `npm run test` | `ctest` in `build/dev` | +| `npm run clean` | Remove `build/` and `dist/` | +| `npm run test:ipc` | Node UDS IPC integration test | +| `npm run worker` | Run worker (stdio IPC) | + +## Installation + +Install the CLI and headers into a prefix (e.g. local tree or system root): + +```bash +cmake --install build/dev --prefix "C:/path/to/install" +``` + +This installs: + +- **`bin/kbot`** (runtime) +- **`include/polymech/`** — `kbot.h`, `llm_client.h`, `polymech_export.h`, `cmd_kbot.h` +- **`include/ipc/`** — `ipc.h`, `ipc_export.h` +- **`lib/`** — import libraries / archives (depending on static vs shared) + +Library layout is defined in `packages/kbot/CMakeLists.txt` and `packages/ipc/CMakeLists.txt`. + +### CMake options (libraries) + +| Cache variable | Effect | +|----------------|--------| +| `IPC_BUILD_SHARED` | Build **`ipc`** as a shared library (`OFF` default) | +| `POLYMECH_KBOT_SHARED` | Build **`kbot`** as a shared library (`OFF` default) | + +Static builds define `IPC_STATIC_BUILD` / `POLYMECH_STATIC_BUILD` for consumers via `INTERFACE` compile definitions. Shared builds export **`IPC_API`** / **`POLYMECH_API`** (see `ipc_export.h`, `polymech_export.h`). + +## CLI overview + +Top-level: + +```bash +kbot --help +kbot -v,--version +kbot --log-level debug|info|warn|error +``` + +### Subcommands + +| Command | Description | +|---------|-------------| +| `parse ` | Parse HTML and list elements | +| `select ` | CSS-select elements | +| `config ` | Load and print a TOML file | +| `fetch ` | HTTP GET | +| `json ` | Prettify JSON | +| `db [-c config] [table] [-l limit]` | Supabase / DB helper (uses `config/postgres.toml` by default) | +| `worker [--uds ]` | IPC worker (see below) | +| `kbot ai ...` / `kbot run ...` | AI and run pipelines (`setup_cmd_kbot` — use `kbot kbot ai --help`) | + +### Worker mode (`kbot worker`) + +Used by orchestrators and tests. + +- **Stdio IPC** (length-prefixed JSON frames on stdin/stdout): + + ```bash + kbot worker + ``` + +- **UDS / TCP** (Windows: TCP port string, e.g. `4001`; Unix: socket path): + + ```bash + kbot worker --uds 4001 + ``` + +Framing: `[uint32 LE length][UTF-8 JSON object with id, type, payload]`. Message types include `ping`, `job`, `kbot-ai`, `kbot-run`, `shutdown`, etc. See `src/main.cpp` and `orchestrator/test-ipc.mjs`. + +### `kbot kbot` (nested) + +CLI for AI tasks and run configurations: + +```bash +kbot kbot ai --help +kbot kbot run --help +``` + +Example: + +```bash +kbot kbot ai --prompt "Hello" --config config/postgres.toml +``` + +API keys are typically resolved from **`config/postgres.toml`** (`[services]`). + +## Using in other CMake projects + +There is no single `find_package(kbot)` config yet. Practical options: + +### 1. Same repository / superbuild (recommended) + +Add this repo’s `cpp` tree as a subdirectory from a parent `CMakeLists.txt` so `FetchContent` and internal targets (`logger`, `json`, `ipc`, `oai`, `kbot`, …) resolve once. Then: + +```cmake +target_link_libraries(your_app PRIVATE ipc kbot) +``` + +`kbot` pulls in `logger`, `json`, `liboai` (`oai`) per `packages/kbot/CMakeLists.txt`. + +### 2. Install prefix + explicit `IMPORTED` libraries + +After `cmake --install`, link import libraries under `lib/` and add `include/` for **`ipc`** and **`polymech`**. You must still satisfy **transitive** dependencies (`oai`, `logger`, `json`, …) from the **same** build/install of this project, or duplicate their build—usually easier to use option 1. + +### 3. Minimal example: IPC framing only + +If you only need **`ipc::encode` / `ipc::decode`** (and can build `logger` + `json` the same way this project does), mirror `packages/ipc/CMakeLists.txt`: + +```cmake +cmake_minimum_required(VERSION 3.20) +project(myapp CXX) +set(CMAKE_CXX_STANDARD 17) + +add_subdirectory(path/to/polymech-mono/packages/kbot/cpp/packages/logger) +add_subdirectory(path/to/polymech-mono/packages/kbot/cpp/packages/json) +add_subdirectory(path/to/polymech-mono/packages/kbot/cpp/packages/ipc) + +add_executable(myapp main.cpp) +target_link_libraries(myapp PRIVATE ipc) +``` + +**`main.cpp`** (stdio-style framing helpers): + +```cpp +#include +#include + +int main() { + ipc::Message msg{"1", "ping", "{}"}; + auto frame = ipc::encode(msg); + // frame: 4-byte LE length + JSON object bytes + + ipc::Message roundtrip; + if (frame.size() > 4 && + ipc::decode(frame.data() + 4, frame.size() - 4, roundtrip)) { + std::cout << roundtrip.type << "\n"; // ping + } + return 0; +} +``` + +### 4. Example: LLM pipeline API (`kbot` library) + +Headers: `kbot.h`, `llm_client.h`, `polymech_export.h`. You need a valid API key and options (see `KBotOptions` in `kbot.h`). + +```cpp +#include +#include "kbot.h" +#include "llm_client.h" + +int main() { + polymech::kbot::KBotOptions opts; + opts.prompt = "Say hello in one sentence."; + opts.api_key = "YOUR_KEY"; + opts.router = "openrouter"; + opts.model = "openai/gpt-4o-mini"; + + polymech::kbot::LLMClient client(opts); + polymech::kbot::LLMResponse r = client.execute_chat(opts.prompt); + if (r.success) { + std::cout << r.text << "\n"; + } else { + std::cerr << r.error << "\n"; + return 1; + } + return 0; +} +``` + +Or use the callback-based pipeline: + +```cpp +polymech::kbot::KBotCallbacks cb; +cb.onEvent = [](const std::string& type, const std::string& json) { + std::cout << type << ": " << json << "\n"; +}; +return polymech::kbot::run_kbot_ai_pipeline(opts, cb); +``` + +Link **`kbot`** (and its public dependencies). **`cmd_kbot.h`** entry points (`run_kbot_ai_ipc`, `run_cmd_kbot_uds`, …) are implemented in **`src/cmd_kbot*.cpp`** in this project; to reuse them, compile those sources into your binary or vendor the logic. + +## Node / IPC tests + +Integration tests live under **`orchestrator/`** (see comments in `orchestrator/test-ipc.mjs`). Typical run from `cpp/`: + +```bash +npm run test:ipc +``` + +Requires a built **`dist/kbot.exe`** (or `kbot` on Unix). ## License -BSD-3-Clause - -## Requirements - -- [https://github.com/taskflow/taskflow](https://github.com/taskflow/taskflow) -- [https://github.com/cameron314/concurrentqueue](https://github.com/cameron314/concurrentqueue) -- [https://github.com/chriskohlhoff/asio](https://github.com/chriskohlhoff/asio) +See [LICENSE](LICENSE) in this directory. diff --git a/packages/kbot/cpp/packages/ipc/CMakeLists.txt b/packages/kbot/cpp/packages/ipc/CMakeLists.txt index ec533971..e6e133a1 100644 --- a/packages/kbot/cpp/packages/ipc/CMakeLists.txt +++ b/packages/kbot/cpp/packages/ipc/CMakeLists.txt @@ -1,11 +1,45 @@ -add_library(ipc STATIC - src/ipc.cpp -) +cmake_minimum_required(VERSION 3.20) + +project(ipc CXX) + +option(IPC_BUILD_SHARED "Build ipc as a shared library (DLL/so)" OFF) + +set(_ipc_sources src/ipc.cpp) + +if(IPC_BUILD_SHARED) + add_library(ipc SHARED ${_ipc_sources}) + target_compile_definitions(ipc PRIVATE IPC_BUILDING_LIBRARY) +else() + add_library(ipc STATIC ${_ipc_sources}) + target_compile_definitions(ipc PRIVATE IPC_STATIC_BUILD=1) + target_compile_definitions(ipc INTERFACE IPC_STATIC_BUILD=1) +endif() target_include_directories(ipc - PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include ) target_link_libraries(ipc - PUBLIC json logger + PUBLIC json logger +) + +if(IPC_BUILD_SHARED) + set_target_properties(ipc PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/dist" + RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_SOURCE_DIR}/dist" + RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_SOURCE_DIR}/dist" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/dist" + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/dist" + ) +endif() + +install(TARGETS ipc + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin +) +install(FILES + ${CMAKE_CURRENT_SOURCE_DIR}/include/ipc/ipc.h + ${CMAKE_CURRENT_SOURCE_DIR}/include/ipc/ipc_export.h + DESTINATION include/ipc ) diff --git a/packages/kbot/cpp/packages/ipc/include/ipc/ipc.h b/packages/kbot/cpp/packages/ipc/include/ipc/ipc.h index d79a30f2..6577ddca 100644 --- a/packages/kbot/cpp/packages/ipc/include/ipc/ipc.h +++ b/packages/kbot/cpp/packages/ipc/include/ipc/ipc.h @@ -1,5 +1,6 @@ #pragma once +#include "ipc/ipc_export.h" #include #include #include @@ -16,19 +17,19 @@ struct Message { /// Encode a Message into a length-prefixed binary frame. /// Layout: [4-byte LE uint32 length][JSON bytes] -std::vector encode(const Message &msg); +IPC_API std::vector encode(const Message &msg); /// Decode a binary frame (without the 4-byte length prefix) into a Message. /// Returns false if the JSON is invalid or missing required fields. -bool decode(const uint8_t *data, size_t len, Message &out); -bool decode(const std::vector &frame, Message &out); +IPC_API bool decode(const uint8_t *data, size_t len, Message &out); +IPC_API bool decode(const std::vector &frame, Message &out); /// Blocking: read exactly one length-prefixed message from a FILE*. /// Returns false on EOF or read error. -bool read_message(Message &out, FILE *in = stdin); +IPC_API bool read_message(Message &out, FILE *in = stdin); /// Write one length-prefixed message to a FILE*. Flushes after write. /// Returns false on write error. -bool write_message(const Message &msg, FILE *out = stdout); +IPC_API bool write_message(const Message &msg, FILE *out = stdout); } // namespace ipc diff --git a/packages/kbot/cpp/packages/kbot/CMakeLists.txt b/packages/kbot/cpp/packages/kbot/CMakeLists.txt index b2c0cbb6..3d220f56 100644 --- a/packages/kbot/cpp/packages/kbot/CMakeLists.txt +++ b/packages/kbot/cpp/packages/kbot/CMakeLists.txt @@ -2,10 +2,18 @@ cmake_minimum_required(VERSION 3.20) project(kbot CXX) -add_library(kbot STATIC - kbot.cpp - llm_client.cpp -) +option(POLYMECH_KBOT_SHARED "Build kbot as a shared library (DLL/so)" OFF) + +set(_kbot_sources kbot.cpp llm_client.cpp) + +if(POLYMECH_KBOT_SHARED) + add_library(kbot SHARED ${_kbot_sources}) + target_compile_definitions(kbot PRIVATE POLYMECH_BUILDING_LIBRARY) +else() + add_library(kbot STATIC ${_kbot_sources}) + target_compile_definitions(kbot PRIVATE POLYMECH_STATIC_BUILD=1) + target_compile_definitions(kbot INTERFACE POLYMECH_STATIC_BUILD=1) +endif() target_include_directories(kbot PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} @@ -17,3 +25,25 @@ target_link_libraries(kbot PUBLIC json oai ) + +if(POLYMECH_KBOT_SHARED) + set_target_properties(kbot PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/dist" + RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_SOURCE_DIR}/dist" + RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_SOURCE_DIR}/dist" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/dist" + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/dist" + ) +endif() + +install(TARGETS kbot + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin +) +install(FILES + ${CMAKE_CURRENT_SOURCE_DIR}/kbot.h + ${CMAKE_CURRENT_SOURCE_DIR}/llm_client.h + ${CMAKE_CURRENT_SOURCE_DIR}/polymech_export.h + DESTINATION include/polymech +) diff --git a/packages/kbot/cpp/packages/kbot/kbot.h b/packages/kbot/cpp/packages/kbot/kbot.h index 6d78374a..dfa36566 100644 --- a/packages/kbot/cpp/packages/kbot/kbot.h +++ b/packages/kbot/cpp/packages/kbot/kbot.h @@ -1,5 +1,6 @@ #pragma once +#include "polymech_export.h" #include #include #include @@ -65,8 +66,8 @@ struct KBotCallbacks { std::function onEvent; }; -int run_kbot_ai_pipeline(const KBotOptions& opts, const KBotCallbacks& cb); -int run_kbot_run_pipeline(const KBotRunOptions& opts, const KBotCallbacks& cb); +POLYMECH_API int run_kbot_ai_pipeline(const KBotOptions& opts, const KBotCallbacks& cb); +POLYMECH_API int run_kbot_run_pipeline(const KBotRunOptions& opts, const KBotCallbacks& cb); } // namespace kbot } // namespace polymech diff --git a/packages/kbot/cpp/packages/kbot/llm_client.h b/packages/kbot/cpp/packages/kbot/llm_client.h index 96c39102..59aa9111 100644 --- a/packages/kbot/cpp/packages/kbot/llm_client.h +++ b/packages/kbot/cpp/packages/kbot/llm_client.h @@ -14,7 +14,7 @@ struct LLMResponse { std::string provider_meta_json; }; -class LLMClient { +class POLYMECH_API LLMClient { public: // Initialize the client with the options (api_key, model, router). explicit LLMClient(const KBotOptions& opts); diff --git a/packages/kbot/cpp/src/cmd_kbot.h b/packages/kbot/cpp/src/cmd_kbot.h index ef8d8f39..32c60708 100644 --- a/packages/kbot/cpp/src/cmd_kbot.h +++ b/packages/kbot/cpp/src/cmd_kbot.h @@ -14,7 +14,7 @@ CLI::App* setup_cmd_kbot(CLI::App& app); int run_cmd_kbot_ai(); int run_cmd_kbot_run(); -/// IPC / UDS Entry points +/// IPC / UDS Entry points (implemented in src/cmd_kbot*.cpp — not in libkbot; compile those TU into your binary). int run_kbot_ai_ipc(const std::string& payload, const std::string& jobId, const kbot::KBotCallbacks& cb); int run_kbot_run_ipc(const std::string& payload, const std::string& jobId, const kbot::KBotCallbacks& cb);