CLAUDE.md 7.7 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

SmartBotic is a workflow automation and execution platform with a microservices architecture. The backend is C++20 with gRPC communication, and the frontend is React/TypeScript.

Build Commands

C++ Backend

The existing build/ directory is configured with Ninja, not Make. Running make in it prints "Nothing to be done" and builds nothing at all - it does not fail, so an edit looks built when it has not been. Always build through cmake, which uses whichever generator the directory was configured with:

# Everything
cmake --build build -j8

# One target - much faster when only one service changed
cmake --build build --target smartbotic-webserver -j8
cmake --build build --target smartbotic-runner -j8

Configuring a build directory from scratch:

# Debug
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug

# Release
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release

# With Address Sanitizer
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug -DENABLE_ASAN=ON

After building a service, restart it to actually run the new binary. The two services live on different machines and are managed by different init systems (split 2026-09-03):

host restart with
webserver zeus (Void Linux, runit) sudo sv restart smartbotic-webserver
runner mulan (Debian, systemd) ssh mulan systemctl --user restart smartbotic-runner

There is no systemctl on zeus at all - it prints "command not found" rather than failing in any way that looks like a wrong host.

Frontend (webui/)

cd webui
npm install
npm run dev      # Dev server (port 3000, proxies to localhost:8090)
npm run build    # Production build
npm run lint     # ESLint

Running Services

The database is not built from this repo - it is the standalone upstream smartbotic-database daemon. It runs on zeus as the Docker container smartbotic-db (moved there 2026-08-09; it and the SD.cpp REST API could not share mulan's memory), so it is neither an APT package nor a system service on the host any more. It is already up; the in-repo services connect to it:

# 1. Database daemon - already running on zeus as the Docker container
#    `smartbotic-db`, published on 9004. Not started from this repo.
docker ps --filter name=smartbotic-db

# 2. WebServer on zeus (HTTP 8090, WebSocket 8091, gRPC 9012 + 9013)
sudo sv status smartbotic-webserver          # runit, not systemd
tail -f /var/log/smartbotic-webserver/current

# 3. Runner on mulan (gRPC port 9011)
ssh mulan systemctl --user status smartbotic-runner
ssh mulan journalctl --user -u smartbotic-runner -f

Because the two are split, three addresses must point across the network. They are env overrides in a systemd drop-in on mulan (~/.config/systemd/user/smartbotic-runner.service.d/zeus-webserver.conf), NOT in config/runner.json - that file is tracked in git and shared by both hosts, so its localhost defaults cannot be edited per machine:

variable value what breaks without it
WEBSERVER_ADDRESS zeus.fsociety.hu:8090 the runner never registers
NODE_SYNC_ADDRESS zeus.fsociety.hu:9012 no node updates reach it
CREDENTIAL_SERVICE_ADDRESS zeus.fsociety.hu:9013 credentials and workflow-control fail
ADVERTISE_ADDRESS mulan.fsociety.hu:9011 the runner registers as localhost:9011, reports online, and dispatches silently go to the webserver's own host

Both the database address and the project namespace are configurable everywhere, via config/webserver.json and config/runner.json:

Key Default Env override
database_address localhost:9004 DATABASE_ADDRESS
database_project smartbotic-automation DATABASE_PROJECT

One smartbotic-database instance is shared by several projects. database_project is the multi-tenant namespace (upstream client >= 2.3): the client transparently sends every collection as <project>:<collection>, so our data stays isolated from other services on the same daemon. Never set it to default - that is the shared back-compat namespace where projects collide.

Architecture

Two In-Repo Services + External Database

  1. WebServer Service (src/webserver/) - HTTP REST API, WebSocket for real-time updates, JWT auth, runner registry
  2. Runner Service (src/runner/) - Workflow execution engine with QuickJS for JavaScript node evaluation

Both persist through the external smartbotic-database daemon (upstream repo: ssh://git@git.smartbotics.ai:10022/fszontagh/smartbotic-database.git), consumed as the libsmartbotic-db-client-dev APT package. cmake/FindPackages.cmake locates it via find_package(smartbotic-db-client CONFIG REQUIRED) → target smartbotic::db-client. lib/storage/storage_client.{hpp,cpp} is a thin PIMPL adapter over smartbotic::database::Client, so callers never touch the upstream API directly.

Data Flow

WebUI (React) → HTTP/WebSocket → WebServer → gRPC → smartbotic-database (external)
                                     ↓
                              gRPC → Runner(s) → executes workflows

Key Directories

  • lib/ - Shared libraries (common utilities, config loader, logging, storage client)
  • proto/ - Protocol Buffer definitions for gRPC services
  • src/ - Microservice implementations
  • nodes/ - Built-in workflow node definitions (JavaScript modules)
  • webui/ - React frontend
  • config/ - Runtime JSON configuration files

Node System

Workflow nodes are JavaScript modules in nodes/ with this interface:

module.exports = {
  configSchema: { /* JSON Schema */ },
  inputSchema: { /* JSON Schema */ },
  outputSchema: { /* JSON Schema */ },
  execute: async (config, input, context) => { /* returns output */ }
}

Nodes are loaded by the Runner service and executed in QuickJS. Hot-reload is supported.

See docs/nodes.md for complete node creation guide.

See docs/node-roadmap.md for nodes worth building next.

Migrating Nodes to Database

After creating or modifying nodes in nodes/, migrate them to the running database:

# 1. Login to get JWT token
TOKEN=$(curl -s http://localhost:8090/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username": "admin", "password": "admin"}' | jq -r '.accessToken')

# 2. Migrate nodes from filesystem to database
curl -X POST http://localhost:8090/api/v1/nodes/migrate \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"nodesPath": "./nodes"}'

# 3. Verify nodes are loaded
curl -s http://localhost:8090/api/v1/nodes \
  -H "Authorization: Bearer $TOKEN" | jq '.nodes[] | "\(.id) - \(.name)"'

Runners automatically receive node updates via gRPC streaming - no restart required.

Configuration

Services load JSON configs from config/ directory:

  • webserver.json - HTTP port, JWT settings, runner load balancing, database_address
  • runner.json - Runner ID, max concurrent executions, hot reload settings, database_address

The database daemon has its own config at /etc/smartbotic-database/config.json, owned by the upstream package - not by this repo.

Environment variables can override config values using ${VAR_NAME:default} syntax.

C++ Standards

  • C++20 required (strict compliance, no extensions)
  • Compiler warnings: Wall, Wextra, Wpedantic
  • Key libraries: gRPC, Protobuf, cpp-httplib, QuickJS, spdlog, nlohmann/json, bcrypt

Frontend Stack

  • React 18.2 + TypeScript + Vite
  • State: Zustand + React Query
  • UI: TailwindCSS + Lucide icons
  • Workflow editor: ReactFlow