]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commitdiff
docs: harmonize project memories with current codebase state
authorJérôme Benoit <jerome.benoit@sap.com>
Sun, 19 Apr 2026 15:12:31 +0000 (17:12 +0200)
committerJérôme Benoit <jerome.benoit@sap.com>
Sun, 19 Apr 2026 15:12:31 +0000 (17:12 +0200)
- Add UI Common and CLI sections to all 4 memories
- Fix UI Common build = typecheck (source-only package, no artifacts)
- Fix monorepo description: 4 TS packages (pnpm) + 1 Python (Poetry)
- Fix OCPP Server coverage threshold column placement
- Flag Vitest watch mode in commands and checklist
- Add Zod dependency and config validation convention
- Fix source tree comment (3 transports, not 2)
- Update CI matrix to Python 3.13/3.14
- Add CLI conventions (factory pattern, payload helpers, short hash, output)
- Add UI Common conventions (canonical types, no re-exports, errors)
- Add per-framework testing conventions (root strict vs ui assert vs vitest)
- Refactor checklist to remove command duplication with suggested_commands
- Fix README references to actual project files

.serena/memories/code_style_conventions.md
.serena/memories/project_overview.md
.serena/memories/suggested_commands.md
.serena/memories/task_completion_checklist.md

index df90126e6b9c92574c31c3437957c2606973b0b6..401a9b3095020ead96ccb5e84073d0f7831c3b48 100644 (file)
 - **Auto-reload**: file watcher on `src/assets/config.json`
 - **Sections**: `log`, `storage`, `uiServer`, `worker`, `stationTemplateUrls`, `supervisionUrls`
 
+## UI Common Conventions
+
+- **Shared library**: Types, WebSocket client, adapters, utilities shared between ui/web and ui/cli
+- **Canonical types**: `ChargingStationData`, `ConnectorEntry`, `EvseEntry`, `SimulatorState`, `ResponsePayload`, `ProcedureName` — all consumers import directly from `ui-common`
+- **Errors**: `ConnectionError`, `ServerFailureError`, `extractErrorMessage` centralized in `errors.ts`
+- **Constants**: `DEFAULT_HOST`, `DEFAULT_PORT`, `DEFAULT_PROTOCOL`, `DEFAULT_PROTOCOL_VERSION`, `DEFAULT_SECURE` in `constants.ts`
+- **Utilities**: `convertToBoolean()`, `convertToInt()`, `getWebSocketStateName()`, `randomUUID()`, `validateUUID()` — shared, not duplicated in consumers
+- **Config validation**: Zod schemas in `src/config/schema.ts` validate UI server configuration. Update schemas alongside type changes
+- **No re-exports**: Consumers import directly from `ui-common`, not through intermediate barrels
+- **Tests**: `node:test` + `node:assert` (not vitest)
+
+## CLI Conventions
+
+- **Command factory pattern**: Each command module exports `createXxxCommands(program): Command` — returns a `Command`, registered via `program.addCommand()`
+- **Declarative registry**: Simple commands with identical structure use a loop over a `[name, description, procedureName][]` array
+- **Payload helpers**: `buildHashIdsPayload()`, `pickDefined()`, `pickPresent()`, `PAYLOAD_OPTION`, `PAYLOAD_DESC` in `payload.ts`
+- **Short hash resolution**: Hash ID prefixes resolved via station list lookup before command execution (transparent to user)
+- **Human output**: Borderless tables via `cli-table3`, status icons, truncated hash IDs, fuzzy timestamps. Renderers dispatch by payload shape via type guards
+- **Output convention**: success → stdout, errors → stderr, `--json` → structured JSON on stdout
+- **Embedded skill**: SKILL.md embedded at build time via esbuild define, served by `skill show`/`skill install`
+- **Tests**: `node:test` + `node:assert`, shared `captureStream` helper in `tests/helpers.ts`
+- **Semantic descriptions**: "Request station(s) to send OCPP X" (not "Send OCPP X") — the CLI instructs the simulator, which instructs the station
+
 ## Vue UI Conventions
 
 - **Route names**: Use `ROUTE_NAMES` constant object from `composables/Constants.ts` — never hardcode route strings
 - **Placeholders**: Use `EMPTY_VALUE_PLACEHOLDER` constant for unknown/missing values — never hardcode `'Ø'`
 - **localStorage keys**: Use `UI_SERVER_CONFIGURATION_INDEX_KEY` and `TOGGLE_BUTTON_KEY_PREFIX` constants
