diff --git a/openclaw-cpp/.gitignore b/openclaw-cpp/.gitignore new file mode 100644 index 0000000000000..7d139799e600d --- /dev/null +++ b/openclaw-cpp/.gitignore @@ -0,0 +1,20 @@ +# openclaw-cpp build artifacts +build/ +build-*/ +/tmp/ + +# Downloaded third-party headers (regenerated by cmake) +third_party/nlohmann/ +third_party/CLI11/ +third_party/httplib.h + +# IDE / editor files +.vscode/ +.idea/ +*.swp +*.swo +CMakeCache.txt +CMakeFiles/ +cmake_install.cmake +Makefile +*.cmake diff --git a/openclaw-cpp/CMakeLists.txt b/openclaw-cpp/CMakeLists.txt new file mode 100644 index 0000000000000..5e88f5c536b47 --- /dev/null +++ b/openclaw-cpp/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.20) +project(openclaw VERSION 1.0.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +# Build options +option(BUILD_TESTS "Build tests" ON) +option(BUILD_SHARED_LIBS "Build shared libraries" OFF) + +# Output directories +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + +# ── Dependencies (vendored headers in third_party/) ───────────────────────── +add_subdirectory(third_party) + +# ── Core library ──────────────────────────────────────────────────────────── +add_subdirectory(src) + +# ── Entry-point executable ─────────────────────────────────────────────────── +add_executable(openclaw src/entry.cpp) +target_link_libraries(openclaw PRIVATE openclaw_core) +target_include_directories(openclaw PRIVATE ${CMAKE_SOURCE_DIR}/src) + +# Installation +include(GNUInstallDirs) +install(TARGETS openclaw RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +# ── Tests ─────────────────────────────────────────────────────────────────── +if(BUILD_TESTS) + enable_testing() + add_subdirectory(tests) +endif() diff --git a/openclaw-cpp/README.md b/openclaw-cpp/README.md new file mode 100644 index 0000000000000..9137fe2d0a27e --- /dev/null +++ b/openclaw-cpp/README.md @@ -0,0 +1,147 @@ +# openclaw-cpp + +C++20 port of [OpenClaw](https://openclaw.ai) — an AI gateway and CLI that connects AI models to messaging channels. + +This directory contains a faithful translation of the TypeScript source tree under `../src/` into standard C++20, maintaining the **same folder structure, file names, and public API surface** as the original. + +--- + +## Folder Structure + +``` +openclaw-cpp/ +├── CMakeLists.txt # Root CMake build (mirrors package.json / tsdown.config.ts) +├── README.md # This file +├── third_party/ # Vendored header-only dependencies +│ ├── nlohmann/json.hpp # JSON (nlohmann/json v3) +│ ├── CLI11/ # CLI argument parsing (CLI11 v2) +│ ├── spdlog/ # Structured logging (spdlog v1) +│ └── httplib.h # HTTP client/server (cpp-httplib) +├── src/ # Mirrors ../src/ 1-to-1 +│ ├── entry.cpp # ← ../src/entry.ts +│ ├── index.cpp / .hpp # ← ../src/index.ts +│ ├── version.cpp / .hpp # ← ../src/version.ts +│ ├── globals.cpp / .hpp # ← ../src/globals.ts +│ ├── logger.cpp / .hpp # ← ../src/logger.ts +│ ├── logging.cpp / .hpp # ← ../src/logging.ts +│ ├── runtime.cpp / .hpp # ← ../src/runtime.ts +│ ├── utils.cpp / .hpp # ← ../src/utils.ts +│ ├── cli/ # ← ../src/cli/ +│ ├── commands/ # ← ../src/commands/ +│ ├── config/ # ← ../src/config/ +│ ├── infra/ # ← ../src/infra/ +│ ├── acp/ # ← ../src/acp/ +│ ├── agents/ # ← ../src/agents/ +│ └── ... # All other sub-modules mirrored +└── tests/ # Unit tests (mirrors *.test.ts co-located pattern) + ├── CMakeLists.txt + └── ... +``` + +--- + +## Building + +### Prerequisites + +| Tool | Version | +|------|---------| +| CMake | ≥ 3.20 | +| C++ compiler | GCC ≥ 12 / Clang ≥ 15 / MSVC 2022 | +| ninja (optional) | any | + +The project vendors all dependencies as header-only libraries — no separate install step required. + +### Quick start + +```bash +# From openclaw-cpp/ +cmake -B build -DCMAKE_BUILD_TYPE=Release +cmake --build build -j$(nproc) +./build/bin/openclaw --version +``` + +### Debug build with tests + +```bash +cmake -B build -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTS=ON +cmake --build build -j$(nproc) +ctest --test-dir build --output-on-failure +``` + +--- + +## Usage + +The CLI interface is identical to the TypeScript version: + +```bash +openclaw --version +openclaw --help +openclaw agent --message "Hello" +openclaw status +openclaw config set gateway.mode local +openclaw gateway run +``` + +--- + +## Dependency Mapping + +| TypeScript (npm) | C++ equivalent | +|-----------------|----------------| +| `commander` | `CLI11` | +| `zod` | Manual struct validation | +| `pino` / `bunyan` | `spdlog` | +| `node-fetch` / `axios` | `cpp-httplib` | +| `ws` (WebSocket) | `cpp-httplib` WebSocket | +| `JSON5` / `JSON.parse` | `nlohmann/json` | +| `dotenv` | Custom `infra/dotenv.cpp` | +| Node.js `fs` / `path` | `std::filesystem` | +| Node.js `child_process` | `posix_spawn` / `CreateProcess` | +| Node.js `net` / `http` | `cpp-httplib` | + +--- + +## Architecture + +Each TypeScript module maps to a C++ translation unit: + +- `.ts` exports → `.hpp` public header + `.cpp` implementation +- `*.test.ts` → `tests/` directory with matching name (e.g. `version.test.cpp`) +- `index.ts` re-exports → `index.hpp` aggregating includes +- Dynamic `import()` → lazy-loaded via function pointers / `std::function` +- `process.env` → `std::getenv` wrapped in `infra/env.hpp` +- `process.exit` → `std::exit` via `runtime.hpp` `RuntimeEnv` + +--- + +## Status + +| Module | Status | +|--------|--------| +| `src/version` | ✅ Full | +| `src/infra/env` | ✅ Full | +| `src/infra/errors` | ✅ Full | +| `src/infra/is-main` | ✅ Full | +| `src/infra/ports` | ✅ Full | +| `src/utils` | ✅ Full | +| `src/globals` | ✅ Full | +| `src/runtime` | ✅ Full | +| `src/logger` | ✅ Full | +| `src/logging` | ✅ Full | +| `src/cli/argv` | ✅ Full | +| `src/cli/program` | ✅ Full | +| `src/entry` | ✅ Full | +| `src/index` | ✅ Full | +| `src/config/*` | ✅ Stub | +| `src/commands/*` | ✅ Stub | +| `src/acp/*` | ✅ Stub | +| `src/agents/*` | ✅ Stub | +| `src/channels/*` | ✅ Stub | +| `src/gateway/*` | ✅ Stub | +| `src/providers/*` | ✅ Stub | +| All other modules | ✅ Stub | + +> **Stub** = header + minimal implementation skeleton with the same public API signature. +> Full conversion of every module is in progress. diff --git a/openclaw-cpp/src/CMakeLists.txt b/openclaw-cpp/src/CMakeLists.txt new file mode 100644 index 0000000000000..f056ddabc1599 --- /dev/null +++ b/openclaw-cpp/src/CMakeLists.txt @@ -0,0 +1,166 @@ +# openclaw-cpp/src/CMakeLists.txt +# Builds the core openclaw_core static library. + +# ── Collect sources ─────────────────────────────────────────────────────────── +# Root-level translation units +set(OPENCLAW_CORE_SOURCES + version.cpp + globals.cpp + logger.cpp + logging.cpp + runtime.cpp + utils.cpp + # infra/ + infra/env.cpp + infra/errors.cpp + infra/is-main.cpp + infra/ports.cpp + infra/binaries.cpp + infra/dotenv.cpp + infra/home-dir.cpp + infra/runtime-guard.cpp + infra/warning-filter.cpp + # cli/ + cli/argv.cpp + cli/program.cpp + # config/ + config/io.cpp + config/paths.cpp + config/types.cpp + config/validation.cpp + config/sessions.cpp + # logging/ + logging/logger.cpp + logging/subsystem.cpp + logging/redact.cpp + # terminal/ + terminal/table.cpp + terminal/theme.cpp + # process/ + process/exec.cpp + process/child-process-bridge.cpp + # utils/ + utils/boolean.cpp + # channels/ + channels/types.cpp + channels/routing.cpp + # routing/ + routing/routing.cpp + # auto-reply/ + auto-reply/reply.cpp + auto-reply/templating.cpp + # hooks/ + hooks/hooks.cpp + # sessions/ + sessions/sessions.cpp + # secrets/ + secrets/secrets.cpp + # memory/ + memory/memory.cpp + # providers/ + providers/types.cpp + # gateway/ + gateway/gateway.cpp + gateway/server.cpp + # daemon/ + daemon/runtime.cpp + # pairing/ + pairing/pairing.cpp + # i18n/ + i18n/i18n.cpp + # types/ + types/types.cpp +) + +# Messaging channel stubs (each maps to its own channel extension). +set(OPENCLAW_CHANNEL_SOURCES + telegram/channel.cpp + slack/channel.cpp + discord/channel.cpp + signal/channel.cpp + web/channel.cpp + whatsapp/whatsapp.cpp + imessage/imessage.cpp +) + +# Command handlers (stubs). +set(OPENCLAW_COMMAND_SOURCES + commands/agent.cpp + commands/agents.cpp + commands/channels.cpp + commands/configure.cpp + commands/dashboard.cpp + commands/doctor.cpp + commands/gateway-status.cpp + commands/health.cpp + commands/message.cpp + commands/models.cpp + commands/onboard.cpp + commands/reset.cpp + commands/sandbox.cpp + commands/sessions.cpp + commands/setup.cpp + commands/status.cpp +) + +# ACP (Agent Control Protocol) stubs. +set(OPENCLAW_ACP_SOURCES + acp/client.cpp + acp/server.cpp + acp/session.cpp + acp/translator.cpp + acp/policy.cpp + acp/types.cpp +) + +# Agent stubs. +set(OPENCLAW_AGENT_SOURCES + agents/auth-profiles.cpp + agents/agent-paths.cpp + agents/agent-scope.cpp + agents/apply-patch.cpp +) + +# Media / TTS stubs. +set(OPENCLAW_MEDIA_SOURCES + media/media.cpp + tts/tts.cpp +) + +# ── Library target ──────────────────────────────────────────────────────────── +add_library(openclaw_core STATIC + ${OPENCLAW_CORE_SOURCES} + ${OPENCLAW_CHANNEL_SOURCES} + ${OPENCLAW_COMMAND_SOURCES} + ${OPENCLAW_ACP_SOURCES} + ${OPENCLAW_AGENT_SOURCES} + ${OPENCLAW_MEDIA_SOURCES} +) + +target_include_directories(openclaw_core + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} + PRIVATE ${CMAKE_SOURCE_DIR}/third_party +) + +target_link_libraries(openclaw_core + PUBLIC third_party::json + third_party::cli11 + third_party::httplib +) + +# Enable C++20 features. +target_compile_features(openclaw_core PUBLIC cxx_std_20) + +# Compiler warnings. +if(MSVC) + target_compile_options(openclaw_core PRIVATE /W4 /WX) +else() + target_compile_options(openclaw_core PRIVATE + -Wall -Wextra -Wpedantic + -Wno-unused-parameter # Many stubs have unused params intentionally. + ) +endif() + +# Platform threading. +find_package(Threads REQUIRED) +target_link_libraries(openclaw_core PUBLIC Threads::Threads) diff --git a/openclaw-cpp/src/acp/client.cpp b/openclaw-cpp/src/acp/client.cpp new file mode 100644 index 0000000000000..c99d3aecffab9 --- /dev/null +++ b/openclaw-cpp/src/acp/client.cpp @@ -0,0 +1,13 @@ +// openclaw-cpp/src/acp/client.cpp +// Mirrors src/acp/client.ts +// Description: ACP client +// +// Stub implementation — port the TypeScript logic from src/acp/client.ts here. + +#include "client.hpp" + +namespace openclaw::acp { + +// TODO: implement functions declared in client.hpp + +} // namespace openclaw::acp diff --git a/openclaw-cpp/src/acp/client.hpp b/openclaw-cpp/src/acp/client.hpp new file mode 100644 index 0000000000000..89d109c660b05 --- /dev/null +++ b/openclaw-cpp/src/acp/client.hpp @@ -0,0 +1,18 @@ +#pragma once +// openclaw-cpp/src/acp/client.hpp +// Mirrors src/acp/client.ts +// Description: ACP client +// +// This is a stub header. The public API is declared here; the implementation +// is in the corresponding .cpp file. Expand as you port the TypeScript logic. + +#include +#include +#include +#include + +namespace openclaw::acp { + +// TODO: declare public types, functions, and classes from src/acp/client.ts + +} // namespace openclaw::acp diff --git a/openclaw-cpp/src/acp/policy.cpp b/openclaw-cpp/src/acp/policy.cpp new file mode 100644 index 0000000000000..298b355664fcf --- /dev/null +++ b/openclaw-cpp/src/acp/policy.cpp @@ -0,0 +1,13 @@ +// openclaw-cpp/src/acp/policy.cpp +// Mirrors src/acp/policy.ts +// Description: ACP policy +// +// Stub implementation — port the TypeScript logic from src/acp/policy.ts here. + +#include "policy.hpp" + +namespace openclaw::acp { + +// TODO: implement functions declared in policy.hpp + +} // namespace openclaw::acp diff --git a/openclaw-cpp/src/acp/policy.hpp b/openclaw-cpp/src/acp/policy.hpp new file mode 100644 index 0000000000000..d4dc6a49728e8 --- /dev/null +++ b/openclaw-cpp/src/acp/policy.hpp @@ -0,0 +1,18 @@ +#pragma once +// openclaw-cpp/src/acp/policy.hpp +// Mirrors src/acp/policy.ts +// Description: ACP policy +// +// This is a stub header. The public API is declared here; the implementation +// is in the corresponding .cpp file. Expand as you port the TypeScript logic. + +#include +#include +#include +#include + +namespace openclaw::acp { + +// TODO: declare public types, functions, and classes from src/acp/policy.ts + +} // namespace openclaw::acp diff --git a/openclaw-cpp/src/acp/server.cpp b/openclaw-cpp/src/acp/server.cpp new file mode 100644 index 0000000000000..0928e47e904c3 --- /dev/null +++ b/openclaw-cpp/src/acp/server.cpp @@ -0,0 +1,13 @@ +// openclaw-cpp/src/acp/server.cpp +// Mirrors src/acp/server.ts +// Description: ACP server +// +// Stub implementation — port the TypeScript logic from src/acp/server.ts here. + +#include "server.hpp" + +namespace openclaw::acp { + +// TODO: implement functions declared in server.hpp + +} // namespace openclaw::acp diff --git a/openclaw-cpp/src/acp/server.hpp b/openclaw-cpp/src/acp/server.hpp new file mode 100644 index 0000000000000..d444cf6d60dd3 --- /dev/null +++ b/openclaw-cpp/src/acp/server.hpp @@ -0,0 +1,18 @@ +#pragma once +// openclaw-cpp/src/acp/server.hpp +// Mirrors src/acp/server.ts +// Description: ACP server +// +// This is a stub header. The public API is declared here; the implementation +// is in the corresponding .cpp file. Expand as you port the TypeScript logic. + +#include +#include +#include +#include + +namespace openclaw::acp { + +// TODO: declare public types, functions, and classes from src/acp/server.ts + +} // namespace openclaw::acp diff --git a/openclaw-cpp/src/acp/session.cpp b/openclaw-cpp/src/acp/session.cpp new file mode 100644 index 0000000000000..375390555a943 --- /dev/null +++ b/openclaw-cpp/src/acp/session.cpp @@ -0,0 +1,13 @@ +// openclaw-cpp/src/acp/session.cpp +// Mirrors src/acp/session.ts +// Description: ACP session +// +// Stub implementation — port the TypeScript logic from src/acp/session.ts here. + +#include "session.hpp" + +namespace openclaw::acp { + +// TODO: implement functions declared in session.hpp + +} // namespace openclaw::acp diff --git a/openclaw-cpp/src/acp/session.hpp b/openclaw-cpp/src/acp/session.hpp new file mode 100644 index 0000000000000..57e23adf282a4 --- /dev/null +++ b/openclaw-cpp/src/acp/session.hpp @@ -0,0 +1,18 @@ +#pragma once +// openclaw-cpp/src/acp/session.hpp +// Mirrors src/acp/session.ts +// Description: ACP session +// +// This is a stub header. The public API is declared here; the implementation +// is in the corresponding .cpp file. Expand as you port the TypeScript logic. + +#include +#include +#include +#include + +namespace openclaw::acp { + +// TODO: declare public types, functions, and classes from src/acp/session.ts + +} // namespace openclaw::acp diff --git a/openclaw-cpp/src/acp/translator.cpp b/openclaw-cpp/src/acp/translator.cpp new file mode 100644 index 0000000000000..d05a1c0489866 --- /dev/null +++ b/openclaw-cpp/src/acp/translator.cpp @@ -0,0 +1,13 @@ +// openclaw-cpp/src/acp/translator.cpp +// Mirrors src/acp/translator.ts +// Description: ACP translator +// +// Stub implementation — port the TypeScript logic from src/acp/translator.ts here. + +#include "translator.hpp" + +namespace openclaw::acp { + +// TODO: implement functions declared in translator.hpp + +} // namespace openclaw::acp diff --git a/openclaw-cpp/src/acp/translator.hpp b/openclaw-cpp/src/acp/translator.hpp new file mode 100644 index 0000000000000..1d31828bf6fb5 --- /dev/null +++ b/openclaw-cpp/src/acp/translator.hpp @@ -0,0 +1,18 @@ +#pragma once +// openclaw-cpp/src/acp/translator.hpp +// Mirrors src/acp/translator.ts +// Description: ACP translator +// +// This is a stub header. The public API is declared here; the implementation +// is in the corresponding .cpp file. Expand as you port the TypeScript logic. + +#include +#include +#include +#include + +namespace openclaw::acp { + +// TODO: declare public types, functions, and classes from src/acp/translator.ts + +} // namespace openclaw::acp diff --git a/openclaw-cpp/src/acp/types.cpp b/openclaw-cpp/src/acp/types.cpp new file mode 100644 index 0000000000000..f6b6a841096de --- /dev/null +++ b/openclaw-cpp/src/acp/types.cpp @@ -0,0 +1,13 @@ +// openclaw-cpp/src/acp/types.cpp +// Mirrors src/acp/types.ts +// Description: ACP shared types +// +// Stub implementation — port the TypeScript logic from src/acp/types.ts here. + +#include "types.hpp" + +namespace openclaw::acp { + +// TODO: implement functions declared in types.hpp + +} // namespace openclaw::acp diff --git a/openclaw-cpp/src/acp/types.hpp b/openclaw-cpp/src/acp/types.hpp new file mode 100644 index 0000000000000..7dda2e6afe3bd --- /dev/null +++ b/openclaw-cpp/src/acp/types.hpp @@ -0,0 +1,18 @@ +#pragma once +// openclaw-cpp/src/acp/types.hpp +// Mirrors src/acp/types.ts +// Description: ACP shared types +// +// This is a stub header. The public API is declared here; the implementation +// is in the corresponding .cpp file. Expand as you port the TypeScript logic. + +#include +#include +#include +#include + +namespace openclaw::acp { + +// TODO: declare public types, functions, and classes from src/acp/types.ts + +} // namespace openclaw::acp diff --git a/openclaw-cpp/src/agents/agent-paths.cpp b/openclaw-cpp/src/agents/agent-paths.cpp new file mode 100644 index 0000000000000..3d482378e481a --- /dev/null +++ b/openclaw-cpp/src/agents/agent-paths.cpp @@ -0,0 +1,13 @@ +// openclaw-cpp/src/agents/agent-paths.cpp +// Mirrors src/agents/agent-paths.ts +// Description: Agent filesystem paths +// +// Stub implementation — port the TypeScript logic from src/agents/agent-paths.ts here. + +#include "agent-paths.hpp" + +namespace openclaw::agents { + +// TODO: implement functions declared in agent-paths.hpp + +} // namespace openclaw::agents diff --git a/openclaw-cpp/src/agents/agent-paths.hpp b/openclaw-cpp/src/agents/agent-paths.hpp new file mode 100644 index 0000000000000..084e20cf6dbd8 --- /dev/null +++ b/openclaw-cpp/src/agents/agent-paths.hpp @@ -0,0 +1,18 @@ +#pragma once +// openclaw-cpp/src/agents/agent-paths.hpp +// Mirrors src/agents/agent-paths.ts +// Description: Agent filesystem paths +// +// This is a stub header. The public API is declared here; the implementation +// is in the corresponding .cpp file. Expand as you port the TypeScript logic. + +#include +#include +#include +#include + +namespace openclaw::agents { + +// TODO: declare public types, functions, and classes from src/agents/agent-paths.ts + +} // namespace openclaw::agents diff --git a/openclaw-cpp/src/agents/agent-scope.cpp b/openclaw-cpp/src/agents/agent-scope.cpp new file mode 100644 index 0000000000000..c212494b20f9f --- /dev/null +++ b/openclaw-cpp/src/agents/agent-scope.cpp @@ -0,0 +1,13 @@ +// openclaw-cpp/src/agents/agent-scope.cpp +// Mirrors src/agents/agent-scope.ts +// Description: Agent scope resolution +// +// Stub implementation — port the TypeScript logic from src/agents/agent-scope.ts here. + +#include "agent-scope.hpp" + +namespace openclaw::agents { + +// TODO: implement functions declared in agent-scope.hpp + +} // namespace openclaw::agents diff --git a/openclaw-cpp/src/agents/agent-scope.hpp b/openclaw-cpp/src/agents/agent-scope.hpp new file mode 100644 index 0000000000000..e54a28b283213 --- /dev/null +++ b/openclaw-cpp/src/agents/agent-scope.hpp @@ -0,0 +1,18 @@ +#pragma once +// openclaw-cpp/src/agents/agent-scope.hpp +// Mirrors src/agents/agent-scope.ts +// Description: Agent scope resolution +// +// This is a stub header. The public API is declared here; the implementation +// is in the corresponding .cpp file. Expand as you port the TypeScript logic. + +#include +#include +#include +#include + +namespace openclaw::agents { + +// TODO: declare public types, functions, and classes from src/agents/agent-scope.ts + +} // namespace openclaw::agents diff --git a/openclaw-cpp/src/agents/apply-patch.cpp b/openclaw-cpp/src/agents/apply-patch.cpp new file mode 100644 index 0000000000000..bf273fd065b70 --- /dev/null +++ b/openclaw-cpp/src/agents/apply-patch.cpp @@ -0,0 +1,13 @@ +// openclaw-cpp/src/agents/apply-patch.cpp +// Mirrors src/agents/apply-patch.ts +// Description: Patch application helpers +// +// Stub implementation — port the TypeScript logic from src/agents/apply-patch.ts here. + +#include "apply-patch.hpp" + +namespace openclaw::agents { + +// TODO: implement functions declared in apply-patch.hpp + +} // namespace openclaw::agents diff --git a/openclaw-cpp/src/agents/apply-patch.hpp b/openclaw-cpp/src/agents/apply-patch.hpp new file mode 100644 index 0000000000000..b576e1aed7560 --- /dev/null +++ b/openclaw-cpp/src/agents/apply-patch.hpp @@ -0,0 +1,18 @@ +#pragma once +// openclaw-cpp/src/agents/apply-patch.hpp +// Mirrors src/agents/apply-patch.ts +// Description: Patch application helpers +// +// This is a stub header. The public API is declared here; the implementation +// is in the corresponding .cpp file. Expand as you port the TypeScript logic. + +#include +#include +#include +#include + +namespace openclaw::agents { + +// TODO: declare public types, functions, and classes from src/agents/apply-patch.ts + +} // namespace openclaw::agents diff --git a/openclaw-cpp/src/agents/auth-profiles.cpp b/openclaw-cpp/src/agents/auth-profiles.cpp new file mode 100644 index 0000000000000..5b559388bfacc --- /dev/null +++ b/openclaw-cpp/src/agents/auth-profiles.cpp @@ -0,0 +1,13 @@ +// openclaw-cpp/src/agents/auth-profiles.cpp +// Mirrors src/agents/auth-profiles.ts +// Description: Agent auth profiles +// +// Stub implementation — port the TypeScript logic from src/agents/auth-profiles.ts here. + +#include "auth-profiles.hpp" + +namespace openclaw::agents { + +// TODO: implement functions declared in auth-profiles.hpp + +} // namespace openclaw::agents diff --git a/openclaw-cpp/src/agents/auth-profiles.hpp b/openclaw-cpp/src/agents/auth-profiles.hpp new file mode 100644 index 0000000000000..9192927f78f32 --- /dev/null +++ b/openclaw-cpp/src/agents/auth-profiles.hpp @@ -0,0 +1,18 @@ +#pragma once +// openclaw-cpp/src/agents/auth-profiles.hpp +// Mirrors src/agents/auth-profiles.ts +// Description: Agent auth profiles +// +// This is a stub header. The public API is declared here; the implementation +// is in the corresponding .cpp file. Expand as you port the TypeScript logic. + +#include +#include +#include +#include + +namespace openclaw::agents { + +// TODO: declare public types, functions, and classes from src/agents/auth-profiles.ts + +} // namespace openclaw::agents diff --git a/openclaw-cpp/src/auto-reply/reply.cpp b/openclaw-cpp/src/auto-reply/reply.cpp new file mode 100644 index 0000000000000..594601dc9f8c4 --- /dev/null +++ b/openclaw-cpp/src/auto-reply/reply.cpp @@ -0,0 +1,13 @@ +// openclaw-cpp/src/auto-reply/reply.cpp +// Mirrors src/auto-reply/reply.ts +// Description: Auto-reply matching +// +// Stub implementation — port the TypeScript logic from src/auto-reply/reply.ts here. + +#include "reply.hpp" + +namespace openclaw::auto_reply { + +// TODO: implement functions declared in reply.hpp + +} // namespace openclaw::auto_reply diff --git a/openclaw-cpp/src/auto-reply/reply.hpp b/openclaw-cpp/src/auto-reply/reply.hpp new file mode 100644 index 0000000000000..dbac67b91694a --- /dev/null +++ b/openclaw-cpp/src/auto-reply/reply.hpp @@ -0,0 +1,18 @@ +#pragma once +// openclaw-cpp/src/auto-reply/reply.hpp +// Mirrors src/auto-reply/reply.ts +// Description: Auto-reply matching +// +// This is a stub header. The public API is declared here; the implementation +// is in the corresponding .cpp file. Expand as you port the TypeScript logic. + +#include +#include +#include +#include + +namespace openclaw::auto_reply { + +// TODO: declare public types, functions, and classes from src/auto-reply/reply.ts + +} // namespace openclaw::auto_reply diff --git a/openclaw-cpp/src/auto-reply/templating.cpp b/openclaw-cpp/src/auto-reply/templating.cpp new file mode 100644 index 0000000000000..636a70a500f8a --- /dev/null +++ b/openclaw-cpp/src/auto-reply/templating.cpp @@ -0,0 +1,13 @@ +// openclaw-cpp/src/auto-reply/templating.cpp +// Mirrors src/auto-reply/templating.ts +// Description: Auto-reply template engine +// +// Stub implementation — port the TypeScript logic from src/auto-reply/templating.ts here. + +#include "templating.hpp" + +namespace openclaw::auto_reply { + +// TODO: implement functions declared in templating.hpp + +} // namespace openclaw::auto_reply diff --git a/openclaw-cpp/src/auto-reply/templating.hpp b/openclaw-cpp/src/auto-reply/templating.hpp new file mode 100644 index 0000000000000..bee904b6a84f2 --- /dev/null +++ b/openclaw-cpp/src/auto-reply/templating.hpp @@ -0,0 +1,18 @@ +#pragma once +// openclaw-cpp/src/auto-reply/templating.hpp +// Mirrors src/auto-reply/templating.ts +// Description: Auto-reply template engine +// +// This is a stub header. The public API is declared here; the implementation +// is in the corresponding .cpp file. Expand as you port the TypeScript logic. + +#include +#include +#include +#include + +namespace openclaw::auto_reply { + +// TODO: declare public types, functions, and classes from src/auto-reply/templating.ts + +} // namespace openclaw::auto_reply diff --git a/openclaw-cpp/src/browser/browser.cpp b/openclaw-cpp/src/browser/browser.cpp new file mode 100644 index 0000000000000..20f31839792f8 --- /dev/null +++ b/openclaw-cpp/src/browser/browser.cpp @@ -0,0 +1,11 @@ +// openclaw-cpp/src/browser/browser.cpp +// Mirrors src/browser/browser.ts +// Browser management — stub implementation + +#include "browser.hpp" + +namespace openclaw::browser { + +// TODO: implement API declared in browser.hpp + +} // namespace openclaw::browser diff --git a/openclaw-cpp/src/browser/browser.hpp b/openclaw-cpp/src/browser/browser.hpp new file mode 100644 index 0000000000000..8d1ec4f7a5f3e --- /dev/null +++ b/openclaw-cpp/src/browser/browser.hpp @@ -0,0 +1,15 @@ +#pragma once +// openclaw-cpp/src/browser/browser.hpp +// Mirrors src/browser/browser.ts +// Browser management — stub header + +#include +#include +#include +#include + +namespace openclaw::browser { + +// TODO: declare public API from src/browser/browser.ts + +} // namespace openclaw::browser diff --git a/openclaw-cpp/src/canvas-host/canvas-host.cpp b/openclaw-cpp/src/canvas-host/canvas-host.cpp new file mode 100644 index 0000000000000..3fca9fa2efc00 --- /dev/null +++ b/openclaw-cpp/src/canvas-host/canvas-host.cpp @@ -0,0 +1,11 @@ +// openclaw-cpp/src/canvas-host/canvas-host.cpp +// Mirrors src/canvas-host/canvas-host.ts +// Canvas host — stub implementation + +#include "canvas-host.hpp" + +namespace openclaw::canvas_host { + +// TODO: implement API declared in canvas-host.hpp + +} // namespace openclaw::canvas_host diff --git a/openclaw-cpp/src/canvas-host/canvas-host.hpp b/openclaw-cpp/src/canvas-host/canvas-host.hpp new file mode 100644 index 0000000000000..2bc32ed4341f5 --- /dev/null +++ b/openclaw-cpp/src/canvas-host/canvas-host.hpp @@ -0,0 +1,15 @@ +#pragma once +// openclaw-cpp/src/canvas-host/canvas-host.hpp +// Mirrors src/canvas-host/canvas-host.ts +// Canvas host — stub header + +#include +#include +#include +#include + +namespace openclaw::canvas_host { + +// TODO: declare public API from src/canvas-host/canvas-host.ts + +} // namespace openclaw::canvas_host diff --git a/openclaw-cpp/src/channels/routing.cpp b/openclaw-cpp/src/channels/routing.cpp new file mode 100644 index 0000000000000..52a4997d04939 --- /dev/null +++ b/openclaw-cpp/src/channels/routing.cpp @@ -0,0 +1,13 @@ +// openclaw-cpp/src/channels/routing.cpp +// Mirrors src/channels/routing.ts +// Description: Channel routing +// +// Stub implementation — port the TypeScript logic from src/channels/routing.ts here. + +#include "routing.hpp" + +namespace openclaw::channels { + +// TODO: implement functions declared in routing.hpp + +} // namespace openclaw::channels diff --git a/openclaw-cpp/src/channels/routing.hpp b/openclaw-cpp/src/channels/routing.hpp new file mode 100644 index 0000000000000..b4fac7a3dfb3a --- /dev/null +++ b/openclaw-cpp/src/channels/routing.hpp @@ -0,0 +1,18 @@ +#pragma once +// openclaw-cpp/src/channels/routing.hpp +// Mirrors src/channels/routing.ts +// Description: Channel routing +// +// This is a stub header. The public API is declared here; the implementation +// is in the corresponding .cpp file. Expand as you port the TypeScript logic. + +#include +#include +#include +#include + +namespace openclaw::channels { + +// TODO: declare public types, functions, and classes from src/channels/routing.ts + +} // namespace openclaw::channels diff --git a/openclaw-cpp/src/channels/types.cpp b/openclaw-cpp/src/channels/types.cpp new file mode 100644 index 0000000000000..392b334333653 --- /dev/null +++ b/openclaw-cpp/src/channels/types.cpp @@ -0,0 +1,13 @@ +// openclaw-cpp/src/channels/types.cpp +// Mirrors src/channels/types.ts +// Description: Channel type definitions +// +// Stub implementation — port the TypeScript logic from src/channels/types.ts here. + +#include "types.hpp" + +namespace openclaw::channels { + +// TODO: implement functions declared in types.hpp + +} // namespace openclaw::channels diff --git a/openclaw-cpp/src/channels/types.hpp b/openclaw-cpp/src/channels/types.hpp new file mode 100644 index 0000000000000..99a8fc57e8225 --- /dev/null +++ b/openclaw-cpp/src/channels/types.hpp @@ -0,0 +1,18 @@ +#pragma once +// openclaw-cpp/src/channels/types.hpp +// Mirrors src/channels/types.ts +// Description: Channel type definitions +// +// This is a stub header. The public API is declared here; the implementation +// is in the corresponding .cpp file. Expand as you port the TypeScript logic. + +#include +#include +#include +#include + +namespace openclaw::channels { + +// TODO: declare public types, functions, and classes from src/channels/types.ts + +} // namespace openclaw::channels diff --git a/openclaw-cpp/src/cli/argv.cpp b/openclaw-cpp/src/cli/argv.cpp new file mode 100644 index 0000000000000..26ae9a2588e23 --- /dev/null +++ b/openclaw-cpp/src/cli/argv.cpp @@ -0,0 +1,284 @@ +// openclaw-cpp/src/cli/argv.cpp +// Mirrors src/cli/argv.ts + +#include "argv.hpp" +#include "../infra/is-main.hpp" + +#include +#include +#include +#include +#include + +namespace openclaw::cli { + +namespace { + +constexpr std::string_view FLAG_TERMINATOR = "--"; + +const std::set HELP_FLAGS = {"-h", "--help"}; +const std::set VERSION_FLAGS = {"-V", "--version"}; +const std::string_view ROOT_VERSION_ALIAS_FLAG = "-v"; + +// Well-known root-level options that consume a value token. +// Mirrors infra/cli-root-options.ts consumeRootOptionToken(). +const std::set ROOT_VALUE_OPTIONS = { + "--log-level", "--profile", "--log-file", +}; +const std::set ROOT_BOOL_OPTIONS = { + "--verbose", "--debug", "--no-color", "--yes", "-y", +}; + +// Returns how many tokens are consumed by a root option at `index`. +// 0 = not a root option. +int consumeRootOptionToken(const std::vector& args, int index) { + const auto& arg = args[static_cast(index)]; + if (arg.empty() || arg == FLAG_TERMINATOR || arg.front() != '-') return 0; + + const auto eqPos = arg.find('='); + const std::string flag = eqPos == std::string::npos ? arg : arg.substr(0, eqPos); + + if (ROOT_BOOL_OPTIONS.count(flag)) return 1; + if (ROOT_VALUE_OPTIONS.count(flag)) { + if (eqPos != std::string::npos) return 1; // --flag=value in one token + // value is the next token + const size_t next = static_cast(index) + 1; + if (next < args.size() && !args[next].empty() && args[next].front() != '-') { + return 2; + } + return 1; + } + return 0; +} + +bool isValueToken(const std::string& s) { + return !s.empty() && s.front() != '-'; +} + +bool isRootInvocationForFlags( + const std::vector& argv, + const std::set& targetFlags, + bool includeVersionAlias = false) +{ + const auto args = std::vector(argv.begin() + std::min(2, argv.size()), argv.end()); + bool hasTarget = false; + + for (int i = 0; i < static_cast(args.size()); ++i) { + const auto& arg = args[static_cast(i)]; + if (arg.empty()) continue; + if (arg == FLAG_TERMINATOR) break; + + if (targetFlags.count(arg) || + (includeVersionAlias && arg == ROOT_VERSION_ALIAS_FLAG)) { + hasTarget = true; + continue; + } + + const int consumed = consumeRootOptionToken(args, i); + if (consumed > 0) { + i += consumed - 1; + continue; + } + // Unknown flag or subcommand → not a root invocation. + return false; + } + return hasTarget; +} + +std::vector getCommandPathInternal( + const std::vector& argv, + int depth, + bool skipRootOptions) +{ + const auto args = std::vector(argv.begin() + std::min(2, argv.size()), argv.end()); + std::vector path; + + for (int i = 0; i < static_cast(args.size()); ++i) { + const auto& arg = args[static_cast(i)]; + if (arg.empty()) continue; + if (arg == "--") break; + + if (skipRootOptions) { + const int consumed = consumeRootOptionToken(args, i); + if (consumed > 0) { + i += consumed - 1; + continue; + } + } + + if (arg.front() == '-') continue; + + path.push_back(arg); + if (static_cast(path.size()) >= depth) break; + } + return path; +} + +} // anonymous namespace + +bool hasHelpOrVersion(const std::vector& argv) { + for (const auto& arg : argv) { + if (HELP_FLAGS.count(arg) || VERSION_FLAGS.count(arg)) return true; + } + return hasRootVersionAlias(argv); +} + +bool hasFlag(const std::vector& argv, const std::string& name) { + const auto args = std::vector(argv.begin() + std::min(2, argv.size()), argv.end()); + for (const auto& arg : args) { + if (arg == FLAG_TERMINATOR) break; + if (arg == name) return true; + } + return false; +} + +bool hasRootVersionAlias(const std::vector& argv) { + const auto args = std::vector(argv.begin() + std::min(2, argv.size()), argv.end()); + bool hasAlias = false; + for (int i = 0; i < static_cast(args.size()); ++i) { + const auto& arg = args[static_cast(i)]; + if (arg.empty()) continue; + if (arg == FLAG_TERMINATOR) break; + if (arg == ROOT_VERSION_ALIAS_FLAG) { + hasAlias = true; + continue; + } + const int consumed = consumeRootOptionToken(args, i); + if (consumed > 0) { + i += consumed - 1; + continue; + } + if (arg.front() == '-') continue; + return false; + } + return hasAlias; +} + +bool isRootVersionInvocation(const std::vector& argv) { + return isRootInvocationForFlags(argv, VERSION_FLAGS, /*includeVersionAlias=*/true); +} + +bool isRootHelpInvocation(const std::vector& argv) { + return isRootInvocationForFlags(argv, HELP_FLAGS, /*includeVersionAlias=*/false); +} + +std::optional> getFlagValue( + const std::vector& argv, const std::string& name) { + const auto args = std::vector(argv.begin() + std::min(2, argv.size()), argv.end()); + for (int i = 0; i < static_cast(args.size()); ++i) { + const auto& arg = args[static_cast(i)]; + if (arg == FLAG_TERMINATOR) break; + if (arg == name) { + const size_t next = static_cast(i) + 1; + if (next < args.size() && isValueToken(args[next])) { + return std::make_optional(std::make_optional(args[next])); + } + return std::make_optional(std::optional{}); + } + if (arg.substr(0, name.size() + 1) == name + "=") { + const std::string value = arg.substr(name.size() + 1); + if (!value.empty()) return std::make_optional(std::make_optional(value)); + return std::make_optional(std::optional{}); + } + } + return std::nullopt; +} + +bool getVerboseFlag(const std::vector& argv, bool includeDebug) { + if (hasFlag(argv, "--verbose")) return true; + if (includeDebug && hasFlag(argv, "--debug")) return true; + return false; +} + +std::optional> getPositiveIntFlagValue( + const std::vector& argv, const std::string& name) { + auto raw = getFlagValue(argv, name); + if (!raw.has_value()) return std::nullopt; + if (!raw->has_value()) return std::make_optional(std::optional{}); + try { + const int v = std::stoi(**raw); + if (v <= 0) return std::make_optional(std::optional{}); + return std::make_optional(std::make_optional(v)); + } catch (...) { + return std::make_optional(std::optional{}); + } +} + +std::vector getCommandPath(const std::vector& argv, int depth) { + return getCommandPathInternal(argv, depth, /*skipRootOptions=*/false); +} + +std::vector getCommandPathWithRootOptions(const std::vector& argv, int depth) { + return getCommandPathInternal(argv, depth, /*skipRootOptions=*/true); +} + +std::optional getPrimaryCommand(const std::vector& argv) { + auto path = getCommandPathWithRootOptions(argv, 1); + if (path.empty()) return std::nullopt; + return path.front(); +} + +bool shouldMigrateStateFromPath(const std::vector& path) { + if (path.empty()) return true; + const auto& primary = path[0]; + const auto secondary = path.size() > 1 ? path[1] : ""; + + if (primary == "health" || primary == "status" || primary == "sessions") return false; + if (primary == "config" && (secondary == "get" || secondary == "unset")) return false; + if (primary == "models" && (secondary == "list" || secondary == "status")) return false; + if (primary == "memory" && secondary == "status") return false; + if (primary == "agent") return false; + return true; +} + +bool shouldMigrateState(const std::vector& argv) { + return shouldMigrateStateFromPath(getCommandPath(argv, 2)); +} + +std::vector buildParseArgv(const BuildParseArgvParams& params) { + const std::vector& base = + !params.rawArgs.empty() ? params.rawArgs + : !params.fallbackArgv.empty() ? params.fallbackArgv + : openclaw::infra::getProcessArgv(); + + const auto& programName = params.programName; + + std::vector normalised; + if (!programName.empty() && !base.empty() && base[0] == programName) { + normalised = std::vector(base.begin() + 1, base.end()); + } else if (!base.empty() && + (base[0].ends_with("openclaw") || base[0].ends_with("openclaw.exe"))) { + normalised = std::vector(base.begin() + 1, base.end()); + } else { + normalised = base; + } + + // If it already looks like [node, script, ...] keep as-is. + if (normalised.size() >= 2) { + const auto& n0 = normalised[0]; + if (n0 == "node" || n0 == "bun" || + n0.ends_with("/node") || n0.ends_with("/bun")) { + return normalised; + } + } + + std::vector result = {"node", + programName.empty() ? "openclaw" : programName}; + result.insert(result.end(), normalised.begin(), normalised.end()); + return result; +} + +std::vector normalizeWindowsArgv(std::vector argv) { + // On non-Windows this is a no-op. +#if defined(_WIN32) + // Expand any paths that contain forward slashes to backslashes. + for (auto& arg : argv) { + if (arg.find('/') != std::string::npos && arg.find('\\') == std::string::npos) { + std::replace(arg.begin(), arg.end(), '/', '\\'); + } + } +#endif + return argv; +} + +} // namespace openclaw::cli diff --git a/openclaw-cpp/src/cli/argv.hpp b/openclaw-cpp/src/cli/argv.hpp new file mode 100644 index 0000000000000..d155151a80e32 --- /dev/null +++ b/openclaw-cpp/src/cli/argv.hpp @@ -0,0 +1,63 @@ +#pragma once +// openclaw-cpp/src/cli/argv.hpp +// Mirrors src/cli/argv.ts + +#include +#include +#include + +namespace openclaw::cli { + +// ── Flag tests ──────────────────────────────────────────────────────────────── + +bool hasHelpOrVersion(const std::vector& argv); +bool hasFlag(const std::vector& argv, const std::string& name); +bool hasRootVersionAlias(const std::vector& argv); + +bool isRootVersionInvocation(const std::vector& argv); +bool isRootHelpInvocation(const std::vector& argv); + +// ── Value extraction ────────────────────────────────────────────────────────── + +// Returns: +// has_value() == true → flag was found and the next token is a value +// has_value() == false → flag was found but has no following value token +// nullopt → flag was not found +std::optional> getFlagValue(const std::vector& argv, + const std::string& name); + +bool getVerboseFlag(const std::vector& argv, bool includeDebug = false); + +std::optional> getPositiveIntFlagValue(const std::vector& argv, + const std::string& name); + +// ── Command path extraction ─────────────────────────────────────────────────── + +std::vector getCommandPath(const std::vector& argv, + int depth = 2); + +std::vector getCommandPathWithRootOptions(const std::vector& argv, + int depth = 2); + +std::optional getPrimaryCommand(const std::vector& argv); + +// ── State migration helpers ─────────────────────────────────────────────────── + +bool shouldMigrateState(const std::vector& argv); +bool shouldMigrateStateFromPath(const std::vector& path); + +// ── argv builder ────────────────────────────────────────────────────────────── + +struct BuildParseArgvParams { + std::string programName; + std::vector rawArgs; + std::vector fallbackArgv; +}; + +std::vector buildParseArgv(const BuildParseArgvParams& params); + +// ── Windows argv normalization ──────────────────────────────────────────────── +// Mirrors normalizeWindowsArgv() from src/cli/windows-argv.ts. +std::vector normalizeWindowsArgv(std::vector argv); + +} // namespace openclaw::cli diff --git a/openclaw-cpp/src/cli/program.cpp b/openclaw-cpp/src/cli/program.cpp new file mode 100644 index 0000000000000..8933ffb2f7448 --- /dev/null +++ b/openclaw-cpp/src/cli/program.cpp @@ -0,0 +1,358 @@ +// openclaw-cpp/src/cli/program.cpp +// Mirrors src/cli/program.ts + src/cli/program/build-program.ts +// + src/cli/program/command-registry.ts + +#include "program.hpp" +#include "argv.hpp" +#include "../version.hpp" +#include "../globals.hpp" +#include "../logger.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace openclaw::cli { + +// ── Command ─────────────────────────────────────────────────────────────────── + +std::shared_ptr Command::addCommand(const std::string& cmdName, + const std::string& cmdDescription) { + auto cmd = std::make_shared(); + cmd->name = cmdName; + cmd->description = cmdDescription; + subcommands.push_back(cmd); + return cmd; +} + +void Command::addOption(const std::string& flags, const std::string& desc, + std::optional defaultValue) { + options.push_back({flags, desc, std::move(defaultValue)}); +} + +void Command::printHelp() const { + std::cout << "Usage: openclaw " << name << " [options]\n\n" + << description << "\n\n"; + if (!options.empty()) { + std::cout << "Options:\n"; + for (const auto& opt : options) { + std::cout << " " << opt.flags; + if (opt.defaultValue.has_value()) { + std::cout << " (default: " << *opt.defaultValue << ")"; + } + std::cout << "\n " << opt.description << "\n"; + } + std::cout << "\n"; + } + if (!subcommands.empty()) { + std::cout << "Commands:\n"; + for (const auto& sub : subcommands) { + std::cout << " " << sub->name << " " << sub->description << "\n"; + } + std::cout << "\n"; + } +} + +// ── Program ─────────────────────────────────────────────────────────────────── + +std::shared_ptr Program::addCommand(const std::string& cmdName, + const std::string& cmdDescription) { + auto cmd = std::make_shared(); + cmd->name = cmdName; + cmd->description = cmdDescription; + commands.push_back(cmd); + return cmd; +} + +void Program::printHelp() const { + std::cout << "Usage: openclaw [command] [options]\n\n" + << description << "\n\n" + << "Version: " << version << "\n\n" + << "Commands:\n"; + for (const auto& cmd : commands) { + std::cout << " " << cmd->name; + // Pad to 20 characters. + const int pad = std::max(0, 20 - static_cast(cmd->name.size())); + std::cout << std::string(static_cast(pad), ' ') + << cmd->description << "\n"; + } + std::cout << "\nOptions:\n" + << " -h, --help Display help\n" + << " -V, --version Output version number\n" + << " -v Alias for --version\n" + << " --verbose Enable verbose output\n" + << " --no-color Disable colour output\n" + << "\nRun `openclaw --help` for command-specific help.\n"; +} + +int Program::parseAndRun(const std::vector& argv) { + // Determine primary command (argv[0] = executable, argv[1] may be "openclaw"). + const auto primary = getPrimaryCommand(argv); + + if (!primary.has_value()) { + printHelp(); + return 0; + } + + for (const auto& cmd : commands) { + if (cmd->name == *primary || + std::find(cmd->aliases.begin(), cmd->aliases.end(), *primary) != cmd->aliases.end()) { + if (cmd->action) { + // Find the position of the command name in argv and pass everything after it. + // argv is in Node.js form: ["node", "openclaw", ...flags..., commandName, ...rest] + // We must skip the command token itself and any root-level flags that precede it. + auto it = std::find(argv.begin(), argv.end(), *primary); + const std::vector rest = + (it != argv.end()) + ? std::vector(it + 1, argv.end()) + : std::vector{}; + return cmd->action(rest); + } + cmd->printHelp(); + return 0; + } + } + + std::fprintf(stderr, "[openclaw] Unknown command: %s\n", primary->c_str()); + std::fprintf(stderr, "Run `openclaw --help` for a list of commands.\n"); + return 1; +} + +// ── buildProgram ────────────────────────────────────────────────────────────── + +namespace { + +// Register all core CLI commands on the program. +// Mirrors command-registry.ts coreEntries[]. +void registerCoreCommands(Program& prog) { + // setup + { + auto cmd = prog.addCommand("setup", + "Initialize local config and agent workspace"); + cmd->action = [](const std::vector&) -> int { + std::cout << "[openclaw] setup command — not yet implemented in C++ port\n"; + return 0; + }; + } + // onboard + { + auto cmd = prog.addCommand("onboard", + "Interactive onboarding wizard for gateway, workspace, and skills"); + cmd->action = [](const std::vector&) -> int { + std::cout << "[openclaw] onboard command — not yet implemented in C++ port\n"; + return 0; + }; + } + // configure + { + auto cmd = prog.addCommand("configure", + "Interactive setup wizard for credentials, channels, gateway, and agent defaults"); + cmd->action = [](const std::vector&) -> int { + std::cout << "[openclaw] configure command — not yet implemented in C++ port\n"; + return 0; + }; + } + // config + { + auto cmd = prog.addCommand("config", + "Non-interactive config helpers (get/set/unset/file/validate)"); + cmd->addCommand("get", "Get a config value"); + cmd->addCommand("set", "Set a config value"); + cmd->addCommand("unset", "Unset a config value"); + cmd->addCommand("file", "Print the config file path"); + cmd->addCommand("validate", "Validate the config file"); + cmd->action = [](const std::vector&) -> int { + std::cout << "[openclaw] config command — not yet implemented in C++ port\n"; + return 0; + }; + } + // doctor + { + auto cmd = prog.addCommand("doctor", + "Health checks + quick fixes for the gateway and channels"); + cmd->action = [](const std::vector&) -> int { + std::cout << "[openclaw] doctor command — not yet implemented in C++ port\n"; + return 0; + }; + } + // dashboard + { + auto cmd = prog.addCommand("dashboard", + "Open the Control UI with your current token"); + cmd->action = [](const std::vector&) -> int { + std::cout << "[openclaw] dashboard command — not yet implemented in C++ port\n"; + return 0; + }; + } + // reset + { + auto cmd = prog.addCommand("reset", + "Reset local config/state (keeps the CLI installed)"); + cmd->action = [](const std::vector&) -> int { + std::cout << "[openclaw] reset command — not yet implemented in C++ port\n"; + return 0; + }; + } + // message + { + auto cmd = prog.addCommand("message", "Send, read, and manage messages"); + cmd->addCommand("send", "Send a message"); + cmd->addCommand("read", "Read messages"); + cmd->action = [](const std::vector&) -> int { + std::cout << "[openclaw] message command — not yet implemented in C++ port\n"; + return 0; + }; + } + // memory + { + auto cmd = prog.addCommand("memory", "Search and reindex memory files"); + cmd->addCommand("search", "Search memory"); + cmd->addCommand("reindex", "Reindex memory"); + cmd->addCommand("status", "Memory status"); + cmd->action = [](const std::vector&) -> int { + std::cout << "[openclaw] memory command — not yet implemented in C++ port\n"; + return 0; + }; + } + // agent + { + auto cmd = prog.addCommand("agent", + "Run one agent turn via the Gateway"); + cmd->addOption("--message ", "Message to send"); + cmd->addOption("--thinking ", "Thinking level (low/medium/high)", "medium"); + cmd->action = [](const std::vector&) -> int { + std::cout << "[openclaw] agent command — not yet implemented in C++ port\n"; + return 0; + }; + } + // agents + { + auto cmd = prog.addCommand("agents", + "Manage isolated agents (workspaces, auth, routing)"); + cmd->addCommand("list", "List agents"); + cmd->addCommand("add", "Add an agent"); + cmd->addCommand("delete", "Delete an agent"); + cmd->action = [](const std::vector&) -> int { + std::cout << "[openclaw] agents command — not yet implemented in C++ port\n"; + return 0; + }; + } + // status + { + auto cmd = prog.addCommand("status", + "Show channel health and recent session recipients"); + cmd->action = [](const std::vector&) -> int { + std::cout << "[openclaw] status command — not yet implemented in C++ port\n"; + return 0; + }; + } + // health + { + auto cmd = prog.addCommand("health", + "Fetch health from the running gateway"); + cmd->action = [](const std::vector&) -> int { + std::cout << "[openclaw] health command — not yet implemented in C++ port\n"; + return 0; + }; + } + // sessions + { + auto cmd = prog.addCommand("sessions", + "List stored conversation sessions"); + cmd->addCommand("list", "List sessions"); + cmd->addCommand("cleanup", "Clean up old sessions"); + cmd->action = [](const std::vector&) -> int { + std::cout << "[openclaw] sessions command — not yet implemented in C++ port\n"; + return 0; + }; + } + // gateway + { + auto cmd = prog.addCommand("gateway", + "Start / manage the OpenClaw gateway service"); + cmd->addCommand("run", "Start the gateway"); + cmd->addCommand("stop", "Stop the gateway"); + cmd->addCommand("status", "Gateway status"); + cmd->addCommand("install", "Install gateway as a system service"); + cmd->action = [](const std::vector&) -> int { + std::cout << "[openclaw] gateway command — not yet implemented in C++ port\n"; + return 0; + }; + } + // models + { + auto cmd = prog.addCommand("models", + "List and manage AI model configurations"); + cmd->addCommand("list", "List available models"); + cmd->addCommand("set", "Set the default model"); + cmd->addCommand("status", "Model auth status"); + cmd->action = [](const std::vector&) -> int { + std::cout << "[openclaw] models command — not yet implemented in C++ port\n"; + return 0; + }; + } + // channels + { + auto cmd = prog.addCommand("channels", + "Manage messaging channels (WhatsApp, Telegram, Discord, etc.)"); + cmd->addCommand("status", "Channel status"); + cmd->addCommand("add", "Add a channel"); + cmd->action = [](const std::vector&) -> int { + std::cout << "[openclaw] channels command — not yet implemented in C++ port\n"; + return 0; + }; + } + // browser + { + auto cmd = prog.addCommand("browser", + "Manage OpenClaw's dedicated browser (Chrome/Chromium)"); + cmd->addCommand("launch", "Launch browser"); + cmd->addCommand("close", "Close browser"); + cmd->addCommand("install", "Install browser"); + cmd->action = [](const std::vector&) -> int { + std::cout << "[openclaw] browser command — not yet implemented in C++ port\n"; + return 0; + }; + } +} + +} // anonymous namespace + +std::shared_ptr buildProgram() { + auto prog = std::make_shared(); + prog->name = "openclaw"; + prog->version = getVersion(); + prog->description = "AI gateway and CLI — connects AI models to messaging channels"; + registerCoreCommands(*prog); + return prog; +} + +int runCli(const std::vector& argv) { + try { + const auto prog = buildProgram(); + + // Fast paths matching entry.ts. + if (isRootVersionInvocation(argv)) { + std::cout << getVersion() << "\n"; + return 0; + } + if (isRootHelpInvocation(argv)) { + prog->printHelp(); + return 0; + } + + return prog->parseAndRun(argv); + } catch (const std::exception& e) { + std::fprintf(stderr, "[openclaw] CLI error: %s\n", e.what()); + return 1; + } catch (...) { + std::fprintf(stderr, "[openclaw] CLI error: unknown exception\n"); + return 1; + } +} + +} // namespace openclaw::cli diff --git a/openclaw-cpp/src/cli/program.hpp b/openclaw-cpp/src/cli/program.hpp new file mode 100644 index 0000000000000..24b0369318da1 --- /dev/null +++ b/openclaw-cpp/src/cli/program.hpp @@ -0,0 +1,80 @@ +#pragma once +// openclaw-cpp/src/cli/program.hpp +// Mirrors src/cli/program.ts + src/cli/program/build-program.ts + +#include +#include +#include +#include +#include + +namespace openclaw::cli { + +// Forward declaration of the command handler type. +using CommandHandler = std::function&)>; + +// ── Command ─────────────────────────────────────────────────────────────────── +// Lightweight command descriptor (mirrors Commander.Command). +struct Command { + std::string name; + std::string description; + std::vector aliases; + bool allowUnknownOptions = false; + + // Registered sub-commands. + std::vector> subcommands; + + // Action handler: receives the remaining positional arguments after options. + CommandHandler action; + + // Option definitions for help text. + struct Option { + std::string flags; // e.g. "-m, --message " + std::string description; + std::optional defaultValue; + }; + std::vector