- **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
- **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
## 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`
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
│ │ ├── 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/)
## 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/`)
| ----------- | ---------------------- | ----------------------- | -------------------- |
| 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.
- **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
### 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/
---
-## 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
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
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`
---
```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
```
# 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).