-- **Separate package**: UI Web cannot import from the backend `src/` — shared logic is duplicated in `composables/Utils.ts`
+- **Separate package**: UI Web cannot import from the backend `src/` — shared logic lives in `ui-common`, web-specific composables in `composables/Utils.ts`
 
 ## Testing Conventions
 
+### Root Simulator (`tests/`)
+
 Full guide: `tests/TEST_STYLE_GUIDE.md`. Key points:
 
 - **Assertions**: `node:assert/strict` — strict only; `assert.ok` only for boolean/existence; never loose equality
@@ -101,6 +126,23 @@ Full guide: `tests/TEST_STYLE_GUIDE.md`. Key points:
 - **Direct imports**: Test files import from the defining module, not through re-export hubs. `src/` barrels remain (public API)
 - **`__testable__` pattern**: `ocpp/1.6/__testable__/` and `ocpp/2.0/__testable__/` directories expose internal classes (e.g., `OCPP20VariableManagerTestable`, `OCPP20RequestServiceTestable`) for unit testing private internals. Import from `__testable__/index.ts` barrel in tests only
 
+### UI Common & CLI (`ui/common/tests/`, `ui/cli/tests/`)
+
+- **Framework**: `node:test` + `node:assert` (not strict, not vitest)
+- **Structure**: `await describe()` / `await it()` pattern
+- **Naming**: files as `module-name.test.ts`, tests as `'should ...'` or descriptive phrase
+- **Shared helpers**: `captureStream()` in `ui/cli/tests/helpers.ts` for stdout/stderr capture
+- **Data**: Use canonical types from `ui-common` with proper enum values in test fixtures
+
+### Web UI (`ui/web/tests/`)
+
+- **Framework**: Vitest + `@vue/test-utils`
+- **Assertions**: `expect()` (Vitest API)
+- **Mocking**: `vi.mock()` for modules, `vi.fn()` for functions, `vi.mocked()` for typed access
+- **Router**: Mock `vue-router` via `vi.mock('vue-router')` for components using `useRoute`/`useRouter`
+- **Toast**: Global `toastMock` in `tests/setup.ts`
+- **File headers**: Mandatory `@file` + `@description` JSDoc
+
 ## Python Conventions (tests/ocpp-server/)
 
 - **Naming**: SCREAMING_SNAKE_CASE constants with unit suffixes (`_SECONDS`), snake_case functions, PascalCase classes
@@ -114,6 +156,6 @@ Full guide: `tests/TEST_STYLE_GUIDE.md`. Key points:
 ## Common Pitfalls
 
 - **ESLint cache**: Clear `.eslintcache` if lint results seem stale after config changes
-- **Web UI is independent**: Always run its quality gates separately from `ui/web/` directory
+- **UI sub-projects are independent**: Always run quality gates separately from `ui/common/`, `ui/cli/`, `ui/web/` directories
 - **OCPP server is Python**: Uses Poetry, not pnpm. Linter is ruff (not pylint/flake8). Type checker is mypy
 - **Barrel circular deps**: `src/utils/` must NOT import from `src/exception/index.js`
index 352e3bdf84ab7134feebcea0c671f095df63e343..c2dfc8fc9776f08b29e8b13f50d5eac64d34fb68 100644 (file)
@@ -4,28 +4,32 @@
 
 Node.js simulator for OCPP-J charging stations, part of SAP e-Mobility solution. Simulates and scales charging stations for load testing and development.
 
-## Monorepo Structure (pnpm workspace)
+## Monorepo Structure
 
-3 sub-projects:
+4 TypeScript packages (pnpm workspace) + 1 Python project (Poetry, independent):
 
 1. **Root Simulator** (`/`) — Node.js/TypeScript OCPP simulator (main project)
