mirror of
https://github.com/logos-co/logos-dev-boost.git
synced 2026-08-27 08:01:08 +00:00
These were already wrong before any deletion. module-builder repointed the cdylib Qt glue onto logos-plugin-qt's logos-qt-host-generator (5081088) and the docs kept naming logos-qt-generator, so the pipeline they describe has not been the pipeline that runs for some time. That matters more here than in most docs: guidelines/ and docs/ are SHIPPED — flake.nix copies them into the dev-boost output, and generate-agents-md.ts embeds them into every scaffolded module's AGENTS.md/CLAUDE.md. So the stale line was being handed to module authors, and to agents reading the scaffold, as the instruction. That is why the six doctests/outputs/ files change too: they are the recorded scaffolder output, and they carry the same text. Only the middle step moves. `logos-cpp-generator --backend cdylib` — the Qt-free C-ABI export wrapper — is a different tool doing a different job and is deliberately untouched; it remains correct in every one of these blocks. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2024 lines
77 KiB
Plaintext
2024 lines
77 KiB
Plaintext
# Logos Development Platform — Full Documentation
|
|
|
|
# logos-dev-boost
|
|
|
|
## Overall Description
|
|
|
|
logos-dev-boost is a developer acceleration tool for the Logos modular application platform. It provides AI coding agents and human developers with accurate, always-available knowledge of the Logos SDK, build system, module architecture, and development workflows.
|
|
|
|
The tool solves a fundamental problem: AI agents (Claude Code, Cursor, Copilot, Codex) have no training data about the Logos ecosystem. They hallucinate APIs, use wrong build commands, generate Qt-dependent code where pure C++ is required, and cannot navigate the multi-repo architecture. Human developers face a similar but smaller-scale problem — the onboarding path from "I want to build a Logos module" to a working, packaged, tested plugin is steep.
|
|
|
|
logos-dev-boost addresses this by operating at three levels:
|
|
|
|
1. **Always-loaded context** — `AGENTS.md` / `CLAUDE.md` files with a compressed documentation index. Loaded automatically at session start. Based on Next.js research showing 100% AI eval pass rate with bundled docs versus 79% with skills-only approaches.
|
|
2. **On-demand skills** — Detailed, step-by-step task guides that activate when the agent works on a specific task. Follows the Agent Skills specification for cross-tool compatibility.
|
|
3. **MCP server** — Live project introspection tools (project info, documentation search, API reference, build help, scaffolding) via the Model Context Protocol.
|
|
|
|
## Definitions & Acronyms
|
|
|
|
| Term | Definition |
|
|
|------|------------|
|
|
| **Universal Module** | A Logos module whose implementation is pure C++ (no Qt types). All Qt glue is generated at build time by the header-first cdylib pipeline. Identified by `"interface": "universal"` in metadata.json. |
|
|
| **UI App** | A QML-based UI component displayed as a tab in Basecamp's MDI workspace. Either pure QML (calls backend modules via `logos.callModule()`) or QML + process-isolated C++ backend (Qt Remote Objects). Identified by `"type": "ui_qml"` in metadata.json. |
|
|
| **LIDL** | Logos Interface Definition Language — a lightweight DSL for declaring module interfaces. The universal pipeline derives a `.lidl` from your C++ header automatically; you can also hand-write one (the `cdylib` interface). Both produce identical generated output. |
|
|
| **Provider Glue** | Generated code (`_cdylib_glue.{h,cpp}`, `_module_impl.cpp`) that wraps a pure C++ implementation class: a uniform Qt-plugin glue over the common module-impl C ABI, plus a Qt-free C-ABI export wrapper. |
|
|
| **Client Stub** | Generated type-safe C++ wrapper class that callers use to invoke a module's methods without string-based dispatch. |
|
|
| **LogosAPI** | The runtime API that modules use to call other modules. Provides `callModule(name, method, args)` which returns a `LogosResult`. |
|
|
| **LogosResult** | Structured return type for cross-module calls. Contains `success()`, `data()` (QVariant), and `errorMessage()`. |
|
|
| **LGX** | Logos Package Format — gzip tar archives with platform-specific variants for distributing modules and UI apps. |
|
|
| **logoscore** | Headless CLI runtime that loads modules and optionally calls their methods. Used for testing modules without the full GUI. |
|
|
| **logos_host** | Per-module host process spawned by `liblogos_core`. Each module runs in isolation, communicating via Qt Remote Objects IPC. |
|
|
| **MCP** | Model Context Protocol — open standard for AI agent tool integration. logos-dev-boost exposes tools via MCP's stdio transport. |
|
|
| **Agent Skill** | A portable knowledge module (SKILL.md + optional assets) that AI agents activate on demand. Follows the agentskills.io specification. |
|
|
|
|
## Domain Model
|
|
|
|
### Two Component Types
|
|
|
|
This distinction is fundamental to the entire Logos ecosystem and to everything logos-dev-boost teaches:
|
|
|
|
**Logos Modules (core)** are process-isolated backend services. The developer writes a plain C++ implementation class using standard types (`std::string`, `int64_t`, `std::vector<T>`, `bool`). No Qt types appear in user code. The build system runs the universal codegen pipeline (header → `.lidl` → cdylib glue) to generate all Qt glue: the uniform Qt-plugin glue and a Qt-free C-ABI export wrapper around your class. Modules are loaded by `logoscore` (headless) or `logos-basecamp` (GUI) via `liblogos_core`. Each runs in its own isolated `logos_host` process and communicates via Qt Remote Objects IPC.
|
|
|
|
Reference implementation: `logos-accounts-module` — `metadata.json` has `"interface": "universal"`, `src/accounts_module_impl.h` is pure C++, and `mkLogosModule` runs the universal codegen automatically (no `preConfigure`).
|
|
|
|
**UI Apps** (`"type": "ui_qml"`) are QML-based UI components displayed as tabs in Basecamp's MDI workspace. Two subtypes exist: pure QML apps (no C++, call backend modules via `logos.callModule()`) and QML + C++ backend apps (process-isolated C++ backend communicating via Qt Remote Objects, QML gets a typed replica via `logos.module()`).
|
|
|
|
```
|
|
Logos Module (universal) UI App (ui_qml)
|
|
───────────────────────── ──────────────────────────
|
|
User writes: Pure C++ impl header QML (pure) or QML + .rep + C++ plugin
|
|
(std::string, int64_t, etc.) (Qt types OK in backend)
|
|
|
|
Generated: .lidl + cdylib glue + plugin class QTRO source/replica (from .rep)
|
|
(header-first cdylib pipeline)
|
|
|
|
metadata.json: "interface": "universal" "type": "ui_qml"
|
|
"type": "core" "view": "Main.qml"
|
|
|
|
Loaded by: logoscore / liblogos_core Basecamp / standalone runner
|
|
Runs in: Isolated logos_host process QML in-process, C++ backend in logos_host
|
|
Has UI: No Yes (tab in MDI workspace)
|
|
```
|
|
|
|
### Universal Module Type System
|
|
|
|
The code generator maps C++ standard types to LIDL types to Qt types:
|
|
|
|
| C++ type | LIDL type | Qt type |
|
|
|----------|-----------|---------|
|
|
| `std::string` / `const std::string&` | `tstr` | `QString` |
|
|
| `bool` | `bool` | `bool` |
|
|
| `int64_t` | `int` | `int` |
|
|
| `uint64_t` | `uint` | `int` |
|
|
| `double` | `float64` | `double` |
|
|
| `void` | `void` | `void` |
|
|
| `std::vector<std::string>` | `[tstr]` | `QStringList` |
|
|
| `std::vector<uint8_t>` | `bstr` | `QByteArray` |
|
|
| `std::vector<int64_t>` | `[int]` | `QVariantList` |
|
|
| `std::vector<bool>` | `[bool]` | `QVariantList` |
|
|
| `LogosMap` | `{tstr: any}` | `QVariantMap` |
|
|
| `LogosList` | `[any]` | `QVariantList` |
|
|
|
|
Module authors only work with the C++ column. The generator handles everything else. `LogosMap`/`LogosList` (from `<logos_json.h>`) are `nlohmann::json` aliases for returning rich structured data while keeping the impl Qt-free.
|
|
|
|
### How logos-dev-boost Layers Work Together
|
|
|
|
```
|
|
Layer 1: AGENTS.md / CLAUDE.md Always loaded at session start
|
|
(compressed docs index) Every AI tool reads these automatically
|
|
│
|
|
Layer 2: Guidelines Loaded into AGENTS.md content
|
|
(core, universal-module, Conventions the agent must always follow
|
|
ui-app, nix-build, etc.)
|
|
│
|
|
Layer 3: Skills Activated on demand by the AI agent
|
|
(create-module, package, Detailed step-by-step task guides
|
|
test, wrap-lib, etc.)
|
|
│
|
|
Layer 4: MCP Server Called by the agent when it needs live data
|
|
(project-info, search-docs, Parses the actual project on disk
|
|
api-reference, build-help)
|
|
│
|
|
Layer 5: Scaffolding Generates new projects from templates
|
|
(init command, templates) Pre-configured with correct AI context
|
|
```
|
|
|
|
## User/Agent Journeys
|
|
|
|
### Journey 1: Create a Universal C++ Module
|
|
|
|
The primary journey. A developer (or AI agent) creates a pure C++ module with no Qt in user code.
|
|
|
|
**Step 1: Scaffold the project**
|
|
|
|
```
|
|
nix run github:logos-co/logos-dev-boost -- init crypto_utils --type module
|
|
```
|
|
|
|
Or tell an AI agent in an empty directory: "create a new Logos module called crypto_utils that provides hashing utilities"
|
|
|
|
The `create-universal-module` skill activates. Output:
|
|
|
|
```
|
|
crypto_utils/
|
|
├── src/
|
|
│ ├── crypto_utils_impl.h # Pure C++ class (std::string, bool, etc.)
|
|
│ └── crypto_utils_impl.cpp # Implementation stubs
|
|
├── metadata.json # "interface": "universal", "type": "core"
|
|
├── CMakeLists.txt # logos_module() — generated_code globbed automatically
|
|
├── flake.nix # mkLogosModule (universal codegen runs automatically)
|
|
├── tests/
|
|
│ ├── main.cpp # LOGOS_TEST_MAIN() entry point
|
|
│ ├── test_crypto_utils.cpp # Unit tests using LOGOS_TEST() and assertions
|
|
│ └── CMakeLists.txt # logos_test() macro (auto-detected by builder)
|
|
├── CLAUDE.md # Generated: knows this is a universal module
|
|
├── AGENTS.md # Universal context for any AI tool
|
|
└── .mcp.json # MCP server registration
|
|
```
|
|
|
|
**Step 2: Implement business logic in pure C++**
|
|
|
|
```cpp
|
|
#pragma once
|
|
#include <string>
|
|
#include <vector>
|
|
#include <cstdint>
|
|
|
|
class CryptoUtilsImpl {
|
|
public:
|
|
std::string hash(const std::string& input);
|
|
bool verify(const std::string& input, const std::string& hash);
|
|
std::string generateKey(int64_t bits);
|
|
std::vector<std::string> listAlgorithms();
|
|
};
|
|
```
|
|
|
|
No `Q_OBJECT`, no `Q_INVOKABLE`, no `QString`. The code generator handles all Qt integration at build time.
|
|
|
|
**Step 3: Build**
|
|
|
|
```bash
|
|
nix build
|
|
```
|
|
|
|
The codegen runs automatically — `mkLogosModule` invokes the universal pipeline (you write no `preConfigure`). It derives a `.lidl` from the impl header, then emits the Qt-plugin glue and a Qt-free C-ABI export wrapper:
|
|
|
|
```bash
|
|
logos-cpp-generator --header-to-lidl src/crypto_utils_impl.h \
|
|
--impl-class CryptoUtilsImpl --metadata metadata.json \
|
|
-o ./generated_code/crypto_utils.lidl
|
|
logos-qt-host-generator --lidl ./generated_code/crypto_utils.lidl --backend cdylib \
|
|
--output-dir ./generated_code
|
|
logos-cpp-generator --lidl ./generated_code/crypto_utils.lidl --backend cdylib \
|
|
--impl-class CryptoUtilsImpl --impl-header crypto_utils_impl.h \
|
|
--output-dir ./generated_code
|
|
```
|
|
|
|
This produces `generated_code/crypto_utils.lidl`, `crypto_utils_cdylib_glue.{h,cpp}` (uniform Qt-plugin glue), and `crypto_utils_module_impl.cpp` (Qt-free C-ABI export wrapper). `LogosModule.cmake` globs these automatically — you don't list them in `CMakeLists.txt`.
|
|
|
|
**Step 4: Test with logoscore**
|
|
|
|
```bash
|
|
logoscore -D -m ./result/lib &
|
|
logoscore load-module crypto_utils
|
|
logoscore call crypto_utils hash hello_world
|
|
logoscore stop
|
|
```
|
|
|
|
**Step 5: Unit test (no logoscore needed)**
|
|
|
|
```bash
|
|
nix build .#unit-tests -L
|
|
```
|
|
|
|
Unit tests use logos-test-framework (`LOGOS_TEST()` macros, `LOGOS_ASSERT_*`) and instantiate `CryptoUtilsImpl` directly — it is a plain C++ class with no framework dependencies. `logos-module-builder` auto-detects `tests/CMakeLists.txt` and creates the `unit-tests` target.
|
|
|
|
**Step 6: Inter-module communication**
|
|
|
|
Other modules call crypto_utils via LogosAPI:
|
|
|
|
```cpp
|
|
LogosResult result = api->callModule("crypto_utils", "hash", {"hello"});
|
|
if (result.success()) {
|
|
std::string hashValue = result.data().toString().toStdString();
|
|
}
|
|
```
|
|
|
|
**Step 7: Package for distribution**
|
|
|
|
```bash
|
|
lgx create crypto_utils
|
|
lgx add crypto_utils.lgx -v linux-x86_64 -f ./result/lib/crypto_utils_plugin.so
|
|
lgx add crypto_utils.lgx -v darwin-arm64 -f ./result/lib/crypto_utils_plugin.dylib
|
|
lgx verify crypto_utils.lgx
|
|
```
|
|
|
|
**What logos-dev-boost provides at each step:**
|
|
|
|
- Step 1: `init` command scaffolds from universal module template; generated CLAUDE.md/AGENTS.md teach agents the universal pattern
|
|
- Step 2: Guidelines ensure pure C++, no Qt types; the type mapping table is always available
|
|
- Step 3: Build help explains the codegen pipeline; troubleshooting for common generator errors
|
|
- Steps 4-5: Testing skill covers logos-test-framework unit tests (LOGOS_TEST, LogosTestContext, mocking) and logoscore integration tests
|
|
- Step 6: Inter-module comm skill explains LogosAPI patterns and dependency declaration
|
|
- Step 7: Packaging skill covers the full LGX workflow
|
|
|
|
### Journey 2: Wrap an External C/C++ Library as a Module
|
|
|
|
Like `logos-accounts-module` wrapping `go-wallet-sdk`, or a module wrapping libsodium.
|
|
|
|
**Step 1: Scaffold with external lib flag**
|
|
|
|
```bash
|
|
nix run github:logos-co/logos-dev-boost -- init sodium_module --type module --external-lib
|
|
```
|
|
|
|
Output includes `lib/` directory structure and `metadata.json` with `"nix.external_libraries"` pre-configured.
|
|
|
|
**Step 2: Configure the external library in metadata.json**
|
|
|
|
```json
|
|
{
|
|
"nix": {
|
|
"external_libraries": [{
|
|
"name": "libsodium",
|
|
"build_command": "make",
|
|
"output_pattern": "build/libsodium.*"
|
|
}]
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 3: Write impl header wrapping the C API**
|
|
|
|
```cpp
|
|
#pragma once
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
extern "C" {
|
|
#include "lib/sodium.h"
|
|
}
|
|
|
|
class SodiumModuleImpl {
|
|
public:
|
|
std::string encrypt(const std::string& plaintext, const std::string& key);
|
|
std::string decrypt(const std::string& ciphertext, const std::string& key);
|
|
std::string generateKey();
|
|
};
|
|
```
|
|
|
|
The external C API is accessed via `extern "C"` includes. The impl class presents a clean C++ interface that the generator can process.
|
|
|
|
**Steps 4+:** Same as Journey 1 (build, test, package).
|
|
|
|
### Journey 3a: Create a Pure QML UI App
|
|
|
|
A Basecamp UI App with no C++ — QML only, calls backend modules via `logos.callModule()`.
|
|
|
|
**Step 1: Scaffold**
|
|
|
|
```bash
|
|
nix run github:logos-co/logos-dev-boost -- init notes_ui --type ui-qml
|
|
```
|
|
|
|
Output:
|
|
|
|
```
|
|
notes_ui/
|
|
├── Main.qml # QML entry point
|
|
├── metadata.json # "type": "ui_qml", "view": "Main.qml"
|
|
├── flake.nix # mkLogosQmlModule
|
|
├── CLAUDE.md
|
|
└── AGENTS.md
|
|
```
|
|
|
|
**Step 2: Develop the QML UI**
|
|
|
|
```qml
|
|
import QtQuick 2.15
|
|
import QtQuick.Controls 2.15
|
|
|
|
Item {
|
|
Button {
|
|
text: "Save Note"
|
|
onClicked: {
|
|
var result = logos.callModule("storage_module", "save", [noteField.text])
|
|
console.log("Saved:", result)
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 3: Build and run**
|
|
|
|
```bash
|
|
nix build
|
|
nix run . # standalone app with QML Inspector on localhost:3768
|
|
```
|
|
|
|
The QML Inspector MCP server starts automatically. AI agents can use `qml_screenshot`, `qml_find_and_click`, `qml_get_tree`, etc. to interact with and verify the UI. Write `.mjs` test files in `tests/` for headless CI testing via `nix build .#integration-test`.
|
|
|
|
### Journey 3b: Create a QML + C++ Backend UI App
|
|
|
|
A Basecamp UI App with process-isolated C++ backend and QML frontend.
|
|
|
|
**Step 1: Scaffold**
|
|
|
|
```bash
|
|
nix run github:logos-co/logos-dev-boost -- init notes_app --type ui-qml-backend
|
|
```
|
|
|
|
Output:
|
|
|
|
```
|
|
notes_app/
|
|
├── src/
|
|
│ ├── notes_app.rep # Qt Remote Objects interface
|
|
│ ├── notes_app_interface.h # extends PluginInterface
|
|
│ ├── notes_app_plugin.h # SimpleSource + ViewPluginBase
|
|
│ ├── notes_app_plugin.cpp # implementation
|
|
│ └── qml/
|
|
│ └── Main.qml # QML frontend (logos.module() replica)
|
|
├── metadata.json # "type": "ui_qml", "main": "notes_app_plugin"
|
|
├── CMakeLists.txt # REP_FILE
|
|
├── flake.nix # mkLogosQmlModule
|
|
├── CLAUDE.md
|
|
└── AGENTS.md
|
|
```
|
|
|
|
**Step 2: Define the backend interface (.rep file)**
|
|
|
|
```
|
|
class NotesApp
|
|
{
|
|
PROP(QString status READWRITE)
|
|
PROP(QVariantList notes READWRITE)
|
|
SLOT(void addNote(const QString& title))
|
|
SLOT(void deleteNote(int index))
|
|
}
|
|
```
|
|
|
|
**Step 3: Implement the C++ backend**
|
|
|
|
```cpp
|
|
class NotesAppPlugin : public NotesAppSimpleSource,
|
|
public NotesAppInterface,
|
|
public NotesAppViewPluginBase
|
|
{
|
|
Q_OBJECT
|
|
Q_PLUGIN_METADATA(IID NotesAppInterface_iid FILE "metadata.json")
|
|
Q_INTERFACES(NotesAppInterface)
|
|
|
|
public:
|
|
Q_INVOKABLE void initLogos(LogosAPI* api) {
|
|
m_logosAPI = api;
|
|
setBackend(this);
|
|
}
|
|
|
|
void addNote(const QString& title) override { /* ... */ }
|
|
void deleteNote(int index) override { /* ... */ }
|
|
};
|
|
```
|
|
|
|
**Step 4: Develop the QML frontend**
|
|
|
|
```qml
|
|
import QtQuick
|
|
import QtQuick.Controls
|
|
|
|
Item {
|
|
id: root
|
|
|
|
readonly property var backend: logos.module("notes_app")
|
|
property bool ready: false
|
|
|
|
Connections {
|
|
target: logos
|
|
function onViewModuleReadyChanged(moduleName, isReady) {
|
|
if (moduleName === "notes_app")
|
|
root.ready = isReady && root.backend !== null;
|
|
}
|
|
}
|
|
Component.onCompleted: {
|
|
root.ready = root.backend !== null && logos.isViewModuleReady("notes_app");
|
|
}
|
|
|
|
ListView {
|
|
model: backend ? backend.notes : []
|
|
delegate: Text { text: modelData.title }
|
|
}
|
|
|
|
Button {
|
|
text: "Add Note"
|
|
enabled: root.ready
|
|
onClicked: logos.watch(backend.addNote("New Note"),
|
|
function() { console.log("Added") },
|
|
function(err) { console.log("Error:", err) }
|
|
)
|
|
}
|
|
}
|
|
```
|
|
|
|
**Step 5: Build and test**
|
|
|
|
```bash
|
|
nix build
|
|
nix run . # standalone app with QML Inspector on localhost:3768
|
|
```
|
|
|
|
AI agents can test the running UI via MCP tools (`qml_screenshot`, `qml_find_and_click`, etc.). Write `.mjs` test files in `tests/` for headless CI via `nix build .#integration-test`.
|
|
|
|
**The C++/QML boundary** (taught by guidelines):
|
|
|
|
| Concern | Goes in C++ | Goes in QML |
|
|
|---------|-------------|-------------|
|
|
| Data models, state | `PROP()` in `.rep` file | Bind to `backend.property` |
|
|
| Business logic | `SLOT()` in `.rep` + implement in plugin | Never — no JS business logic |
|
|
| Module calls | `LogosAPI*` in `initLogos()` | `logos.callModule()` (pure QML only) |
|
|
| File I/O, networking | Always C++ | Never |
|
|
| UI layout, styling | Never | Always — `Logos.Theme`, `Logos.Controls` |
|
|
| User interactions | `SLOT()` methods | `logos.watch(backend.doX())` |
|
|
| Plugin lifecycle | `initLogos()` + `setBackend(this)` | N/A |
|
|
|
|
### Journey 4: AI Agent Building a Module from Scratch
|
|
|
|
What happens when a developer tells an AI agent "create a module that provides encryption utilities":
|
|
|
|
1. Agent reads AGENTS.md (always loaded) — knows about universal interface, Logos ecosystem, type system, build pipeline. This is the critical difference from not having logos-dev-boost.
|
|
|
|
2. Agent activates `create-universal-module` skill — gets step-by-step template with correct file structure, `metadata.json` schema, and the `mkLogosModule` `flake.nix` pattern (universal codegen runs automatically).
|
|
|
|
3. Agent writes pure C++ impl header — guidelines ensure it uses `std::string` not `QString`, `int64_t` not `int`, returns meaningful types from the type mapping table.
|
|
|
|
4. Agent writes `flake.nix` — skill provides the exact `mkLogosModule` template (universal codegen runs automatically; no `preConfigure`) with the correct `logos-module-builder` input.
|
|
|
|
5. Agent builds with `nix build` — build-help guidelines explain the pipeline. If errors occur, agent knows common fixes: generator type mapping issues, missing `find_package`, `metadata.json`/header class name mismatch.
|
|
|
|
6. Agent runs unit tests with `nix build .#unit-tests -L` — the scaffolded `tests/` directory uses logos-test-framework (`LOGOS_TEST()`, `LOGOS_ASSERT_*`). Tests are auto-detected by `logos-module-builder`. Agent also tests with `logoscore` for integration testing — testing skill provides exact commands and expected output patterns.
|
|
|
|
**Without logos-dev-boost:** Agent would write `Q_INVOKABLE` methods, use `QString` everywhere, try `cmake --build` instead of `nix build`, hallucinate a `LogosPlugin` base class that doesn't exist, and have no idea about the code generator pipeline.
|
|
|
|
### Journey 5: Installing logos-dev-boost for an Existing Project
|
|
|
|
For a developer with an existing Logos module who wants AI assistance:
|
|
|
|
**Step 1: Run the installer**
|
|
|
|
```bash
|
|
nix run github:logos-co/logos-dev-boost -- install
|
|
```
|
|
|
|
**Step 2: Interactive configuration**
|
|
|
|
```
|
|
Detected: Universal C++ module (accounts_module)
|
|
SDK version: logos-cpp-sdk 0.3.0
|
|
|
|
Which AI tools do you use?
|
|
[x] Claude Code
|
|
[x] Cursor
|
|
[ ] Codex
|
|
[ ] Gemini CLI
|
|
|
|
Generated:
|
|
CLAUDE.md (always-loaded context for Claude Code)
|
|
AGENTS.md (universal context for any AI tool)
|
|
.cursor/rules/logos.mdc (Cursor-specific rules)
|
|
.claude/skills/ (8 skills for Claude Code)
|
|
.mcp.json (MCP server registration)
|
|
.logos-dev-boost/ (pre-built MCP server binary)
|
|
```
|
|
|
|
**Step 3: AI tools auto-detect configuration**
|
|
|
|
- Claude Code reads `CLAUDE.md` automatically, discovers `.claude/skills/`, connects to MCP server via `.mcp.json`
|
|
- Cursor reads `AGENTS.md` automatically, loads `.cursor/rules/logos.mdc`, connects to MCP server
|
|
- Manual fallback if auto-detection fails:
|
|
- Claude Code: `claude mcp add -s local -t stdio logos-dev-boost node .logos-dev-boost/mcp-server/index.js`
|
|
- Cursor: Command Palette -> "/open MCP Settings" -> toggle on `logos-dev-boost`
|
|
- Codex: `codex mcp add logos-dev-boost -- node .logos-dev-boost/mcp-server/index.js`
|
|
|
|
## Features & Requirements
|
|
|
|
### Phase 1: Foundation (MVP)
|
|
|
|
- Always-loaded context files (AGENTS.md, CLAUDE.md) with compressed documentation index
|
|
- 7 guideline files covering core conventions, universal modules, UI apps, Nix build, testing, metadata.json, and code generation
|
|
- 8 on-demand skills for common development tasks
|
|
- Scaffolding templates for universal modules, external library modules, and UI apps
|
|
- Context file generators (AGENTS.md, CLAUDE.md, .cursor/rules, llms.txt)
|
|
- Nix flake with `init`, `install`, and `generate` commands
|
|
|
|
### Phase 2: MCP Server
|
|
|
|
- Live project introspection via 5 MCP tools (project-info, search-docs, api-reference, build-help, scaffold)
|
|
- Full-text documentation search over bundled docs
|
|
- Context-aware build commands with troubleshooting
|
|
- Interactive installer that detects AI tools and generates per-tool configuration
|
|
|
|
### Phase 3: Rich Features
|
|
|
|
- Semantic documentation search with local ONNX embeddings
|
|
- Cross-repo dependency graph tool
|
|
- LIDL language validation and preview
|
|
- Integration with logos-qt-mcp for combined dev-time and runtime introspection
|
|
|
|
### Phase 4: Ecosystem
|
|
|
|
- Third-party module skills (module authors ship skills in their repos)
|
|
- Hosted documentation API with centralized semantic search
|
|
- CI integration (`logos-dev-boost check` validates project configuration)
|
|
- Auto-update for context files when dependencies change
|
|
|
|
## Success Metrics
|
|
|
|
1. **Module creation time** — An AI agent can scaffold, build, and test a new universal C++ module in under 5 minutes (currently impossible without deep knowledge)
|
|
2. **Zero hallucinated APIs** — Agents never suggest non-existent Logos APIs or use Qt types in universal module code
|
|
3. **Build success rate** — Agent-generated Nix flakes and C++ impl headers build on first try
|
|
4. **Correct interface choice** — Agents use the universal interface for modules and ui_qml for UI apps, never mixing the two
|
|
5. **Onboarding time** — New human developers can create their first module in under 30 minutes with AI assistance
|
|
|
|
## Supported Platforms
|
|
|
|
logos-dev-boost runs on any platform with Nix:
|
|
- Linux (x86_64, aarch64)
|
|
- macOS (x86_64, aarch64)
|
|
|
|
The generated context files (AGENTS.md, CLAUDE.md, skills) are plain text and work on any platform.
|
|
|
|
|
|
---
|
|
|
|
# Project Description
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
logos-dev-boost/
|
|
├── docs/ # Documentation (this directory)
|
|
│ ├── index.md # Entry point
|
|
│ ├── spec.md # Domain spec: purpose, journeys, features
|
|
│ └── project.md # Implementation details (this file)
|
|
├── guidelines/ # Always-loaded AI conventions
|
|
│ ├── core.md # Two component types, naming, file structure
|
|
│ ├── universal-module.md # Pure C++ impl pattern, type mapping, codegen
|
|
│ ├── ui-app.md # ui_qml apps: pure QML + QML with C++ backend
|
|
│ ├── nix-build.md # Flake structure, build commands, overrides
|
|
│ ├── testing.md # logoscore, unit tests, TEST_GROUPS
|
|
│ ├── metadata-json.md # Full metadata.json schema
|
|
│ └── codegen.md # logos-cpp-generator pipeline, LIDL, types
|
|
├── skills/ # On-demand task knowledge (agentskills.io)
|
|
│ ├── create-universal-module/ # Scaffold + implement a universal C++ module
|
|
│ ├── wrap-external-lib/ # Wrap a C/C++ library as a module
|
|
│ ├── create-ui-app/ # QML UI app for Basecamp (pure QML or QML + backend)
|
|
│ ├── package-lgx/ # Create + distribute LGX packages
|
|
│ ├── inter-module-comm/ # LogosAPI patterns, dependency declaration
|
|
│ ├── testing-modules/ # logoscore + unit testing patterns
|
|
│ ├── nix-flake-setup/ # Flake config, overrides, workspace integration
|
|
│ └── add-to-workspace/ # Register module in logos-workspace
|
|
├── templates/ # Scaffolding templates (used by init command)
|
|
│ ├── universal-module/ # Pure C++ module
|
|
│ ├── universal-module-extlib/ # With external library wrapping
|
|
│ └── ui-app/ # QML UI app (pure QML + QML with backend)
|
|
├── generators/ # Context file generators (TypeScript)
|
|
│ ├── generate-agents-md.ts # AGENTS.md with compressed docs index
|
|
│ ├── generate-claude-md.ts # CLAUDE.md (imports AGENTS.md content)
|
|
│ ├── generate-cursor-rules.ts # .cursor/rules/logos.mdc
|
|
│ └── generate-llms-txt.ts # llms.txt from docs/
|
|
├── mcp-server/ # MCP server (TypeScript, stdio transport)
|
|
│ ├── index.ts # Server entry point
|
|
│ └── tools/ # MCP tool implementations
|
|
│ ├── project-info.ts # SDK version, module type, build targets
|
|
│ ├── search-docs.ts # Full-text search over docs/ (fuse.js)
|
|
│ ├── api-reference.ts # Type system, LogosAPI, LogosResult
|
|
│ ├── build-help.ts # Context-aware build commands
|
|
│ └── scaffold.ts # Wraps init templates
|
|
├── installer/ # One-command setup
|
|
│ ├── cli.ts # CLI entry point (init, install, generate)
|
|
│ └── install.ts # IDE detection, file generation
|
|
├── tests/ # Tests
|
|
├── flake.nix # Nix package definition
|
|
├── package.json # Node.js package
|
|
├── tsconfig.json # TypeScript configuration
|
|
└── README.md
|
|
```
|
|
|
|
## Stack, Frameworks & Dependencies
|
|
|
|
| Component | Technology | Purpose |
|
|
|-----------|-----------|---------|
|
|
| MCP server | TypeScript, `@modelcontextprotocol/sdk` | AI agent tool integration via stdio |
|
|
| Documentation search | fuse.js | Full-text search over bundled docs |
|
|
| Context generators | TypeScript, Node.js | Generate AGENTS.md, CLAUDE.md, .cursor/rules |
|
|
| Scaffolding | Nix flake templates, `logos-module-builder` | Project initialization |
|
|
| Build/packaging | Nix flakes | Self-contained offline-capable distribution |
|
|
|
|
### External Logos Dependencies
|
|
|
|
| Dependency | Role | Source |
|
|
|-----------|------|--------|
|
|
| logos-module-builder | Nix flake templates for module scaffolding | github:logos-co/logos-module-builder |
|
|
| logos-cpp-sdk | SDK headers, code generator (`logos-cpp-generator`) | github:logos-co/logos-cpp-sdk |
|
|
| logos-liblogos | Runtime library documentation reference | github:logos-co/logos-liblogos |
|
|
|
|
## Components
|
|
|
|
### Guidelines
|
|
|
|
Guidelines are Markdown files loaded into the always-on context (AGENTS.md / CLAUDE.md). Each is kept under 2000 tokens to minimize context cost while providing essential conventions.
|
|
|
|
| Guideline | Content |
|
|
|-----------|---------|
|
|
| `core.md` | Two component types (Universal Module vs UI App), naming conventions, file structure, `metadata.json` as source of truth |
|
|
| `universal-module.md` | Pure C++ impl pattern, type mapping table, `"interface": "universal"`, impl class naming (`<name>_impl.h`), public methods = module API |
|
|
| `ui-app.md` | `ui_qml` apps: pure QML (logos.callModule bridge) and QML + C++ backend (Qt Remote Objects, .rep file, SimpleSource + ViewPluginBase, logos.module() replica) |
|
|
| `nix-build.md` | Flake structure, `follows` declarations, `preConfigure` for codegen, build commands, `--auto-local` |
|
|
| `testing.md` | Unit tests (call impl class directly), logoscore integration tests, `TEST_GROUPS`, `nix flake check` |
|
|
| `metadata-json.md` | Full schema including `"interface"`, `"dependencies"`, `"nix"` config, `"external_libraries"`, `"cmake"` settings |
|
|
| `codegen.md` | header-first cdylib pipeline, C++ to LIDL type mapping, generated file structure, LIDL format |
|
|
|
|
### Skills
|
|
|
|
Skills are on-demand knowledge modules following the agentskills.io specification. Each lives in its own directory with a `SKILL.md` file containing YAML frontmatter (`name`, `description`) and Markdown instructions.
|
|
|
|
| Skill | Trigger | Content |
|
|
|-------|---------|---------|
|
|
| `create-universal-module` | Creating a new Logos module | Full scaffold: metadata.json, flake.nix with preConfigure, CMakeLists.txt, impl header template |
|
|
| `wrap-external-lib` | Wrapping a C/C++ library | External library config, `extern "C"` patterns, based on logos-accounts-module |
|
|
| `create-ui-app` | Creating a Basecamp UI app | Two paths: pure QML (Main.qml + logos.callModule) or QML + C++ backend (.rep, SimpleSource, ViewPluginBase, logos.module replica) |
|
|
| `package-lgx` | Packaging for distribution | lgx create/add/verify workflow, portable builds, nix-bundle-lgx |
|
|
| `inter-module-comm` | Module-to-module calls | `LogosAPI::callModule()`, dependency declaration, `LogosResult` handling |
|
|
| `testing-modules` | Writing tests | Unit tests, logoscore integration, TEST_GROUPS, mock transport |
|
|
| `nix-flake-setup` | Nix configuration | Flake template, inputs, follows, preConfigure, override-input |
|
|
| `add-to-workspace` | Registering in logos-workspace | flake.nix inputs, scripts/ws REPOS, dep-graph.nix |
|
|
|
|
### Generators
|
|
|
|
TypeScript scripts that produce IDE-specific context files from the guidelines and documentation:
|
|
|
|
| Generator | Output | Description |
|
|
|-----------|--------|-------------|
|
|
| `generate-agents-md.ts` | `AGENTS.md` | Universal context file. Compressed docs index (~8-15KB), conventions, type system. Works with all AI tools. |
|
|
| `generate-claude-md.ts` | `CLAUDE.md` | Claude Code-specific context. Imports AGENTS.md content plus Claude-specific skill references. |
|
|
| `generate-cursor-rules.ts` | `.cursor/rules/logos.mdc` | Cursor-specific rules with file glob patterns for activation. |
|
|
| `generate-llms-txt.ts` | `llms.txt`, `llms-full.txt` | Machine-readable documentation index following the llms.txt specification. |
|
|
|
|
Generators detect the project type from `metadata.json` (`"interface": "universal"` vs `"type": "ui_qml"`) and include context specific to that project type.
|
|
|
|
### MCP Server
|
|
|
|
TypeScript MCP server using `@modelcontextprotocol/sdk` with stdio transport. Provides live project introspection tools.
|
|
|
|
| Tool | Input | Output |
|
|
|------|-------|--------|
|
|
| `logos_project_info` | (none — reads current directory) | Project type, interface type, SDK version, dependencies, build targets |
|
|
| `logos_search_docs` | `{ "query": "..." }` | Ranked search results from bundled documentation |
|
|
| `logos_api_reference` | `{ "interface": "LogosAPI" }` | Type mapping table, method signatures, usage examples |
|
|
| `logos_build_help` | `{ "action": "build" }` | Context-aware build commands, codegen pipeline explanation, troubleshooting |
|
|
| `logos_scaffold` | `{ "name": "...", "type": "module" }` | Creates project from template, returns file list and next steps |
|
|
|
|
### Installer
|
|
|
|
The installer (`install.ts`) performs interactive setup:
|
|
|
|
1. Detects project type from `flake.nix` inputs and `metadata.json`
|
|
2. Detects installed AI tools (checks for `.claude/`, `.cursor/`, presence of `claude`/`codex`/`gemini` in PATH)
|
|
3. Generates appropriate context files per detected tool
|
|
4. Builds MCP server locally (via Nix) to `.logos-dev-boost/`
|
|
5. Writes `.mcp.json` pointing to the built MCP server binary
|
|
6. Installs skills to tool-specific directories
|
|
|
|
## CLI Reference
|
|
|
|
### `logos-dev-boost init`
|
|
|
|
Scaffold a new Logos project.
|
|
|
|
```bash
|
|
nix run github:logos-co/logos-dev-boost -- init <name> --type <module|ui-qml|ui-qml-backend> [--external-lib]
|
|
```
|
|
|
|
| Argument | Description |
|
|
|----------|-------------|
|
|
| `<name>` | Project name (snake_case, e.g., `crypto_utils`) |
|
|
| `--type module` | Universal C++ module (default) |
|
|
| `--type ui-qml` | Pure QML UI app for Basecamp (no C++) |
|
|
| `--type ui-qml-backend` | QML + process-isolated C++ backend UI app |
|
|
| `--external-lib` | Add external library wrapping scaffold (modules only) |
|
|
|
|
Creates the directory, generates the appropriate template (mkLogosModule for modules, mkLogosQmlModule for UI apps), adds AI context files, and initializes git.
|
|
|
|
### `logos-dev-boost install`
|
|
|
|
Configure AI tools for an existing Logos project.
|
|
|
|
```bash
|
|
nix run github:logos-co/logos-dev-boost -- install
|
|
```
|
|
|
|
Interactive prompt asks which AI tools to configure. Generates context files and MCP server registration.
|
|
|
|
### `logos-dev-boost generate`
|
|
|
|
Regenerate context files (useful after updating dependencies or documentation).
|
|
|
|
```bash
|
|
nix run github:logos-co/logos-dev-boost -- generate [--agents-md] [--claude-md] [--cursor-rules] [--llms-txt]
|
|
```
|
|
|
|
Without flags, regenerates all context files. With flags, regenerates only the specified files.
|
|
|
|
## Operational
|
|
|
|
### Building logos-dev-boost
|
|
|
|
```bash
|
|
# Install dependencies
|
|
npm install
|
|
|
|
# Build TypeScript
|
|
npm run build
|
|
|
|
# Or via Nix (produces self-contained package)
|
|
nix build
|
|
```
|
|
|
|
### Testing
|
|
|
|
```bash
|
|
# Run tests
|
|
npm test
|
|
|
|
# Via Nix
|
|
nix flake check
|
|
```
|
|
|
|
### Nix Package Outputs
|
|
|
|
```nix
|
|
packages.default # Full logos-dev-boost (docs + guidelines + skills + generators + MCP server)
|
|
packages.docs # Documentation bundle only
|
|
apps.default # CLI entry point (init, install, generate)
|
|
apps.mcp-server # MCP server only
|
|
```
|
|
|
|
## Extension Points
|
|
|
|
### Custom Guidelines
|
|
|
|
Add `.md` files to your project's `.ai/guidelines/` directory. These are merged with logos-dev-boost guidelines when generating AGENTS.md / CLAUDE.md.
|
|
|
|
### Custom Skills
|
|
|
|
Add `SKILL.md` files to your project's `.ai/skills/<skill-name>/` directory. These are installed alongside logos-dev-boost skills.
|
|
|
|
### Third-Party Module Skills
|
|
|
|
Module authors can ship skills in their repos at `resources/boost/skills/<skill-name>/SKILL.md`. When a project depends on that module and runs `logos-dev-boost install`, these skills are automatically discovered and installed.
|
|
|
|
## Relationship to logos-module-builder
|
|
|
|
logos-dev-boost wraps `logos-module-builder` for scaffolding — it does not duplicate its templates. The `init` command calls `nix flake init -t logos-module-builder` (appropriate variant) and then layers on:
|
|
|
|
- Universal module impl header template (for `--type module`)
|
|
- Pure QML template (for `--type ui-qml`) or QML + C++ backend template (for `--type ui-qml-backend`)
|
|
- AI context files (AGENTS.md, CLAUDE.md, .mcp.json)
|
|
- Test skeleton
|
|
|
|
This means logos-dev-boost always uses the latest logos-module-builder templates for Nix/CMake scaffolding.
|
|
|
|
## Consumers
|
|
|
|
- AI coding agents (Claude Code, Cursor, Copilot, Codex, Gemini) via AGENTS.md, CLAUDE.md, skills, and MCP
|
|
- Human developers via documentation, scaffolding, and generated project structure
|
|
- CI/CD pipelines via `logos-dev-boost check` (Phase 4)
|
|
|
|
|
|
---
|
|
|
|
# Code Generator (logos-cpp-generator)
|
|
|
|
## Overview
|
|
|
|
`logos-cpp-generator` bridges pure C++ module implementations to the Logos runtime's Qt plugin system. Module authors write standard C++ and the generator produces all Qt boilerplate automatically.
|
|
|
|
Universal modules are **header-first cdylibs**: the generator derives a LIDL contract from your impl header, then emits a Qt-free cdylib (exporting the common module-impl C ABI) wrapped by a uniform Qt-plugin glue that `logos_host` loads unchanged. Your module's own translation units stay Qt-free — Qt appears only in the generated glue.
|
|
|
|
## The universal pipeline
|
|
|
|
This is how universal modules are built. You write only the impl class; `logos-module-builder` runs every step below for you in `preConfigure` (see [In flake.nix](#in-flakenix)).
|
|
|
|
```
|
|
C++ impl header (your code)
|
|
│
|
|
▼ logos-cpp-generator --header-to-lidl
|
|
parseImplHeader() — extracts public methods, maps C++ types to LIDL types
|
|
│
|
|
▼
|
|
<name>.lidl (derived interface contract; also the events sidecar dependents consume)
|
|
│
|
|
├──► logos-qt-host-generator --lidl --backend cdylib
|
|
│ └──► <name>_cdylib_glue.h / <name>_cdylib_glue.cpp
|
|
│ — uniform Qt-plugin glue over the module-impl C ABI
|
|
│
|
|
└──► logos-cpp-generator --lidl --backend cdylib
|
|
└──► <name>_module_impl.cpp
|
|
— Qt-free C-ABI export wrapper around your impl class
|
|
```
|
|
|
|
### Commands
|
|
|
|
```bash
|
|
# 1. Derive the LIDL contract from your impl header.
|
|
logos-cpp-generator --header-to-lidl src/<name>_impl.h \
|
|
--impl-class <ImplClassName> \
|
|
--metadata metadata.json \
|
|
-o ./generated_code/<name>.lidl
|
|
|
|
# 2. Generate the uniform Qt-plugin glue (logos_host loads it unchanged).
|
|
logos-qt-host-generator --lidl ./generated_code/<name>.lidl \
|
|
--backend cdylib \
|
|
--output-dir ./generated_code
|
|
|
|
# 3. Generate the Qt-free C-ABI export wrapper (+ typed event emitters)
|
|
# around your hand-written impl class.
|
|
logos-cpp-generator --lidl ./generated_code/<name>.lidl \
|
|
--backend cdylib \
|
|
--impl-class <ImplClassName> \
|
|
--impl-header <name>_impl.h \
|
|
--output-dir ./generated_code
|
|
```
|
|
|
|
You never run these by hand — `mkLogosModule` invokes them automatically when `metadata.json` declares `"interface": "universal"`.
|
|
|
|
| Flag | Description |
|
|
|------|-------------|
|
|
| `--header-to-lidl <path>` | Path to the pure C++ impl header to derive the LIDL contract from |
|
|
| `--lidl <path>` | Path to a `.lidl` contract (steps 2 & 3 consume the file emitted by step 1) |
|
|
| `--backend cdylib` | Emit the cdylib module-impl C ABI artifacts (glue + export wrapper) |
|
|
| `--impl-class <name>` | Name of the C++ implementation class (PascalCase + `Impl`) |
|
|
| `--impl-header <name>` | Header filename (for include directives in generated code) |
|
|
| `--metadata <path>` | Path to metadata.json (provides name, version, description) |
|
|
| `-o <path>` / `--output-dir <path>` | Output `.lidl` file (step 1) / directory for generated files (steps 2 & 3) |
|
|
|
|
### Generated Files
|
|
|
|
All land in `generated_code/`. Don't edit them and don't list them in `CMakeLists.txt` — `LogosModule.cmake` globs them automatically (see [In CMakeLists.txt](#in-cmakeliststxt)).
|
|
|
|
**`<name>.lidl`** — the interface contract derived from your impl header. Doubles as the published events sidecar that dependents' typed-event codegen consumes.
|
|
|
|
**`<name>_cdylib_glue.h` / `<name>_cdylib_glue.cpp`** — the uniform Qt-plugin glue. A `QObject` subclass with `Q_PLUGIN_METADATA` + `Q_INTERFACES` and a `LogosProviderObject` that marshals method calls to JSON and forwards them to the cdylib's module-impl C ABI (`dispatch` / `getMethods` / `set_context` / emit callback / `accept_token`). The glue is identical regardless of the module's source language — it only knows the C ABI.
|
|
|
|
**`<name>_module_impl.cpp`** — the Qt-free C-ABI export wrapper. Implements the common module-impl C ABI (`logos_module_impl.h`) around your hand-written impl class, plus typed event emitters. This translation unit links no Qt; it is what makes a universal module a cdylib.
|
|
|
|
### Type Mapping Table
|
|
|
|
| C++ type | LIDL type | JSON / wire |
|
|
|----------|-----------|-------------|
|
|
| `std::string` / `const std::string&` | `tstr` | string |
|
|
| `bool` | `bool` | bool |
|
|
| `int64_t` | `int` | number |
|
|
| `uint64_t` | `uint` | number |
|
|
| `double` | `float64` | number |
|
|
| `void` | `void` | — |
|
|
| `std::vector<std::string>` | `[tstr]` | array of string |
|
|
| `std::vector<uint8_t>` | `bstr` | `{"_bytes":"<base64url>"}` |
|
|
| `std::vector<int64_t>` | `[int]` | array of number |
|
|
| `LogosMap` | `{tstr: any}` | object |
|
|
| `LogosList` | `[any]` | array |
|
|
| Anything else | `any` | any |
|
|
|
|
`LogosMap` and `LogosList` (from `<logos_json.h>`) are `nlohmann::json` aliases for returning structured data without Qt. The generator sets a `jsonReturn` flag on these methods so the dispatch layer carries the JSON through faithfully.
|
|
|
|
## LIDL (define the contract first)
|
|
|
|
LIDL is a lightweight Interface Definition Language. The universal pipeline derives a `.lidl` from your header automatically (step 1 above), but you can also hand-write one to define the interface before the implementation:
|
|
|
|
```
|
|
module crypto_utils {
|
|
version "1.0.0"
|
|
description "Cryptographic utilities"
|
|
|
|
method hash(input: tstr) -> tstr
|
|
method verify(input: tstr, hash: tstr) -> bool
|
|
method generateKey(bits: int) -> tstr
|
|
method listAlgorithms() -> [tstr]
|
|
}
|
|
```
|
|
|
|
A hand-written `.lidl` feeds steps 2 & 3 directly (this is the `cdylib` interface; the `universal` interface just derives the `.lidl` from your header first). Both routes produce identical generated output.
|
|
|
|
## Common Issues
|
|
|
|
- **Unknown type warning**: If the generator encounters a C++ type not in the mapping table, it maps to `any`. Prefer explicit types from the table.
|
|
- **Class not found**: `--impl-class` must exactly match the class name in the header (case-sensitive).
|
|
- **metadata.json mismatch**: The `name` in metadata.json must match the expected plugin binary name.
|
|
- **Generated files not found by CMake**: You do *not* list `generated_code/` files in `SOURCES` — `LogosModule.cmake` globs them. Just make sure `generated_code` is in `INCLUDE_DIRS`.
|
|
|
|
## In CMakeLists.txt
|
|
|
|
List only your own sources. `LogosModule.cmake` globs `generated_code/*.cpp` and `*.h` automatically (excluding `logos_sdk`/`*_api`), so the generated glue is picked up without being named:
|
|
|
|
```cmake
|
|
logos_module(
|
|
NAME my_module
|
|
SOURCES
|
|
src/my_module_impl.h
|
|
src/my_module_impl.cpp
|
|
INCLUDE_DIRS
|
|
${CMAKE_CURRENT_SOURCE_DIR}/generated_code
|
|
)
|
|
```
|
|
|
|
## In flake.nix
|
|
|
|
You don't write a `preConfigure` — `mkLogosModule` runs the universal pipeline for you when `metadata.json` sets `"interface": "universal"`:
|
|
|
|
```nix
|
|
{
|
|
inputs = {
|
|
logos-module-builder.url = "github:logos-co/logos-module-builder";
|
|
nix-bundle-lgx.url = "github:logos-co/nix-bundle-lgx";
|
|
};
|
|
|
|
outputs = inputs@{ logos-module-builder, ... }:
|
|
logos-module-builder.lib.mkLogosModule {
|
|
src = ./.;
|
|
configFile = ./metadata.json;
|
|
flakeInputs = inputs;
|
|
};
|
|
}
|
|
```
|
|
|
|
|
|
---
|
|
|
|
# Logos Core Conventions
|
|
|
|
## Two Component Types
|
|
|
|
Logos has two fundamentally different types of components. Always identify which you are building before writing code.
|
|
|
|
**Logos Modules** (`"type": "core"`) — Process-isolated backend services. Pure C++ implementation using standard types. No Qt types in user code. All Qt glue is generated at build time. Loaded by `logoscore` or `liblogos_core`. Each runs in its own `logos_host` subprocess.
|
|
|
|
**UI Apps** (`"type": "ui_qml"`) — QML-based UI apps displayed as tabs in Basecamp's MDI workspace. Two subtypes: pure QML (no C++, calls modules via `logos.callModule()`) or QML + C++ backend (process-isolated via Qt Remote Objects, QML gets a typed replica via `logos.module()`).
|
|
|
|
**Rule:** Never mix these. A module is either core (headless, universal interface) or UI (visual, ui_qml). If something needs both backend logic and a UI, create a core module for the logic and a separate UI app that calls it, or use a QML + backend app. The `full-app` scaffold type does exactly this — it creates a `module/` subdirectory (core) and a `ui/` subdirectory (UI app) with a shared root.
|
|
|
|
## Full App Layout
|
|
|
|
When a project requires both a module and a UI, use the `full-app` layout:
|
|
|
|
```
|
|
logos-<name>/ # Open this in your IDE
|
|
<name>-module/ # Universal C++ module (core backend)
|
|
metadata.json # "type": "core", "interface": "universal"
|
|
flake.nix # standalone — cd <name>-module && nix build
|
|
src/<name>_impl.h
|
|
src/<name>_impl.cpp
|
|
<name>-ui/ # Basecamp UI app (frontend)
|
|
metadata.json # "type": "ui_qml", "dependencies": ["<name>"]
|
|
flake.nix # includes <name>.url = "path:../<name>-module"
|
|
src/<name>_ui_plugin.h/cpp
|
|
src/<Pascal>UiBackend.h/cpp
|
|
src/qml/Main.qml
|
|
project.json # { "type": "full-app", "name": "<name>" }
|
|
AGENTS.md / CLAUDE.md / .mcp.json
|
|
```
|
|
|
|
Scaffold with: `logos-dev-boost init <name> --type full-app`
|
|
|
|
Each sub-project is a standalone flake — build inside the sub-directory:
|
|
```bash
|
|
cd <name>-module && git init && git add -A && nix build
|
|
cd ../<name>-ui && git init && git add -A && nix build
|
|
```
|
|
|
|
## Module Naming
|
|
|
|
- Module names use `snake_case`: `crypto_utils`, `accounts_module`, `storage_module`
|
|
- Impl class is `PascalCase` + `Impl`: `CryptoUtilsImpl`, `AccountsModuleImpl`
|
|
- Impl header is `<name>_impl.h`: `crypto_utils_impl.h`
|
|
- Plugin binary is `<name>_plugin.so/.dylib`: `crypto_utils_plugin.so`
|
|
- The `name` in `metadata.json` must match the binary name prefix exactly
|
|
|
|
## File Structure
|
|
|
|
Universal module:
|
|
```
|
|
my_module/
|
|
├── src/
|
|
│ ├── my_module_impl.h # Public API (pure C++ types)
|
|
│ └── my_module_impl.cpp # Implementation
|
|
├── metadata.json # "interface": "universal", "type": "core"
|
|
├── CMakeLists.txt # logos_module() macro
|
|
├── flake.nix # preConfigure runs logos-cpp-generator
|
|
└── tests/
|
|
```
|
|
|
|
Pure QML app:
|
|
```
|
|
my_app/
|
|
├── Main.qml # QML entry point
|
|
├── metadata.json # "type": "ui_qml", "view": "Main.qml"
|
|
└── flake.nix # mkLogosQmlModule
|
|
```
|
|
|
|
QML + C++ backend app:
|
|
```
|
|
my_app/
|
|
├── src/
|
|
│ ├── my_app.rep # Qt Remote Objects interface
|
|
│ ├── my_app_interface.h # extends PluginInterface
|
|
│ ├── my_app_plugin.h/cpp # SimpleSource + ViewPluginBase
|
|
│ └── qml/Main.qml # QML frontend (logos.module() replica)
|
|
├── metadata.json # "type": "ui_qml", "main": "my_app_plugin"
|
|
├── CMakeLists.txt # logos_module() with REP_FILE
|
|
└── flake.nix # mkLogosQmlModule
|
|
```
|
|
|
|
## metadata.json Is the Source of Truth
|
|
|
|
Every module and UI app must have a `metadata.json`. It declares identity, type, interface, dependencies, and build configuration. The `name` field must match the binary name prefix. The `dependencies` array must list exact `name` values from dependent modules' own `metadata.json` files.
|
|
|
|
## Inter-Module Communication
|
|
|
|
All cross-module calls go through `LogosAPI`:
|
|
```cpp
|
|
LogosResult result = api->callModule("module_name", "method_name", {arg1, arg2});
|
|
if (result.success()) {
|
|
QVariant data = result.data();
|
|
}
|
|
```
|
|
|
|
Always handle the case where a target module is not loaded. Always declare dependencies in `metadata.json`.
|
|
|
|
|
|
---
|
|
|
|
# metadata.json Schema
|
|
|
|
## Full Schema
|
|
|
|
```json
|
|
{
|
|
"name": "my_module",
|
|
"version": "1.0.0",
|
|
"description": "What this module does",
|
|
"author": "Author Name",
|
|
"type": "core",
|
|
"interface": "universal",
|
|
"category": "general",
|
|
"main": "my_module_plugin",
|
|
"dependencies": [],
|
|
"include": [],
|
|
"capabilities": [],
|
|
|
|
"nix": {
|
|
"packages": {
|
|
"build": [],
|
|
"runtime": []
|
|
},
|
|
"external_libraries": [],
|
|
"cmake": {
|
|
"find_packages": [],
|
|
"extra_sources": [],
|
|
"extra_include_dirs": [],
|
|
"extra_link_libraries": []
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Required Fields
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `name` | string | Module identifier. Must match binary prefix: `my_module` -> `my_module_plugin.so` |
|
|
| `version` | string | Semantic version (`"1.0.0"`) |
|
|
| `type` | string | `"core"` for modules, `"ui_qml"` for UI apps |
|
|
| `main` | string | Plugin binary name without extension: `"my_module_plugin"` (optional for pure QML UI apps) |
|
|
|
|
## Universal Module Fields
|
|
|
|
| Field | Value | Description |
|
|
|-------|-------|-------------|
|
|
| `interface` | `"universal"` | Signals that this module uses pure C++ impl + code generation |
|
|
|
|
When `"interface": "universal"` is set, `mkLogosModule` automatically runs the universal codegen pipeline (header → `.lidl` → cdylib glue) before CMake — no `preConfigure` needed. See [codegen.md](codegen.md).
|
|
|
|
## Dependencies
|
|
|
|
```json
|
|
"dependencies": ["storage_module", "crypto_module"]
|
|
```
|
|
|
|
Values must match the `name` field in the dependency module's own `metadata.json`. The runtime loads dependencies before the module.
|
|
|
|
Flake input attribute names should also match the dependency module names when possible. Example: if you depend on `storage_module` from repo `logos-storage-module`, the flake input should be named `logos-storage-module`.
|
|
|
|
## External Libraries
|
|
|
|
```json
|
|
"nix": {
|
|
"external_libraries": [
|
|
{
|
|
"name": "mylib",
|
|
"build_command": "make static-library",
|
|
"output_pattern": "build/libmylib.*"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
For Go libraries, add `"go_build": true`. The external library source is provided as a non-flake input in `flake.nix` and mapped via `externalLibInputs`.
|
|
|
|
## Nix Packages
|
|
|
|
```json
|
|
"nix": {
|
|
"packages": {
|
|
"build": ["pkg-config"],
|
|
"runtime": ["nlohmann_json", "openssl"]
|
|
}
|
|
}
|
|
```
|
|
|
|
`build` packages are available during compilation only. `runtime` packages are linked and available at runtime.
|
|
|
|
## CMake Configuration
|
|
|
|
```json
|
|
"nix": {
|
|
"cmake": {
|
|
"find_packages": ["Threads", "OpenSSL"],
|
|
"extra_sources": ["src/helper.cpp"],
|
|
"extra_include_dirs": ["include"],
|
|
"extra_link_libraries": ["Threads::Threads"]
|
|
}
|
|
}
|
|
```
|
|
|
|
These values are passed to CMake by the `logos_module()` macro. They supplement, not replace, the automatic SDK and Qt dependencies.
|
|
|
|
## UI App Specific Fields
|
|
|
|
```json
|
|
{
|
|
"type": "ui_qml",
|
|
"view": "Main.qml",
|
|
"icon": "icon.png",
|
|
"category": "tools"
|
|
}
|
|
```
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `view` | string | **Required** for `ui_qml`. Path to QML entry point (e.g., `"Main.qml"` or `"qml/Main.qml"`) |
|
|
| `main` | string | **Optional**. If present, indicates a C++ backend plugin (e.g., `"my_app_plugin"`). If absent, the app is pure QML |
|
|
|
|
Pure QML apps have no `"main"` field — no C++ compilation occurs. QML + backend apps have both `"main"` (C++ plugin) and `"view"` (QML entry point).
|
|
|
|
UI apps do not use `"interface": "universal"` — they use `mkLogosQmlModule` in their flake.nix.
|
|
|
|
|
|
---
|
|
|
|
# Nix Build Patterns
|
|
|
|
## All Builds Go Through Nix
|
|
|
|
Never run raw `cmake` without `nix develop` or `ws develop`. The Nix build system provides Qt, the SDK, the code generator, and all dependencies. Running `cmake --build` outside Nix will fail.
|
|
|
|
## Flake Structure for Universal Modules
|
|
|
|
```nix
|
|
{
|
|
description = "My Logos Module";
|
|
|
|
inputs = {
|
|
logos-module-builder.url = "github:logos-co/logos-module-builder";
|
|
nix-bundle-lgx.url = "github:logos-co/nix-bundle-lgx";
|
|
};
|
|
|
|
outputs = inputs@{ logos-module-builder, ... }:
|
|
logos-module-builder.lib.mkLogosModule {
|
|
src = ./.;
|
|
configFile = ./metadata.json;
|
|
flakeInputs = inputs;
|
|
};
|
|
}
|
|
```
|
|
|
|
No `preConfigure` is needed. When `metadata.json` declares `"interface": "universal"`, `mkLogosModule` runs the universal codegen pipeline (header → `.lidl` → cdylib glue) for you before CMake. See [codegen.md](codegen.md).
|
|
|
|
## Flake Structure for Modules with External Libraries
|
|
|
|
Add the external library as a non-flake input and pass it via `externalLibInputs`:
|
|
|
|
```nix
|
|
inputs = {
|
|
logos-module-builder.url = "github:logos-co/logos-module-builder";
|
|
nix-bundle-lgx.url = "github:logos-co/nix-bundle-lgx";
|
|
my-lib = {
|
|
url = "github:org/my-lib/commit-hash";
|
|
flake = false;
|
|
};
|
|
};
|
|
|
|
outputs = inputs@{ logos-module-builder, ... }:
|
|
logos-module-builder.lib.mkLogosModule {
|
|
src = ./.;
|
|
configFile = ./metadata.json;
|
|
flakeInputs = inputs;
|
|
externalLibInputs = {
|
|
mylib = inputs.my-lib;
|
|
};
|
|
};
|
|
```
|
|
|
|
The universal codegen still runs automatically — `externalLibInputs` only adds the external library to the build; it doesn't change the codegen pipeline.
|
|
|
|
## Build Commands
|
|
|
|
```bash
|
|
nix build # Build the module
|
|
nix build .#lib # Build just the shared library
|
|
nix flake check -L # Run tests
|
|
nix develop # Enter dev shell with build tools
|
|
```
|
|
|
|
Inside the workspace (multi-repo):
|
|
```bash
|
|
ws build my-module # Build
|
|
ws build my-module --auto-local # Build with local dirty dep overrides
|
|
ws test my-module # Run tests
|
|
ws test my-module --auto-local # Test with local overrides
|
|
ws develop my-module # Enter dev shell
|
|
```
|
|
|
|
## Key Rules
|
|
|
|
- Flake inputs must be tracked by git. Run `git add <file>` before Nix can see new files.
|
|
- `nixpkgs` follows `logos-cpp-sdk` via `logos-module-builder`. Never pin a separate nixpkgs.
|
|
- Qt version is fixed by `logos-cpp-sdk`. All repos must use the same Qt to avoid version conflicts.
|
|
- Use `-L` flag to stream build logs: `nix build -L`
|
|
- Use `--override-input` to test with local dependency changes (the `ws` CLI does this for you with `--auto-local`).
|
|
|
|
## Flake Structure for UI QML Apps
|
|
|
|
Pure QML (no C++ backend):
|
|
|
|
```nix
|
|
{
|
|
inputs.logos-module-builder.url = "github:logos-co/logos-module-builder";
|
|
|
|
outputs = inputs@{ logos-module-builder, ... }:
|
|
logos-module-builder.lib.mkLogosQmlModule {
|
|
src = ./.;
|
|
configFile = ./metadata.json;
|
|
flakeInputs = inputs;
|
|
};
|
|
}
|
|
```
|
|
|
|
QML + C++ backend:
|
|
|
|
```nix
|
|
{
|
|
inputs = {
|
|
logos-module-builder.url = "github:logos-co/logos-module-builder";
|
|
# Add module dependencies as inputs:
|
|
# some_module.url = "github:logos-co/logos-some-module";
|
|
};
|
|
|
|
outputs = inputs@{ logos-module-builder, ... }:
|
|
logos-module-builder.lib.mkLogosQmlModule {
|
|
src = ./.;
|
|
configFile = ./metadata.json;
|
|
flakeInputs = inputs;
|
|
};
|
|
}
|
|
```
|
|
|
|
Both use `mkLogosQmlModule` (not `mkLogosModule`). No `preConfigure` needed — the `.rep` file (if present) is handled automatically via `REP_FILE` in CMakeLists.txt.
|
|
|
|
## Dev Shell for CMake Iteration
|
|
|
|
```bash
|
|
nix develop
|
|
cmake -B build -GNinja && cmake --build build
|
|
```
|
|
|
|
The dev shell provides all build dependencies. Use this for rapid C++ iteration without full Nix rebuilds.
|
|
|
|
|
|
---
|
|
|
|
# Testing Logos Modules
|
|
|
|
## Unit Tests with logos-test-framework
|
|
|
|
Universal modules have a plain C++ impl class. Test it using the logos-test-framework, which is provided automatically by `logos-module-builder`.
|
|
|
|
### Test File Structure
|
|
|
|
```
|
|
tests/
|
|
├── main.cpp # LOGOS_TEST_MAIN() entry point
|
|
├── test_my_module.cpp # Test cases using LOGOS_TEST()
|
|
└── CMakeLists.txt # logos_test() macro
|
|
```
|
|
|
|
### Writing Tests
|
|
|
|
```cpp
|
|
// tests/main.cpp
|
|
#include <logos_test.h>
|
|
LOGOS_TEST_MAIN()
|
|
```
|
|
|
|
```cpp
|
|
// tests/test_my_module.cpp
|
|
#include <logos_test.h>
|
|
#include "../src/my_module_impl.h"
|
|
|
|
LOGOS_TEST(hash_returns_nonempty_string) {
|
|
MyModuleImpl impl;
|
|
LOGOS_ASSERT_FALSE(impl.hash("hello").empty());
|
|
}
|
|
|
|
LOGOS_TEST(verify_matches_hash) {
|
|
MyModuleImpl impl;
|
|
auto hash = impl.hash("hello");
|
|
LOGOS_ASSERT_TRUE(impl.verify("hello", hash));
|
|
}
|
|
```
|
|
|
|
### CMakeLists.txt for Tests
|
|
|
|
```cmake
|
|
# tests/CMakeLists.txt
|
|
cmake_minimum_required(VERSION 3.14)
|
|
project(MyModuleTests LANGUAGES CXX)
|
|
|
|
include(LogosTest)
|
|
|
|
logos_test(
|
|
NAME my_module_tests
|
|
MODULE_SOURCES ../src/my_module_impl.cpp
|
|
TEST_SOURCES
|
|
main.cpp
|
|
test_my_module.cpp
|
|
)
|
|
```
|
|
|
|
The `logos_test()` CMake macro handles all framework wiring: Qt dependencies, SDK mock headers, include paths, and CTest registration.
|
|
|
|
### Mocking Other Modules
|
|
|
|
Use `LogosTestContext` when your module calls other modules:
|
|
|
|
```cpp
|
|
LOGOS_TEST(calls_waku_publish) {
|
|
auto t = LogosTestContext("chat_module");
|
|
t.mockModule("waku_module", "relayPublish").returns(true);
|
|
|
|
ChatImpl impl;
|
|
t.init(&impl);
|
|
|
|
impl.sendMessage("hello");
|
|
LOGOS_ASSERT(t.moduleCalled("waku_module", "relayPublish"));
|
|
}
|
|
```
|
|
|
|
### Mocking C Libraries
|
|
|
|
For modules wrapping external C/C++ libraries, write mock stubs:
|
|
|
|
```cpp
|
|
// tests/mocks/mock_libcalc.cpp
|
|
#include <logos_clib_mock.h>
|
|
extern "C" { #include "libcalc.h" }
|
|
|
|
extern "C" int calc_add(int a, int b) {
|
|
LOGOS_CMOCK_RECORD("calc_add");
|
|
return LOGOS_CMOCK_RETURN(int, "calc_add");
|
|
}
|
|
```
|
|
|
|
Reference them in CMake:
|
|
|
|
```cmake
|
|
logos_test(
|
|
NAME calc_module_tests
|
|
MODULE_SOURCES ../src/calc_module_impl.cpp
|
|
TEST_SOURCES main.cpp test_calc.cpp
|
|
MOCK_C_SOURCES mocks/mock_libcalc.cpp
|
|
)
|
|
```
|
|
|
|
### Running Unit Tests
|
|
|
|
```bash
|
|
nix build .#unit-tests -L # Build and run unit tests
|
|
nix flake check -L # All Nix checks including tests
|
|
```
|
|
|
|
`logos-module-builder` auto-detects `tests/CMakeLists.txt` and adds `checks.<system>.unit-tests` and `packages.<system>.unit-tests` automatically.
|
|
|
|
## Integration Tests with logoscore
|
|
|
|
Test the module as a loaded plugin via the headless runtime. Start a clean daemon,
|
|
load the module(s), then call methods with the `call` client command:
|
|
|
|
```bash
|
|
# Start a clean daemon, then load the module(s) (deps resolved automatically)
|
|
logoscore -D -m ./result/lib &
|
|
logoscore load-module my_module
|
|
logoscore load-module other_module
|
|
|
|
# Call methods (positional args; @file reads a parameter from a file)
|
|
logoscore call my_module doSomething test_input
|
|
logoscore call my_module init config
|
|
logoscore call my_module process data
|
|
logoscore call my_module callOther hello
|
|
|
|
# Stop the daemon when done
|
|
logoscore stop
|
|
```
|
|
|
|
logoscore arguments:
|
|
- `-D` -- Start the daemon
|
|
- `-m <path>` -- Directory to scan for module plugins (repeatable)
|
|
- `-l <mod1,mod2>` -- Comma-separated modules to pre-load on startup
|
|
- `call <module> <method> [args...]` -- Call a method on a loaded module
|
|
|
|
Type auto-detection in `call` args: `true`/`false` -> bool, `42` -> int, `3.14` -> double, else -> string. Use `@filename` to load file content as an argument.
|
|
|
|
### TEST_GROUPS
|
|
|
|
The test runner supports groups for selective testing:
|
|
|
|
```bash
|
|
TEST_GROUPS=basic ws test logos-test-modules --auto-local
|
|
TEST_GROUPS=ipc ws test logos-test-modules --auto-local
|
|
TEST_GROUPS=basic,ipc,errors ws test logos-test-modules --auto-local
|
|
```
|
|
|
|
### Running Tests via Nix
|
|
|
|
```bash
|
|
nix build .#unit-tests -L # Run unit tests
|
|
nix flake check -L # Run all checks defined in the flake
|
|
|
|
ws test my-module # In the workspace
|
|
ws test my-module --auto-local # With local dep overrides
|
|
ws test --all --type cpp # All C++ repos
|
|
```
|
|
|
|
## 3. UI Integration Tests (QML Inspector)
|
|
|
|
UI apps (`type: "ui_qml"`) can be tested via the QML Inspector MCP server built into `logos-standalone-app`. Tests interact with the live UI — clicking buttons, reading text, taking screenshots.
|
|
|
|
### Test file pattern
|
|
|
|
```javascript
|
|
// tests/smoke.mjs
|
|
const { resolve } = await import("node:path");
|
|
const { test, run } = await import(
|
|
resolve(process.env.LOGOS_QT_MCP || "./result-mcp", "test-framework/framework.mjs")
|
|
);
|
|
|
|
test("my_app: basic interaction", async (app) => {
|
|
await app.expectTexts(["My App"]);
|
|
await app.click("Add");
|
|
await app.expectTexts(["Result:"]);
|
|
});
|
|
|
|
run();
|
|
```
|
|
|
|
### Test API
|
|
|
|
| Method | Description |
|
|
|--------|-------------|
|
|
| `app.click(text, opts?)` | Find element by text and click it |
|
|
| `app.expectTexts(texts)` | Assert all texts are visible |
|
|
| `app.waitFor(fn, opts)` | Poll until fn succeeds (timeout, interval, description) |
|
|
| `app.screenshot()` | Capture current state |
|
|
| `app.findByType(type)` | Find elements by QML type |
|
|
| `app.findByProperty(prop, value)` | Find elements by property |
|
|
| `app.getTree()` | Get full QML element tree |
|
|
|
|
### Running UI tests
|
|
|
|
```bash
|
|
# Interactive (app already running on localhost:3768)
|
|
node tests/smoke.mjs
|
|
|
|
# CI mode (launches app headless, tests, exits)
|
|
node tests/smoke.mjs --ci ./result/bin/logos-standalone-app --verbose
|
|
|
|
# Hermetic via Nix (offscreen, no display needed)
|
|
nix build .#integration-test
|
|
```
|
|
|
|
Modules with `.mjs` test files in `tests/` automatically get `nix build .#integration-test` via `mkPluginTest`.
|
|
|
|
### MCP tools for AI agents
|
|
|
|
When the app is running, the `.mcp.json` auto-registers these tools with Claude Code / Cursor:
|
|
|
|
`qml_screenshot`, `qml_find_and_click`, `qml_find_by_type`, `qml_find_by_property`, `qml_list_interactive`, `qml_get_tree`
|
|
|
|
This lets AI agents visually verify UI changes, click through workflows, and debug layout issues in real time.
|
|
|
|
## Key Testing Rules
|
|
|
|
- Unit tests use `LOGOS_TEST()` and `LOGOS_ASSERT_*` macros from `<logos_test.h>`
|
|
- Unit tests should NOT require logoscore -- instantiate the impl class directly
|
|
- `tests/CMakeLists.txt` must use `include(LogosTest)` + `logos_test()`
|
|
- Use `LogosTestContext` for mocking module calls and C library functions
|
|
- Integration tests verify the full plugin lifecycle (load, call, response)
|
|
- In CI, start the daemon in the background, run your `call`s, then `logoscore stop` so the job exits
|
|
- 30-second timeout per `call`; exit code non-zero on failure
|
|
- After adding `checks` to a repo's `flake.nix`, run `ws sync-graph` so the workspace discovers them
|
|
|
|
|
|
---
|
|
|
|
# UI App Development
|
|
|
|
UI apps use `"type": "ui_qml"` in metadata.json. There are two subtypes:
|
|
|
|
1. **Pure QML** — no C++ compilation, QML files only, calls backend modules via `logos.callModule()`
|
|
2. **QML + C++ Backend** — process-isolated C++ backend (Qt Remote Objects), QML frontend runs in-process
|
|
|
|
## Pure QML Apps
|
|
|
|
Simplest UI app type. No compilation, no C++ code. The host loads QML directly.
|
|
|
|
### metadata.json
|
|
|
|
```json
|
|
{
|
|
"name": "my_app",
|
|
"type": "ui_qml",
|
|
"version": "1.0.0",
|
|
"description": "My QML UI application",
|
|
"view": "Main.qml",
|
|
"icon": null,
|
|
"category": "tools",
|
|
"dependencies": ["some_backend_module"]
|
|
}
|
|
```
|
|
|
|
Key fields: `"type": "ui_qml"` and `"view"` pointing to the QML entry point. No `"main"` field.
|
|
|
|
### QML Pattern
|
|
|
|
```qml
|
|
import QtQuick 2.15
|
|
import QtQuick.Controls 2.15
|
|
|
|
Item {
|
|
id: root
|
|
|
|
function callBackend(method, args) {
|
|
if (typeof logos === "undefined" || !logos.callModule) {
|
|
console.log("Logos bridge not available")
|
|
return
|
|
}
|
|
return logos.callModule("some_backend_module", method, args)
|
|
}
|
|
|
|
Button {
|
|
text: "Do Something"
|
|
onClicked: {
|
|
var result = callBackend("myMethod", ["arg1", "arg2"])
|
|
console.log("Result:", result)
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
The `logos` bridge is injected by the host (Basecamp or standalone runner). Use `logos.callModule(moduleName, method, args)` to call backend modules.
|
|
|
|
### flake.nix
|
|
|
|
Add backend module dependencies (from `metadata.json` `"dependencies"`) as flake inputs. The input attribute name must match the dependency name.
|
|
|
|
```nix
|
|
{
|
|
inputs = {
|
|
logos-module-builder.url = "github:logos-co/logos-module-builder";
|
|
|
|
# Each metadata.json dependency needs a matching flake input.
|
|
# Use path: for local development, github: for CI/published modules:
|
|
some_backend_module.url = "path:../logos-some-backend-module";
|
|
# some_backend_module.url = "github:logos-co/logos-some-backend-module";
|
|
};
|
|
|
|
outputs = inputs@{ logos-module-builder, ... }:
|
|
logos-module-builder.lib.mkLogosQmlModule {
|
|
src = ./.;
|
|
configFile = ./metadata.json;
|
|
flakeInputs = inputs;
|
|
};
|
|
}
|
|
```
|
|
|
|
Uses `mkLogosQmlModule` (not `mkLogosModule`). No `preConfigure` needed.
|
|
|
|
### Resolving Module Dependencies
|
|
|
|
Each backend module must be built with its shared library (`.so`/`.dylib`) present in `lib/`. Three ways to point at a dependency:
|
|
|
|
| Approach | `flake.nix` input URL | Build command |
|
|
|----------|----------------------|---------------|
|
|
| Local path in flake.nix | `path:../logos-my-module` | `nix run .` |
|
|
| Remote URL + local override | `github:org/repo` | `nix run . --override-input dep_name path:../logos-my-module` |
|
|
| Fully remote | `github:org/repo` | `nix run .` |
|
|
|
|
`--override-input` overrides a flake input at build time without editing `flake.nix` — useful for quick iteration.
|
|
|
|
## QML + C++ Backend Apps
|
|
|
|
For apps that need business logic, state management, or access to system APIs. The C++ backend runs in a **separate isolated process** (`logos_host`), communicating with the QML frontend via Qt Remote Objects IPC.
|
|
|
|
### metadata.json
|
|
|
|
```json
|
|
{
|
|
"name": "my_app",
|
|
"type": "ui_qml",
|
|
"version": "1.0.0",
|
|
"description": "My UI app with C++ backend",
|
|
"main": "my_app_plugin",
|
|
"view": "qml/Main.qml",
|
|
"icon": null,
|
|
"category": "tools",
|
|
"dependencies": []
|
|
}
|
|
```
|
|
|
|
Key difference from pure QML: has `"main"` field pointing to the C++ plugin binary, and `"view"` points to `qml/Main.qml` (inside `src/`).
|
|
|
|
### .rep File (Qt Remote Objects Interface)
|
|
|
|
```
|
|
class MyApp
|
|
{
|
|
PROP(QString status READWRITE)
|
|
SLOT(int doSomething(int a, int b))
|
|
}
|
|
```
|
|
|
|
Defines the IPC interface. Properties auto-sync to QML replicas. Slots are callable from QML.
|
|
|
|
### C++ Plugin Class
|
|
|
|
The plugin inherits three bases:
|
|
- `MyAppSimpleSource` — generated from .rep, provides property storage + slot declarations
|
|
- `MyAppInterface` — extends `PluginInterface`, used for Qt plugin loading
|
|
- `MyAppViewPluginBase` — provides `setBackend()` to wire up Qt Remote Objects
|
|
|
|
```cpp
|
|
class MyAppPlugin : public MyAppSimpleSource,
|
|
public MyAppInterface,
|
|
public MyAppViewPluginBase
|
|
{
|
|
Q_OBJECT
|
|
Q_PLUGIN_METADATA(IID MyAppInterface_iid FILE "metadata.json")
|
|
Q_INTERFACES(MyAppInterface)
|
|
|
|
public:
|
|
explicit MyAppPlugin(QObject* parent = nullptr);
|
|
|
|
QString name() const override { return "my_app"; }
|
|
QString version() const override { return "1.0.0"; }
|
|
|
|
Q_INVOKABLE void initLogos(LogosAPI* api);
|
|
|
|
// Implement slots from .rep
|
|
int doSomething(int a, int b) override;
|
|
|
|
private:
|
|
LogosAPI* m_logosAPI = nullptr;
|
|
};
|
|
```
|
|
|
|
In `initLogos()`, call `setBackend(this)` to wire up the Qt Remote Objects source:
|
|
|
|
```cpp
|
|
void MyAppPlugin::initLogos(LogosAPI* api) {
|
|
m_logosAPI = api;
|
|
setBackend(this);
|
|
}
|
|
```
|
|
|
|
### CMakeLists.txt
|
|
|
|
```cmake
|
|
cmake_minimum_required(VERSION 3.14)
|
|
project(MyAppPlugin LANGUAGES CXX)
|
|
|
|
if(DEFINED ENV{LOGOS_MODULE_BUILDER_ROOT})
|
|
include($ENV{LOGOS_MODULE_BUILDER_ROOT}/cmake/LogosModule.cmake)
|
|
else()
|
|
message(FATAL_ERROR "LogosModule.cmake not found. Set LOGOS_MODULE_BUILDER_ROOT.")
|
|
endif()
|
|
|
|
logos_module(
|
|
NAME my_app
|
|
REP_FILE src/my_app.rep
|
|
SOURCES
|
|
src/my_app_interface.h
|
|
src/my_app_plugin.h
|
|
src/my_app_plugin.cpp
|
|
)
|
|
```
|
|
|
|
Key: `REP_FILE` tells the build system to generate Qt Remote Objects source/replica headers from the `.rep` file.
|
|
|
|
### QML Frontend
|
|
|
|
```qml
|
|
import QtQuick
|
|
import QtQuick.Controls
|
|
|
|
Item {
|
|
id: root
|
|
|
|
readonly property var backend: logos.module("my_app")
|
|
property bool ready: false
|
|
readonly property string status: backend ? backend.status : ""
|
|
|
|
Connections {
|
|
target: logos
|
|
function onViewModuleReadyChanged(moduleName, isReady) {
|
|
if (moduleName === "my_app")
|
|
root.ready = isReady && root.backend !== null;
|
|
}
|
|
}
|
|
Component.onCompleted: {
|
|
root.ready = root.backend !== null && logos.isViewModuleReady("my_app");
|
|
}
|
|
|
|
Button {
|
|
text: "Do Something"
|
|
enabled: root.ready
|
|
onClicked: {
|
|
logos.watch(backend.doSomething(1, 2),
|
|
function(value) { console.log("Result:", value) },
|
|
function(error) { console.log("Error:", error) }
|
|
)
|
|
}
|
|
}
|
|
|
|
Text {
|
|
text: "Status: " + root.status
|
|
}
|
|
}
|
|
```
|
|
|
|
Key QML APIs:
|
|
- `logos.module("name")` — returns a typed Qt Remote Objects replica
|
|
- `logos.isViewModuleReady("name")` — checks backend connection (use `viewModuleReadyChanged` signal for reactivity)
|
|
- `logos.watch(pendingReply, onSuccess, onError)` — handles async slot calls
|
|
|
|
**Important:** `isViewModuleReady()` is a `Q_INVOKABLE` method, not a property. A QML binding like `readonly property bool ready: logos.isViewModuleReady("name")` will never re-evaluate. Use the `Connections` + `Component.onCompleted` pattern shown above instead.
|
|
|
|
## C++/QML Boundary Rules
|
|
|
|
| Concern | C++ (backend plugin) | QML |
|
|
| -------------------- | ---------------------------------------- | -------------------------------------- |
|
|
| Data models, state | `PROP()` in `.rep` file | Bind to `backend.property` |
|
|
| Business logic | Implement as `SLOT()` in `.rep` | Never — no JS business logic |
|
|
| Module calls | Via `LogosAPI*` in `initLogos()` | Via `logos.callModule()` (pure QML) |
|
|
| File I/O, networking | Always C++ | Never |
|
|
| UI layout, styling | Never | QML; use `Logos.Theme` inside Basecamp |
|
|
| User interactions | `SLOT()` methods | `onClicked: logos.watch(backend.doX())`|
|
|
| Plugin lifecycle | `initLogos()` + `setBackend(this)` | N/A |
|
|
|
|
## Build and Test
|
|
|
|
```bash
|
|
git init && git add -A # nix needs files tracked
|
|
nix build # compiles (backend) or packages (pure QML)
|
|
nix run . # standalone app
|
|
|
|
# Override a module dependency at build time (no flake.nix edits needed):
|
|
nix run . --override-input some_module path:../logos-some-module
|
|
```
|
|
|
|
## Calling Logos Modules
|
|
|
|
From C++ backend (in `initLogos` or slot implementations):
|
|
|
|
```cpp
|
|
auto* client = m_logosAPI->getClient("storage_module");
|
|
QVariant result = client->invokeRemoteMethod("storage_module", "save", key, value);
|
|
```
|
|
|
|
From pure QML:
|
|
|
|
```qml
|
|
var result = logos.callModule("storage_module", "save", [key, value])
|
|
```
|
|
|
|
Always declare module dependencies in `metadata.json` `"dependencies"` so they are loaded before the UI app.
|
|
|
|
## UI Integration Testing (QML Inspector + MCP)
|
|
|
|
`logos-standalone-app` includes a QML Inspector MCP server that lets AI agents and test scripts interact with the running UI — take screenshots, click elements, inspect the QML tree.
|
|
|
|
### Available MCP tools
|
|
|
|
| Tool | Description |
|
|
|------|-------------|
|
|
| `qml_screenshot` | Capture a screenshot of the current app state |
|
|
| `qml_find_and_click` | Find a UI element by text and click it |
|
|
| `qml_find_by_type` | Locate elements by QML type name |
|
|
| `qml_find_by_property` | Locate elements by property value |
|
|
| `qml_list_interactive` | List all clickable/interactive elements |
|
|
| `qml_get_tree` | Get the full QML element tree |
|
|
|
|
### Interactive testing (AI agent workflow)
|
|
|
|
```bash
|
|
nix build && nix run . # launches app with inspector on localhost:3768
|
|
```
|
|
|
|
The `.mcp.json` in the project directory auto-registers the MCP server with Claude Code and other MCP clients. The AI agent can then screenshot, click buttons, and verify UI state in real time.
|
|
|
|
### Writing integration test files
|
|
|
|
```javascript
|
|
// tests/smoke.mjs
|
|
const { test, run } = await import(
|
|
resolve(process.env.LOGOS_QT_MCP || "./result-mcp", "test-framework/framework.mjs")
|
|
);
|
|
|
|
test("my_app: click add and verify result", async (app) => {
|
|
await app.click("Add");
|
|
await app.expectTexts(["Result:"]);
|
|
});
|
|
|
|
run();
|
|
```
|
|
|
|
### Running tests
|
|
|
|
```bash
|
|
# Interactive (app already running)
|
|
node tests/smoke.mjs
|
|
|
|
# CI mode (launches app headless, tests, exits)
|
|
node tests/smoke.mjs --ci ./result/bin/logos-standalone-app --verbose
|
|
|
|
# Hermetic via Nix (headless, offscreen)
|
|
nix build .#integration-test
|
|
```
|
|
|
|
### Nix `mkPluginTest` builder
|
|
|
|
UI modules with `.mjs` test files in `tests/` automatically get `nix build .#integration-test`. For manual setup:
|
|
|
|
```nix
|
|
integration-test = logos-standalone-app.lib.${system}.mkPluginTest {
|
|
inherit pkgs;
|
|
pluginPkg = myModulePackage;
|
|
testFiles = [ ./tests/smoke.mjs ];
|
|
name = "my-module-integration-test";
|
|
};
|
|
```
|
|
|
|
The builder runs headless (`QT_QPA_PLATFORM=offscreen`), connects to the QML inspector, and executes each test file sequentially.
|
|
|
|
|
|
---
|
|
|
|
# Universal Module Development
|
|
|
|
## The Universal Interface Pattern
|
|
|
|
Universal modules use **pure C++** for their implementation. You write a single implementation class using standard C++ types. The build system generates all Qt/plugin infrastructure automatically — universal modules are **header-first cdylibs** (see [codegen.md](codegen.md)).
|
|
|
|
**You write:** A C++ class with `std::string`, `int64_t`, `bool`, `std::vector<T>`.
|
|
**The generator produces:** a derived `.lidl` contract, the uniform Qt-plugin glue, and a Qt-free C-ABI export wrapper around your class — so your code never touches Qt.
|
|
|
|
## Rules
|
|
|
|
- **NO Qt types** in your impl header or implementation: no `QString`, `QObject`, `Q_INVOKABLE`, `QVariant`
|
|
- **NO Qt includes** in your impl header (Qt headers in `.cpp` are OK if needed for internal use, but the public API must be pure C++)
|
|
- Set `"interface": "universal"` in `metadata.json`
|
|
- Name the impl class `<PascalCaseName>Impl` (e.g., `CryptoUtilsImpl`)
|
|
- Name the impl header `<name>_impl.h` (e.g., `crypto_utils_impl.h`)
|
|
- Only `public` methods become module API methods. Private/protected are ignored by the generator.
|
|
- Constructors, destructors, typedefs, and using declarations are skipped by the generator.
|
|
|
|
## Type Mapping
|
|
|
|
| Use this in your C++ | Generator maps to | Qt type produced |
|
|
|----------------------|-------------------|-----------------|
|
|
| `std::string` / `const std::string&` | `tstr` | `QString` |
|
|
| `bool` | `bool` | `bool` |
|
|
| `int64_t` | `int` | `int` |
|
|
| `uint64_t` | `uint` | `int` |
|
|
| `double` | `float64` | `double` |
|
|
| `void` | `void` | `void` |
|
|
| `std::vector<std::string>` | `[tstr]` | `QStringList` |
|
|
| `std::vector<uint8_t>` | `bstr` | `QByteArray` |
|
|
| `std::vector<int64_t>` | `[int]` | `QVariantList` |
|
|
| `std::vector<double>` | `[float64]` | `QVariantList` |
|
|
| `std::vector<bool>` | `[bool]` | `QVariantList` |
|
|
| `LogosMap` | `{tstr: any}` | `QVariantMap` |
|
|
| `LogosList` | `[any]` | `QVariantList` |
|
|
|
|
`LogosMap` and `LogosList` (from `<logos_json.h>`) are aliases for `nlohmann::json`. Use them when you need to return structured objects or arrays while keeping your impl Qt-free. The generator automatically converts them to `QVariantMap`/`QVariantList` in the glue layer.
|
|
|
|
If you use a type not in this table, the generator maps it to `any` (`QVariant`). Prefer explicit types from the table for type safety.
|
|
|
|
## Emitting Events
|
|
|
|
To emit events from your module, declare a public `emitEvent` callback in your impl header:
|
|
|
|
```cpp
|
|
#include <functional>
|
|
std::function<void(const std::string& eventName, const std::string& data)> emitEvent;
|
|
```
|
|
|
|
The generator detects this automatically and wires it to the Logos event system. Call it from your implementation:
|
|
|
|
```cpp
|
|
if (emitEvent) {
|
|
emitEvent("somethingHappened", someData);
|
|
}
|
|
```
|
|
|
|
No `events` array in `metadata.json` is needed — the generator infers everything from the header.
|
|
|
|
## Impl Header Template
|
|
|
|
```cpp
|
|
#pragma once
|
|
#include <string>
|
|
#include <vector>
|
|
#include <cstdint>
|
|
|
|
class MyModuleImpl {
|
|
public:
|
|
MyModuleImpl();
|
|
~MyModuleImpl();
|
|
|
|
std::string doSomething(const std::string& input);
|
|
bool validate(const std::string& data);
|
|
int64_t count();
|
|
std::vector<std::string> listItems();
|
|
|
|
private:
|
|
// Private members are not exposed as module API
|
|
};
|
|
```
|
|
|
|
## Build Pipeline
|
|
|
|
You don't write a `preConfigure` or run the generator — `mkLogosModule` runs the universal pipeline automatically when `metadata.json` sets `"interface": "universal"`. It derives a `.lidl` from your impl header, then emits the uniform Qt-plugin glue and a Qt-free C-ABI export wrapper around your class (run for you, you don't invoke these):
|
|
|
|
```bash
|
|
logos-cpp-generator --header-to-lidl src/<name>_impl.h \
|
|
--impl-class <ImplClassName> --metadata metadata.json \
|
|
-o ./generated_code/<name>.lidl
|
|
logos-qt-host-generator --lidl ./generated_code/<name>.lidl --backend cdylib \
|
|
--output-dir ./generated_code
|
|
logos-cpp-generator --lidl ./generated_code/<name>.lidl --backend cdylib \
|
|
--impl-class <ImplClassName> --impl-header <name>_impl.h \
|
|
--output-dir ./generated_code
|
|
```
|
|
|
|
This produces `generated_code/<name>.lidl`, `<name>_cdylib_glue.{h,cpp}`, and `<name>_module_impl.cpp`. You do **not** list these in `CMakeLists.txt` — `LogosModule.cmake` globs `generated_code/` automatically. See [codegen.md](codegen.md) for details.
|
|
|
|
## Testing
|
|
|
|
Unit tests instantiate the impl class directly — it is a plain C++ class:
|
|
|
|
```cpp
|
|
#include "my_module_impl.h"
|
|
// No Qt test framework needed for basic tests
|
|
MyModuleImpl impl;
|
|
assert(impl.doSomething("test") == "expected");
|
|
```
|
|
|
|
Integration tests use `logoscore` (start a daemon, then call via the client):
|
|
```bash
|
|
logoscore -D -m ./result/lib &
|
|
logoscore load-module my_module
|
|
logoscore call my_module doSomething test
|
|
logoscore stop
|
|
```
|
|
|
|
|
|
---
|