-2. **Web UI** (`/ui/web`) — Vue 3 + Vite dashboard for monitoring/control
-3. **OCPP Mock Server** (`/tests/ocpp-server`) — Python OCPP 2.0.1 mock server for testing
+2. **UI Common** (`/ui/common`) — Shared TypeScript library (types, WebSocket client, utilities) for UI packages
+3. **Web UI** (`/ui/web`) — Vue 3 + Vite dashboard for monitoring/control
+4. **CLI** (`/ui/cli`) — Command-line tool for managing the simulator (Commander.js, esbuild bundled)
+5. **OCPP Mock Server** (`/tests/ocpp-server`) — Python OCPP 2.0.1 mock server for testing
 
 ## Tech Stack
 
-| Sub-project | Runtime          | Language                 | Package Manager | Test Framework          | Build Tool |
-| ----------- | ---------------- | ------------------------ | --------------- | ----------------------- | ---------- |
-| Simulator   | Node.js >=22.0.0 | TypeScript 6.0           | pnpm >=10.9.0   | Node.js native `--test` | esbuild    |
-| Web UI      | Node.js >=22.0.0 | TypeScript 6.0 + Vue 3.5 | pnpm >=10.9.0   | Vitest                  | Vite 8     |
-| OCPP Server | Python >=3.12    | Python                   | Poetry >=2.0    | pytest + pytest-asyncio | N/A        |
+| Sub-project | Runtime          | Language                 | Package Manager | Test Framework          | Build Tool        |
+| ----------- | ---------------- | ------------------------ | --------------- | ----------------------- | ----------------- |
+| Simulator   | Node.js >=22.0.0 | TypeScript 6.0           | pnpm >=10.9.0   | Node.js native `--test` | esbuild           |
+| UI Common   | Node.js >=22.0.0 | TypeScript 6.0           | pnpm >=10.9.0   | Node.js native `--test` | N/A (source-only) |
+| Web UI      | Node.js >=22.0.0 | TypeScript 6.0 + Vue 3.5 | pnpm >=10.9.0   | Vitest                  | Vite 8            |
+| CLI         | Node.js >=22.0.0 | TypeScript 6.0           | pnpm >=10.9.0   | Node.js native `--test` | esbuild           |
+| OCPP Server | Python >=3.12    | Python                   | Poetry >=2.0    | pytest + pytest-asyncio | N/A               |
 
 ## Coverage Thresholds
 
-| Sub-project | Branches      | Functions | Lines/Statements |
-| ----------- | ------------- | --------- | ---------------- |
-| Web UI      | 89%           | 83%       | 91%              |
-| OCPP Server | 83% (overall) | —         | —                |
+| Sub-project | Branches | Functions | Lines/Statements              |
+| ----------- | -------- | --------- | ----------------------------- |
+| Web UI      | 89%      | 83%       | 91%                           |
+| OCPP Server | —        | —         | 83% (fail_under, branch=true) |
 
 ## Source Structure
 
@@ -38,7 +42,7 @@ src/
 │   │   ├── auth/              # Authentication subsystem (barrel: index.ts)
 │   │   └── index.ts           # OCPP barrel
 │   ├── broadcast-channel/     # Worker communication
-│   ├── ui-server/             # UI server (HTTP + WebSocket)
+│   ├── ui-server/             # UI server (WebSocket, MCP, HTTP)
 │   └── index.ts               # Charging station barrel
 ├── types/                      # Type definitions (barrel: index.ts)
 │   └── ocpp/                  # OCPP-specific types (1.6/, 2.0/)
@@ -74,13 +78,7 @@ src/
 
 ## Auth Subsystem (`ocpp/auth/`)
 
-- **OCPPAuthServiceImpl**: Strategy priority chain (local → remote → certificate)
-- **3 strategies**: LocalAuthStrategy (cache + local auth list lookup), RemoteAuthStrategy (CSMS network calls), CertificateAuthStrategy (X.509)
-- **InMemoryAuthCache**: LRU with TTL, rate limiting, periodic cleanup
-- **InMemoryLocalAuthListManager**: CSMS-managed authorization list with Full/Differential updates, version tracking, capacity limits
-- **AuthComponentFactory**: Creates adapters, strategies, caches, managers from config
-- **AuthHelpers**: Cross-version utility functions (TTL calculation, result formatting, logging sanitization)
-- **Version adapters**: OCPP16AuthAdapter, OCPP20AuthAdapter
+Strategy pattern with priority chain: local (cache + auth list) → remote (CSMS) → certificate (X.509). Independent subsystem with its own barrel, factory, version adapters (OCPP16/20), LRU cache with TTL, and rate limiting.
 
 ## UI Server (`ui-server/`)
 
@@ -110,7 +108,9 @@ tests/
 | ----------- | ---------------------- | ----------------------- | -------------------- |
 | Simulator   | Ubuntu, macOS, Windows | Node 22.x, 24.x, latest | Ubuntu + Node 24.x   |
 | Web UI      | Ubuntu, macOS, Windows | Node 22.x, 24.x, latest | Ubuntu + Node 24.x   |
-| OCPP Server | Ubuntu, macOS, Windows | Python 3.12, 3.13       | Ubuntu + Python 3.13 |
+| UI Common   | Ubuntu                 | Node 24.x               | Ubuntu + Node 24.x   |
+| CLI         | Ubuntu, macOS, Windows | Node 22.x, 24.x, latest | Ubuntu + Node 24.x   |
+| OCPP Server | Ubuntu, macOS, Windows | Python 3.13, 3.14       | Ubuntu + Python 3.14 |
 
 Gated steps (lint, typecheck, coverage, SonarCloud) run only on the gated platform. Build + test run on all platforms.
 
@@ -134,4 +134,8 @@ docs/
 - **winston** — Logging with daily rotation
 - **@mikro-orm/** — Database ORM (SQLite, MariaDB)
 - **mnemonist** — Data structures (CircularBuffer)
+- **zod** — Schema validation (UI Common config)
+- **commander** — CLI command framework
+- **cli-table3** — CLI table rendering
+- **chalk** — CLI terminal colors
 - **websockets** + **ocpp** — Python OCPP mock server
index acd168664ab36101ca78bd8789a08789b120b58f..d565d89b3130f8375542af2123d3bc18ab786dcf 100644 (file)
@@ -5,7 +5,7 @@
 ### Install & Build
 
 ```bash
-pnpm install                    # Install all dependencies (root + ui/web)
+pnpm install                    # Install all dependencies (root + ui/common + ui/cli + ui/web)
 pnpm build                      # Production build (esbuild)
 pnpm build:dev                  # Development build with source maps
 pnpm clean:dist                 # Remove dist/
@@ -38,21 +38,69 @@ pnpm test:coverage              # Tests with LCOV coverage → coverage/lcov.inf
 
 ---
 
-## Web UI (`/ui/web`)
+## UI Common (`/ui/common`)
 
-**IMPORTANT**: This is a separate sub-project. Run commands from `ui/web/` directory.
+**IMPORTANT**: Shared library. Run commands from `ui/common/` directory. Build this first — ui/cli and ui/web depend on it.
 
-### Install & Build
+### Build & Quality
 
 ```bash
-cd ui/web
-pnpm build                      # Vite production build
-pnpm clean:dist                 # Remove dist/
+cd ui/common
+pnpm build                      # Typecheck (same as typecheck — no build artifacts, source-only package)
+pnpm typecheck                  # tsc --noEmit (identical to build)
+pnpm lint                       # ESLint check only
+pnpm format                     # Prettier + ESLint auto-fix
+pnpm test                       # Node.js native test runner (node:test + node:assert)
+pnpm test:coverage              # Tests with coverage
+```
+
+---
+
+## CLI (`/ui/cli`)
+
+**IMPORTANT**: Separate sub-project. Run commands from `ui/cli/` directory. Depends on ui/common.
+
+### Build & Quality
+
+```bash
+cd ui/cli
+pnpm build                      # esbuild bundle → dist/cli.js
+pnpm typecheck                  # tsc --noEmit
+pnpm lint                       # ESLint check only
+pnpm format                     # Prettier + ESLint auto-fix
+pnpm test                       # Node.js native test runner (node:test + node:assert)
+pnpm test:coverage              # Tests with coverage
+pnpm test:integration           # Integration tests (requires built CLI)
 ```
 
 ### Run
 
 ```bash
+node dist/cli.js --help                        # Show help
+node dist/cli.js --config <path> station list  # List stations with config
+node dist/cli.js --json simulator state        # Get state in JSON mode
+node dist/cli.js skill show                    # Print embedded agent skill
+node dist/cli.js skill install                 # Install agent skill locally
+```
+
+### Features
+
+- **Short hash prefix matching**: Use truncated hash IDs (e.g., `e9041c`) instead of full 96-char hashes
+- **Custom payloads**: `-p '{"key":"val"}'`, `-p @file.json`, or stdin via `-p -`
+- **Human output**: Borderless tables with status icons, colors, connector info
+- **JSON output**: `--json` flag for machine-readable output
+
+---
+
+## Web UI (`/ui/web`)
+
+**IMPORTANT**: Separate sub-project. Run commands from `ui/web/` directory. Depends on ui/common.
+
+### Build & Run
+
+```bash
+cd ui/web
+pnpm build                      # Vite production build
 pnpm dev                        # Vite dev server with hot-reload (port 5173)
 pnpm start                      # Build + serve via Node.js HTTP server (port 3030)
 pnpm preview                    # Build + Vite preview
@@ -64,37 +112,26 @@ pnpm preview                    # Build + Vite preview
 pnpm format                     # Prettier + ESLint auto-fix
 pnpm typecheck                  # vue-tsc type checking (no emit)
 pnpm lint                       # ESLint check only
-pnpm lint:fix                   # ESLint auto-fix only
-```
-
-### Test
-
-```bash
-pnpm test                       # Run Vitest tests
-pnpm test:coverage              # Vitest with V8 coverage
+pnpm test                       # Vitest (watch mode locally — use `vitest run` for single-run)
+pnpm test:coverage              # Vitest single-run with V8 coverage
 ```
 
 ---
 
 ## OCPP Mock Server (`/tests/ocpp-server`)
 
-**IMPORTANT**: This is a Python sub-project. Uses Poetry for dependency management.
+**IMPORTANT**: Python sub-project. Uses Poetry, not pnpm.
 
-### Install
+### Install & Run
 
 ```bash
 cd tests/ocpp-server
-poetry install --no-root         # Install dependencies
-```
-
-### Run
-
-```bash
-poetry run task server                                    # Start server (127.0.0.1:9000)
-poetry run python server.py --host 0.0.0.0 --port 8080   # Custom host/port
-poetry run python server.py --boot-status rejected        # Custom boot status
-poetry run python server.py --auth-mode whitelist --whitelist token1 token2
-poetry run python server.py --command GetBaseReport --period 5
+poetry install --no-root
+poetry run task server                                              # Start (127.0.0.1:9000)
+poetry run python server.py --host 0.0.0.0 --port 8080             # Custom host/port
+poetry run python server.py --boot-status rejected                  # Custom boot status
+poetry run python server.py --auth-mode whitelist --whitelist t1 t2 # Auth mode
+poetry run python server.py --command GetBaseReport --period 5      # Periodic command
 ```
 
 ### Code Quality
@@ -103,44 +140,28 @@ poetry run python server.py --command GetBaseReport --period 5
 poetry run task format           # Ruff auto-fix + format
 poetry run task typecheck        # Mypy type check
 poetry run task lint             # Ruff check + format check
-```
-
-### Test
-
-```bash
-poetry run task test             # Run pytest
-poetry run task test_coverage    # Pytest with coverage report
+poetry run task test             # pytest
+poetry run task test_coverage    # pytest with coverage
 ```
 
 ---
 
 ## Pre-Commit Hooks (automatic on `git commit`)
 
-1. **lint-staged**: Prettier + ESLint on staged files (root + ui/web + ocpp-server)
-2. **commitlint**: Enforces Conventional Commits format on commit messages
+1. **lint-staged**: Prettier + ESLint on staged files (root + ui/\* + ocpp-server)
+2. **commitlint**: Conventional Commits format
 
-### Conventional Commits Format
-
-```
-<type>[optional scope]: <description>
-```
-
-Types: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `chore`, `ci`, `revert`
-Header max length: 100 characters.
+Types: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `chore`, `ci`, `revert`. Max 100 chars.
 
 ---
 
 ## OCPP Spec Search (QMD)
 
-Collection `ocpp-specs` — OCPP 1.6, 2.0.1, 2.1 specs from `docs/` (24 files, 3213 chunks).
-
-**Project-specific gotchas**:
-
-- `CI=true` disables LLM ops → prefix `env -u CI` on all `qmd query` / `qmd vsearch` calls. `qmd search` (BM25 only) works without it.
-- Collection flag: `-c` or `--collection` (singular).
-- Result limit flag: `-n`.
+Collection `ocpp-specs` — OCPP 1.6, 2.0.1, 2.1 specs from `docs/`.
 
-Re-index after spec updates: `qmd update --pull && qmd embed`
+- `CI=true` disables LLM ops → prefix `env -u CI` on `qmd query` / `qmd vsearch`
+- `qmd search` (BM25 only) works without it
+- Re-index: `qmd update --pull && qmd embed`
 
 ---
 
@@ -149,6 +170,6 @@ Re-index after spec updates: `qmd update --pull && qmd embed`
 ```bash
 cd docker
 make                             # Build + start simulator + web UI containers
-make clean                       # Stop containers + remove images
+make clean                       # Stop + remove
 make docker-push-ecr             # Build, tag, push to AWS ECR
 ```
index ad4188a38cf46c5786805bf9ac7d2773b1874fed..8bea104c531831def632f6ab696d998a8b85cd45 100644 (file)
@@ -1,46 +1,45 @@
 # Task Completion Checklist
 
-## After Completing Any Task
-
-### 1. Root Simulator (always)
-
-```bash
-pnpm format                 # prettier + eslint auto-fix
-pnpm typecheck              # tsc --noEmit
-pnpm lint                   # eslint check only
-pnpm build                  # esbuild production bundle
-pnpm test                   # node native test runner
-```
-
-### 2. Web UI (if `ui/web/` files changed)
-
-```bash
-cd ui/web
-pnpm format                 # prettier + eslint auto-fix
-pnpm typecheck              # vue-tsc --noEmit
-pnpm lint                   # eslint check only
-pnpm build                  # vite production build
-pnpm test                   # vitest
-```
-
-### 3. OCPP Mock Server (if `tests/ocpp-server/` files changed)
-
-```bash
-cd tests/ocpp-server
-poetry run task format      # ruff check --fix + ruff format
-poetry run task typecheck   # mypy
-poetry run task lint        # ruff check --diff + ruff format --check
-poetry run task test        # pytest -v
-```
-
-### 4. Documentation
-
-- Update docs if public API changed
+Run quality gates for each sub-project affected by your changes. See `suggested_commands` for full command reference.
+
+## Quality Gate Order
+
+For each sub-project: `pnpm format` → `pnpm typecheck` → `pnpm lint` → `pnpm build` (if applicable) → `pnpm test`
+
+### 1. Root Simulator (if `src/` or `tests/` changed)
+
+`pnpm format && pnpm typecheck && pnpm lint && pnpm build && pnpm test`
+
+### 2. UI Common (if `ui/common/` changed)
+
+`cd ui/common && pnpm format && pnpm typecheck && pnpm lint && pnpm test`
+
+Note: `pnpm build` is identical to `pnpm typecheck` (source-only package, no build artifacts).
+
+### 3. CLI (if `ui/cli/` changed)
+
+`cd ui/cli && pnpm format && pnpm typecheck && pnpm lint && pnpm build && pnpm test`
+
+### 4. Web UI (if `ui/web/` changed)
+
+`cd ui/web && pnpm format && pnpm typecheck && pnpm lint && pnpm build && pnpm test:coverage`
+
+Note: Use `test:coverage` (single-run). `pnpm test` starts Vitest in watch mode locally.
+
+### 5. OCPP Mock Server (if `tests/ocpp-server/` changed)
+
+`cd tests/ocpp-server && poetry run task format && poetry run task typecheck && poetry run task lint && poetry run task test`
+
+### 6. Documentation
+
+- Update relevant README if user-facing behavior changed (root `README.md`, `ui/cli/README.md`, `ui/web/README.md`)
 - Commit messages follow Conventional Commits (enforced by hook)
 
-### 5. OCPP Compliance (if applicable)
+### 7. OCPP Compliance (if applicable)
 
-- Verify OCPP standard compliance for any protocol changes
+- Verify OCPP standard compliance for protocol changes
 - Validate against JSON schemas when `ocppStrictCompliance` is enabled
 
-Refer to `code_style_conventions` memory for coding rules, and `suggested_commands` memory for full command reference.
+### Dependency Order
+
+If changing `ui/common`: run common gates first, then rebuild/test CLI and Web UI (they depend on it).