diff --git a/.prettierignore b/.prettierignore index ebf20c6..1f2e5e6 100644 --- a/.prettierignore +++ b/.prettierignore @@ -6,3 +6,4 @@ build/ coverage/ pnpm-lock.yaml *.md +apps/web/next-env.d.ts diff --git a/AGENTS.md b/AGENTS.md index 83bd690..f642b79 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,12 +21,14 @@ infrastructure developers. ``` ocpp-debugkit/ ├── packages/ -│ ├── core/ # Data model, parser, normalizer, timeline, failure detection -│ ├── scenarios/ # Predefined trace scenarios for testing -│ ├── reporter/ # Report generators (Markdown, HTML) -│ ├── cli/ # Command-line interface -│ ├── replay/ # Replay engine (v0.2+) -│ └── react/ # Reusable React components (v0.2+) +│ └── toolkit/ # Single npm package @ocpp-debugkit/toolkit +│ └── src/ +│ ├── core/ # Data model, parser, normalizer, timeline, failure detection +│ ├── scenarios/ # Predefined trace scenarios for testing +│ ├── reporter/ # Report generators (Markdown, HTML) +│ ├── replay/ # Replay engine +│ ├── react/ # Reusable React components +│ └── cli/ # Command-line interface (bin: ocpp-debugkit) ├── apps/ │ └── web/ # Single Next.js app (landing, inspector, docs, blog) ├── turbo.json # Turborepo task pipeline @@ -52,16 +54,20 @@ pnpm changeset # Add a changeset for release ## Package Dependency Graph ``` - core ← everything depends on this - / | \ - scenarios | reporter | replay - \ | / | - cli | - | | - apps/web (single Next.js app) +@ocpp-debugkit/toolkit (single package, subpath exports) + src/ + core ← internal modules depend on this + scenarios ← depends on core (internal) + reporter ← depends on core (internal) + replay ← depends on core (internal) + react ← depends on core, scenarios, reporter, replay (internal) + cli ← depends on core, scenarios, reporter (internal; Node-only) + +apps/web (single Next.js app) + imports: @ocpp-debugkit/toolkit/core, /scenarios, /reporter, /react, /replay ``` -**Build order:** core → scenarios/reporter/replay → cli → app +**Build order:** toolkit (all internal modules in one tsc pass) → app ## Testing Conventions diff --git a/CURRENT_STATE.md b/CURRENT_STATE.md index c20c69f..898573a 100644 --- a/CURRENT_STATE.md +++ b/CURRENT_STATE.md @@ -4,13 +4,13 @@ ## Current Version -`0.1.0` — Inspector MVP (released) +`0.2.0` — Packaging Consolidation + Scenario Evaluator & Replay (in progress) ## Active Milestone -**v0.1.0 — Inspector MVP (complete)** +**v0.2.0 — Packaging Consolidation + Scenario Evaluator & Replay (in progress)** -All v0.1.0 issues complete. Packages published to npm. Web app deployed. +Phase 0: Packaging consolidation — creating `@ocpp-debugkit/toolkit` single package with subpath exports. ## What's Done @@ -156,11 +156,23 @@ All v0.1.0 issues complete. Packages published to npm. Web app deployed. - ✅ `/docs` index with navigation sidebar - ✅ `/docs/quickstart`, `/docs/glossary`, `/docs/architecture`, `/docs/trace-format`, `/docs/cli`, `/docs/scenarios` +### Packaging Consolidation (v0.2.0 Phase 0 — in progress) + +- 🔄 `packages/toolkit/` — single `@ocpp-debugkit/toolkit` package created +- 🔄 Core, scenarios, reporter, CLI code moved into `src/core/`, `src/scenarios/`, `src/reporter/`, `src/cli/` +- 🔄 `src/replay/` and `src/react/` stub modules added +- 🔄 Subpath exports configured (`/core`, `/scenarios`, `/reporter`, `/replay`, `/react`, `/cli`, `/fixtures`) +- 🔄 CLI binary `ocpp-debugkit` via `package.json#bin` +- 🔄 Old package directories removed +- 🔄 Web app updated to consume `@ocpp-debugkit/toolkit` +- 🔄 All 196 existing tests pass from new locations +- 🔄 Build, lint, typecheck, format all green + ## What's Next -1. **v0.2.0** — Scenario evaluator & replay (expanded failure rules, replay - engine, `@ocpp-debugkit/react`, CLI external scenario files, HTML report - format, app replay UI) +1. **v0.2.0 remaining** — External fixture test, migration docs, release workflow + simplification, expanded failure detection (7 new rules), 10 scenarios, replay + engine, React components, HTML reports, app replay UI 2. **v0.3.0** — Integrations & OSS credibility (trace diffing, CI mode, anonymize, integration examples) 3. **v1.0.0** — API stabilization, 20+ scenarios, docs overhaul, contributor @@ -174,10 +186,9 @@ All v0.1.0 issues complete. Packages published to npm. Web app deployed. | Package | Status | Version | |---------|--------|---------| -| `@ocpp-debugkit/core` | published | 0.1.1 | -| `@ocpp-debugkit/scenarios` | published | 0.1.1 | -| `@ocpp-debugkit/reporter` | published | 0.1.1 | -| `@ocpp-debugkit/cli` | published | 0.1.1 | -| `@ocpp-debugkit/replay` | not started | — | -| `@ocpp-debugkit/react` | not started | — | +| `@ocpp-debugkit/toolkit` | in progress | 0.2.0 | +| `@ocpp-debugkit/core` | published (deprecated after v0.2.0) | 0.1.1 | +| `@ocpp-debugkit/scenarios` | published (deprecated after v0.2.0) | 0.1.1 | +| `@ocpp-debugkit/reporter` | published (deprecated after v0.2.0) | 0.1.1 | +| `@ocpp-debugkit/cli` | published (deprecated after v0.2.0) | 0.1.1 | | `apps/web` | deployed | — | diff --git a/apps/web/package.json b/apps/web/package.json index a43c554..4981069 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -12,9 +12,7 @@ "test:e2e": "playwright test" }, "dependencies": { - "@ocpp-debugkit/core": "workspace:*", - "@ocpp-debugkit/reporter": "workspace:*", - "@ocpp-debugkit/scenarios": "workspace:*", + "@ocpp-debugkit/toolkit": "workspace:*", "next": "16.2.10", "react": "19.2.4", "react-dom": "19.2.4" diff --git a/apps/web/src/app/docs/cli/page.tsx b/apps/web/src/app/docs/cli/page.tsx index d5ac5f0..e630869 100644 --- a/apps/web/src/app/docs/cli/page.tsx +++ b/apps/web/src/app/docs/cli/page.tsx @@ -9,7 +9,7 @@ export default function CliReferencePage() {

Installation

-        {`npm install -g @ocpp-debugkit/cli`}
+        {`npm install -g @ocpp-debugkit/toolkit`}
       

inspect

diff --git a/apps/web/src/app/docs/quickstart/page.tsx b/apps/web/src/app/docs/quickstart/page.tsx index 46d98ce..6b9c17c 100644 --- a/apps/web/src/app/docs/quickstart/page.tsx +++ b/apps/web/src/app/docs/quickstart/page.tsx @@ -5,7 +5,7 @@ export default function QuickStartPage() {

Install the CLI

-        {`npm install -g @ocpp-debugkit/cli`}
+        {`npm install -g @ocpp-debugkit/toolkit`}
       

Inspect a trace

@@ -40,7 +40,7 @@ ocpp-debugkit scenario run failed-auth`}

Use the core library

For programmatic use in your own TypeScript/JavaScript project:

-        {`import { parseTrace, buildSessionTimeline, detectFailures } from '@ocpp-debugkit/core';
+        {`import { parseTrace, buildSessionTimeline, detectFailures } from '@ocpp-debugkit/toolkit/core';
 
 const result = parseTrace(traceString);
 const sessions = buildSessionTimeline(result.events);
diff --git a/apps/web/src/app/inspector/page.tsx b/apps/web/src/app/inspector/page.tsx
index 41228b0..f3bbfd8 100644
--- a/apps/web/src/app/inspector/page.tsx
+++ b/apps/web/src/app/inspector/page.tsx
@@ -14,9 +14,9 @@ import {
   type Scenario,
   ParseError,
   MAX_INPUT_SIZE_BYTES,
-} from '@ocpp-debugkit/core';
-import { generateMarkdownReport } from '@ocpp-debugkit/reporter';
-import { scenarios } from '@ocpp-debugkit/scenarios';
+} from '@ocpp-debugkit/toolkit/core';
+import { generateMarkdownReport } from '@ocpp-debugkit/toolkit/reporter';
+import { scenarios } from '@ocpp-debugkit/toolkit/scenarios';
 
 // ---------------------------------------------------------------------------
 // Types
diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx
index a17a80b..be1703a 100644
--- a/apps/web/src/app/page.tsx
+++ b/apps/web/src/app/page.tsx
@@ -73,13 +73,13 @@ export default function HomePage() {
           Architecture
         
         

- A modular monorepo with independent packages. Use only what you need. + A single package with modular subpath exports. Use only what you need.

- - - - + + + +
@@ -91,7 +91,7 @@ export default function HomePage() {
             {`# Install the CLI
-npm install -g @ocpp-debugkit/cli
+npm install -g @ocpp-debugkit/toolkit
 
 # Inspect a trace
 ocpp-debugkit inspect trace.json
diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md
deleted file mode 100644
index 81de88f..0000000
--- a/packages/cli/CHANGELOG.md
+++ /dev/null
@@ -1,38 +0,0 @@
-# @ocpp-debugkit/cli
-
-## 0.1.1
-
-### Patch Changes
-
-- 38618c7: Add package READMEs for npm discoverability.
-- Updated dependencies [38618c7]
-  - @ocpp-debugkit/core@0.1.1
-  - @ocpp-debugkit/scenarios@0.1.1
-  - @ocpp-debugkit/reporter@0.1.1
-
-## 0.1.0
-
-### Minor Changes
-
-- a959fa4: Create CLI package with inspect, report, and scenario commands.
-
-  - `ocpp-debugkit inspect ` — parse + analyze + output summary
-  - `ocpp-debugkit report ` — generate Markdown report (stdout or --output)
-  - `ocpp-debugkit scenario list` — list all 5 built-in scenarios
-  - `ocpp-debugkit scenario run ` — run scenario through analysis engine,
-    compare detected vs expected failures
-  - Path safety: validated file paths, size limits
-  - Input validation: safe parsing, non-sensitive error messages
-  - 17 integration tests (execa-based)
-  - Converted JSON fixtures to TS modules (fixes Node.js ESM JSON import issue)
-
-### Patch Changes
-
-- Updated dependencies [07bca8c]
-- Updated dependencies [9543d50]
-- Updated dependencies [f2cef5d]
-- Updated dependencies [805434f]
-- Updated dependencies [17c2aa0]
-  - @ocpp-debugkit/core@0.1.0
-  - @ocpp-debugkit/reporter@0.1.0
-  - @ocpp-debugkit/scenarios@0.1.0
diff --git a/packages/cli/README.md b/packages/cli/README.md
deleted file mode 100644
index c0814e5..0000000
--- a/packages/cli/README.md
+++ /dev/null
@@ -1,69 +0,0 @@
-# @ocpp-debugkit/cli
-
-> Command-line interface for OCPP DebugKit — inspect traces, generate reports, run scenarios.
-
-## Installation
-
-```bash
-npm install -g @ocpp-debugkit/cli
-```
-
-## Commands
-
-### inspect
-
-Parse and analyze an OCPP trace file:
-
-```bash
-ocpp-debugkit inspect trace.json
-```
-
-Outputs event count, sessions, detected failures, and parse warnings.
-
-### report
-
-Generate a Markdown report:
-
-```bash
-# To stdout
-ocpp-debugkit report trace.json
-
-# To a file
-ocpp-debugkit report trace.json --output report.md
-
-# Specify format
-ocpp-debugkit report trace.json --format markdown
-```
-
-### scenario list
-
-List all available built-in scenarios:
-
-```bash
-ocpp-debugkit scenario list
-```
-
-### scenario run
-
-Run a scenario through the analysis engine and compare detected vs expected failures:
-
-```bash
-ocpp-debugkit scenario run failed-auth
-```
-
-**Note:** `scenario run` runs static fixtures through the local analysis engine only. It is not active endpoint testing, WebSocket simulation, or live station/CSMS testing.
-
-## Accepted Trace Formats
-
-- **JSON Object** — `{ "metadata": {...}, "events": [...] }`
-- **JSONL** — one event per line
-- **Bare array** — `[[2, "id", "Action", {}], ...]`
-
-## Limits
-
-- Maximum input size: 10 MB
-- Maximum event count: 10,000
-
-## License
-
-Apache 2.0
diff --git a/packages/cli/package.json b/packages/cli/package.json
deleted file mode 100644
index e8cd431..0000000
--- a/packages/cli/package.json
+++ /dev/null
@@ -1,54 +0,0 @@
-{
-  "name": "@ocpp-debugkit/cli",
-  "version": "0.1.1",
-  "description": "Command-line interface for OCPP DebugKit — inspect traces, generate reports, run scenarios.",
-  "license": "Apache-2.0",
-  "type": "module",
-  "main": "./dist/index.js",
-  "types": "./dist/index.d.ts",
-  "bin": {
-    "ocpp-debugkit": "./dist/index.js"
-  },
-  "files": [
-    "dist",
-    "README.md",
-    "NOTICE"
-  ],
-  "keywords": [
-    "ocpp",
-    "ocpp1.6",
-    "ev-charging",
-    "cli",
-    "debugging",
-    "trace-inspector"
-  ],
-  "repository": {
-    "type": "git",
-    "url": "https://github.com/ocpp-debugkit/ocpp-debugkit.git",
-    "directory": "packages/cli"
-  },
-  "homepage": "https://github.com/ocpp-debugkit/ocpp-debugkit/tree/main/packages/cli",
-  "bugs": {
-    "url": "https://github.com/ocpp-debugkit/ocpp-debugkit/issues"
-  },
-  "scripts": {
-    "build": "tsc -p tsconfig.json",
-    "typecheck": "tsc --noEmit",
-    "test": "vitest run",
-    "clean": "rm -rf dist .turbo"
-  },
-  "dependencies": {
-    "@ocpp-debugkit/core": "workspace:*",
-    "@ocpp-debugkit/scenarios": "workspace:*",
-    "@ocpp-debugkit/reporter": "workspace:*",
-    "commander": "^13.0.0"
-  },
-  "devDependencies": {
-    "typescript": "^5.7.0",
-    "vitest": "^3.0.0",
-    "execa": "^9.0.0"
-  },
-  "publishConfig": {
-    "access": "public"
-  }
-}
diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json
deleted file mode 100644
index dd1cdcb..0000000
--- a/packages/cli/tsconfig.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-  "extends": "../../tsconfig.base.json",
-  "compilerOptions": {
-    "outDir": "./dist",
-    "rootDir": "./src"
-  },
-  "include": ["src"],
-  "exclude": ["node_modules", "dist", "**/*.test.ts"]
-}
diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md
deleted file mode 100644
index bd233cb..0000000
--- a/packages/core/CHANGELOG.md
+++ /dev/null
@@ -1,38 +0,0 @@
-# @ocpp-debugkit/core
-
-## 0.1.1
-
-### Patch Changes
-
-- 38618c7: Add package READMEs for npm discoverability.
-
-## 0.1.0
-
-### Minor Changes
-
-- 07bca8c: Finalize core package for npm publishing.
-
-  - Add `sideEffects: false` for tree-shaking
-  - Add `files` field to limit published content
-  - Add `keywords`, `repository`, `homepage`, `bugs` fields for npm discoverability
-  - Verify barrel export is complete (types, schemas, parser, normalizer, timeline, detection, summarizer, validator, fixtures)
-
-- 9543d50: Implement trace parser, event normalizer, and Zod schemas.
-
-  - `parseTrace()` accepts JSON Object, JSONL, and bare array trace formats
-  - `normalizeEvents()` classifies message types, infers directions (ADR-0004),
-    and normalizes timestamps to epoch milliseconds (ADR-0005)
-  - Zod schemas validate all untrusted input, preventing prototype pollution
-  - Input size limit (10 MB) and event count limit (10,000) enforced
-  - Malformed individual events are skipped with `ParseWarning` (ADR-0007)
-  - Added `Failure`, `Scenario`, `SessionSummary`, `ValidationResult` types
-  - 78 new unit tests (46 normalizer + 32 parser)
-
-- f2cef5d: Implement session timeline, failure detection, summarizer, and validator.
-
-  - `buildSessionTimeline()` correlates events into sessions by transactionId (ADR-0006)
-  - `detectFailures()` implements 3 detection rules: FAILED_AUTHORIZATION,
-    CONNECTOR_FAULT, STATION_OFFLINE_DURING_SESSION
-  - `summarizeSession()` / `summarizeSessions()` produce overview statistics
-  - `validateMessage()` / `validateMessages()` check OCPP 1.6 JSON structural compliance
-  - 40 new unit tests (10 timeline + 11 detection + 5 summarizer + 14 validator)
diff --git a/packages/core/README.md b/packages/core/README.md
deleted file mode 100644
index 69a55b2..0000000
--- a/packages/core/README.md
+++ /dev/null
@@ -1,69 +0,0 @@
-# @ocpp-debugkit/core
-
-> Core data model, parser, normalizer, timeline, and failure detection for OCPP DebugKit.
-
-## Installation
-
-```bash
-npm install @ocpp-debugkit/core
-```
-
-## Usage
-
-### Parse a trace
-
-```typescript
-import { parseTrace } from '@ocpp-debugkit/core';
-
-const result = parseTrace(traceString);
-// result.events — normalized Event[]
-// result.warnings — ParseWarning[] for malformed entries
-```
-
-Accepts JSON Object, JSONL, and bare array formats.
-
-### Build session timeline
-
-```typescript
-import { buildSessionTimeline } from '@ocpp-debugkit/core';
-
-const sessions = buildSessionTimeline(result.events);
-```
-
-### Detect failures
-
-```typescript
-import { detectFailures } from '@ocpp-debugkit/core';
-
-const failures = detectFailures(result.events, sessions);
-// Three detection rules: FAILED_AUTHORIZATION, CONNECTOR_FAULT,
-// STATION_OFFLINE_DURING_SESSION
-```
-
-### Generate summaries
-
-```typescript
-import { summarizeSessions } from '@ocpp-debugkit/core';
-
-const summaries = summarizeSessions(sessions, failures);
-```
-
-### Validate messages
-
-```typescript
-import { validateMessage } from '@ocpp-debugkit/core';
-
-const result = validateMessage(event);
-// result.valid, result.errors
-```
-
-## Key Types
-
-- `Event` — canonical normalized OCPP message
-- `Session` — logical charging session
-- `Failure` — detected failure with severity and suggested steps
-- `Scenario` — predefined trace fixture with expected failures
-
-## License
-
-Apache 2.0
diff --git a/packages/core/package.json b/packages/core/package.json
deleted file mode 100644
index d35c9dd..0000000
--- a/packages/core/package.json
+++ /dev/null
@@ -1,61 +0,0 @@
-{
-  "name": "@ocpp-debugkit/core",
-  "version": "0.1.1",
-  "description": "Core data model, parser, normalizer, timeline, and failure detection for OCPP DebugKit.",
-  "license": "Apache-2.0",
-  "type": "module",
-  "sideEffects": false,
-  "main": "./dist/index.js",
-  "module": "./dist/index.js",
-  "types": "./dist/index.d.ts",
-  "exports": {
-    ".": {
-      "types": "./dist/index.d.ts",
-      "import": "./dist/index.js"
-    },
-    "./fixtures": {
-      "types": "./dist/fixtures/index.d.ts",
-      "import": "./dist/fixtures/index.js"
-    }
-  },
-  "files": [
-    "dist",
-    "README.md",
-    "NOTICE"
-  ],
-  "keywords": [
-    "ocpp",
-    "ocpp1.6",
-    "ev-charging",
-    "charge-point",
-    "csms",
-    "debugging",
-    "trace-parser",
-    "failure-detection"
-  ],
-  "repository": {
-    "type": "git",
-    "url": "https://github.com/ocpp-debugkit/ocpp-debugkit.git",
-    "directory": "packages/core"
-  },
-  "homepage": "https://github.com/ocpp-debugkit/ocpp-debugkit/tree/main/packages/core",
-  "bugs": {
-    "url": "https://github.com/ocpp-debugkit/ocpp-debugkit/issues"
-  },
-  "scripts": {
-    "build": "tsc -p tsconfig.json",
-    "typecheck": "tsc --noEmit",
-    "test": "vitest run",
-    "clean": "rm -rf dist .turbo"
-  },
-  "dependencies": {
-    "zod": "^4.4.3"
-  },
-  "devDependencies": {
-    "typescript": "^5.7.0",
-    "vitest": "^3.0.0"
-  },
-  "publishConfig": {
-    "access": "public"
-  }
-}
diff --git a/packages/reporter/CHANGELOG.md b/packages/reporter/CHANGELOG.md
deleted file mode 100644
index df6c77f..0000000
--- a/packages/reporter/CHANGELOG.md
+++ /dev/null
@@ -1,28 +0,0 @@
-# @ocpp-debugkit/reporter
-
-## 0.1.1
-
-### Patch Changes
-
-- 38618c7: Add package READMEs for npm discoverability.
-- Updated dependencies [38618c7]
-  - @ocpp-debugkit/core@0.1.1
-
-## 0.1.0
-
-### Minor Changes
-
-- 805434f: Create reporter package with Markdown report generator.
-
-  - `generateMarkdownReport()` produces a structured Markdown report with:
-    session overview, timeline summary, failures (with severity and suggested
-    steps), suggested next steps, and raw event appendix
-  - `AnalysisResult` input type representing the analysis pipeline output
-  - 11 tests covering structure, failure inclusion, readability, metadata, severity
-
-### Patch Changes
-
-- Updated dependencies [07bca8c]
-- Updated dependencies [9543d50]
-- Updated dependencies [f2cef5d]
-  - @ocpp-debugkit/core@0.1.0
diff --git a/packages/reporter/README.md b/packages/reporter/README.md
deleted file mode 100644
index 253a0e1..0000000
--- a/packages/reporter/README.md
+++ /dev/null
@@ -1,50 +0,0 @@
-# @ocpp-debugkit/reporter
-
-> Report generators for OCPP DebugKit analysis results.
-
-## Installation
-
-```bash
-npm install @ocpp-debugkit/reporter
-```
-
-## Usage
-
-### Generate a Markdown report
-
-```typescript
-import { generateMarkdownReport } from '@ocpp-debugkit/reporter';
-import type { AnalysisResult } from '@ocpp-debugkit/reporter';
-
-const report = generateMarkdownReport({
-  events,
-  sessions,
-  failures,
-  summaries,
-  warnings,
-});
-
-// report is a Markdown string with:
-// - Session overview table
-// - Timeline summary (action sequences)
-// - Failures (severity, description, suggested steps)
-// - Suggested next steps
-// - Raw event appendix
-```
-
-## Input Type
-
-```typescript
-interface AnalysisResult {
-  events: Event[];
-  sessions: Session[];
-  failures: Failure[];
-  summaries: SessionSummary[];
-  warnings: ParseWarning[];
-  metadata?: TraceMetadata;
-}
-```
-
-## License
-
-Apache 2.0
diff --git a/packages/reporter/package.json b/packages/reporter/package.json
deleted file mode 100644
index b94b5c7..0000000
--- a/packages/reporter/package.json
+++ /dev/null
@@ -1,55 +0,0 @@
-{
-  "name": "@ocpp-debugkit/reporter",
-  "version": "0.1.1",
-  "description": "Report generators (Markdown, HTML) for OCPP DebugKit analysis results.",
-  "license": "Apache-2.0",
-  "type": "module",
-  "sideEffects": false,
-  "main": "./dist/index.js",
-  "module": "./dist/index.js",
-  "types": "./dist/index.d.ts",
-  "exports": {
-    ".": {
-      "types": "./dist/index.d.ts",
-      "import": "./dist/index.js"
-    }
-  },
-  "files": [
-    "dist",
-    "README.md",
-    "NOTICE"
-  ],
-  "keywords": [
-    "ocpp",
-    "ocpp1.6",
-    "ev-charging",
-    "report",
-    "markdown",
-    "debugging"
-  ],
-  "repository": {
-    "type": "git",
-    "url": "https://github.com/ocpp-debugkit/ocpp-debugkit.git",
-    "directory": "packages/reporter"
-  },
-  "homepage": "https://github.com/ocpp-debugkit/ocpp-debugkit/tree/main/packages/reporter",
-  "bugs": {
-    "url": "https://github.com/ocpp-debugkit/ocpp-debugkit/issues"
-  },
-  "scripts": {
-    "build": "tsc -p tsconfig.json",
-    "typecheck": "tsc --noEmit",
-    "test": "vitest run",
-    "clean": "rm -rf dist .turbo"
-  },
-  "dependencies": {
-    "@ocpp-debugkit/core": "workspace:*"
-  },
-  "devDependencies": {
-    "typescript": "^5.7.0",
-    "vitest": "^3.0.0"
-  },
-  "publishConfig": {
-    "access": "public"
-  }
-}
diff --git a/packages/reporter/tsconfig.json b/packages/reporter/tsconfig.json
deleted file mode 100644
index dd1cdcb..0000000
--- a/packages/reporter/tsconfig.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-  "extends": "../../tsconfig.base.json",
-  "compilerOptions": {
-    "outDir": "./dist",
-    "rootDir": "./src"
-  },
-  "include": ["src"],
-  "exclude": ["node_modules", "dist", "**/*.test.ts"]
-}
diff --git a/packages/scenarios/CHANGELOG.md b/packages/scenarios/CHANGELOG.md
deleted file mode 100644
index d4a04a1..0000000
--- a/packages/scenarios/CHANGELOG.md
+++ /dev/null
@@ -1,28 +0,0 @@
-# @ocpp-debugkit/scenarios
-
-## 0.1.1
-
-### Patch Changes
-
-- 38618c7: Add package READMEs for npm discoverability.
-- Updated dependencies [38618c7]
-  - @ocpp-debugkit/core@0.1.1
-
-## 0.1.0
-
-### Minor Changes
-
-- 17c2aa0: Create scenarios package with 5 initial scenarios.
-
-  - Scenario registry with `getScenario()` lookup
-  - 5 scenarios: normal-session, failed-auth, connector-fault,
-    station-offline, unexpected-stop-reason
-  - Each scenario's expectedFailures aligns with v0.1 detection rules
-  - 21 tests (registry, engine integration, synthetic data policy)
-
-### Patch Changes
-
-- Updated dependencies [07bca8c]
-- Updated dependencies [9543d50]
-- Updated dependencies [f2cef5d]
-  - @ocpp-debugkit/core@0.1.0
diff --git a/packages/scenarios/README.md b/packages/scenarios/README.md
deleted file mode 100644
index e37b07f..0000000
--- a/packages/scenarios/README.md
+++ /dev/null
@@ -1,44 +0,0 @@
-# @ocpp-debugkit/scenarios
-
-> Predefined OCPP trace scenarios for testing the DebugKit analysis engine.
-
-## Installation
-
-```bash
-npm install @ocpp-debugkit/scenarios
-```
-
-## Usage
-
-```typescript
-import { scenarios, getScenario } from '@ocpp-debugkit/scenarios';
-
-// List all scenario names
-scenarios.forEach(s => console.log(s.name, s.expectedFailures));
-
-// Get a specific scenario
-const scenario = getScenario('failed-auth');
-
-// Run through the analysis engine
-import { parseTrace, buildSessionTimeline, detectFailures } from '@ocpp-debugkit/core';
-
-const result = parseTrace(JSON.stringify(scenario.trace));
-const sessions = buildSessionTimeline(result.events);
-const failures = detectFailures(result.events, sessions);
-```
-
-## Available Scenarios
-
-| Name | Expected Failures |
-|------|-------------------|
-| `normal-session` | none |
-| `failed-auth` | `FAILED_AUTHORIZATION` |
-| `connector-fault` | `CONNECTOR_FAULT` |
-| `station-offline` | `STATION_OFFLINE_DURING_SESSION` |
-| `unexpected-stop-reason` | none (parser/timeline-only fixture) |
-
-All scenario data is fully synthetic — no real station identifiers or personal data.
-
-## License
-
-Apache 2.0
diff --git a/packages/scenarios/package.json b/packages/scenarios/package.json
deleted file mode 100644
index c24ae2a..0000000
--- a/packages/scenarios/package.json
+++ /dev/null
@@ -1,55 +0,0 @@
-{
-  "name": "@ocpp-debugkit/scenarios",
-  "version": "0.1.1",
-  "description": "Predefined OCPP trace scenarios for testing the DebugKit analysis engine.",
-  "license": "Apache-2.0",
-  "type": "module",
-  "sideEffects": false,
-  "main": "./dist/index.js",
-  "module": "./dist/index.js",
-  "types": "./dist/index.d.ts",
-  "exports": {
-    ".": {
-      "types": "./dist/index.d.ts",
-      "import": "./dist/index.js"
-    }
-  },
-  "files": [
-    "dist",
-    "README.md",
-    "NOTICE"
-  ],
-  "keywords": [
-    "ocpp",
-    "ocpp1.6",
-    "ev-charging",
-    "scenarios",
-    "test-fixtures",
-    "trace"
-  ],
-  "repository": {
-    "type": "git",
-    "url": "https://github.com/ocpp-debugkit/ocpp-debugkit.git",
-    "directory": "packages/scenarios"
-  },
-  "homepage": "https://github.com/ocpp-debugkit/ocpp-debugkit/tree/main/packages/scenarios",
-  "bugs": {
-    "url": "https://github.com/ocpp-debugkit/ocpp-debugkit/issues"
-  },
-  "scripts": {
-    "build": "tsc -p tsconfig.json",
-    "typecheck": "tsc --noEmit",
-    "test": "vitest run",
-    "clean": "rm -rf dist .turbo"
-  },
-  "dependencies": {
-    "@ocpp-debugkit/core": "workspace:*"
-  },
-  "devDependencies": {
-    "typescript": "^5.7.0",
-    "vitest": "^3.0.0"
-  },
-  "publishConfig": {
-    "access": "public"
-  }
-}
diff --git a/packages/scenarios/tsconfig.json b/packages/scenarios/tsconfig.json
deleted file mode 100644
index dd1cdcb..0000000
--- a/packages/scenarios/tsconfig.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-  "extends": "../../tsconfig.base.json",
-  "compilerOptions": {
-    "outDir": "./dist",
-    "rootDir": "./src"
-  },
-  "include": ["src"],
-  "exclude": ["node_modules", "dist", "**/*.test.ts"]
-}
diff --git a/packages/toolkit/NOTICE b/packages/toolkit/NOTICE
new file mode 100644
index 0000000..36936e8
--- /dev/null
+++ b/packages/toolkit/NOTICE
@@ -0,0 +1,14 @@
+OCPP DebugKit
+Copyright 2026 OCPP DebugKit Contributors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/packages/toolkit/README.md b/packages/toolkit/README.md
new file mode 100644
index 0000000..65d8c73
--- /dev/null
+++ b/packages/toolkit/README.md
@@ -0,0 +1,35 @@
+# @ocpp-debugkit/toolkit
+
+Open-source DevTools for debugging OCPP charging sessions.
+
+## Installation
+
+```bash
+npm install @ocpp-debugkit/toolkit
+```
+
+## Usage
+
+### Modular imports (recommended)
+
+```ts
+import { parseTrace, detectFailures, buildSessionTimeline } from '@ocpp-debugkit/toolkit/core';
+import { builtInScenarios, getScenario } from '@ocpp-debugkit/toolkit/scenarios';
+import { generateMarkdownReport } from '@ocpp-debugkit/toolkit/reporter';
+import { ReplayEngine } from '@ocpp-debugkit/toolkit/replay';
+import { SessionTimeline } from '@ocpp-debugkit/toolkit/react';
+```
+
+### CLI
+
+```bash
+npm install -g @ocpp-debugkit/toolkit
+ocpp-debugkit inspect trace.json
+ocpp-debugkit report trace.json
+ocpp-debugkit scenario list
+ocpp-debugkit scenario run normal-session
+```
+
+## License
+
+Apache 2.0
diff --git a/packages/toolkit/package.json b/packages/toolkit/package.json
new file mode 100644
index 0000000..39c5e8a
--- /dev/null
+++ b/packages/toolkit/package.json
@@ -0,0 +1,110 @@
+{
+  "name": "@ocpp-debugkit/toolkit",
+  "version": "0.2.0",
+  "description": "Open-source DevTools for debugging OCPP charging sessions — parser, normalizer, timeline, failure detection, scenarios, replay, reports, CLI, and React components.",
+  "license": "Apache-2.0",
+  "type": "module",
+  "sideEffects": false,
+  "main": "./dist/index.js",
+  "module": "./dist/index.js",
+  "types": "./dist/index.d.ts",
+  "bin": {
+    "ocpp-debugkit": "./dist/cli/index.js"
+  },
+  "exports": {
+    ".": {
+      "types": "./dist/index.d.ts",
+      "import": "./dist/index.js"
+    },
+    "./core": {
+      "types": "./dist/core/index.d.ts",
+      "import": "./dist/core/index.js"
+    },
+    "./scenarios": {
+      "types": "./dist/scenarios/index.d.ts",
+      "import": "./dist/scenarios/index.js"
+    },
+    "./reporter": {
+      "types": "./dist/reporter/index.d.ts",
+      "import": "./dist/reporter/index.js"
+    },
+    "./replay": {
+      "types": "./dist/replay/index.d.ts",
+      "import": "./dist/replay/index.js"
+    },
+    "./react": {
+      "types": "./dist/react/index.d.ts",
+      "import": "./dist/react/index.js"
+    },
+    "./cli": {
+      "types": "./dist/cli/index.d.ts",
+      "import": "./dist/cli/index.js"
+    },
+    "./fixtures": {
+      "types": "./dist/core/fixtures/index.d.ts",
+      "import": "./dist/core/fixtures/index.js"
+    },
+    "./package.json": "./package.json"
+  },
+  "files": [
+    "dist",
+    "README.md",
+    "NOTICE"
+  ],
+  "keywords": [
+    "ocpp",
+    "ocpp1.6",
+    "ev-charging",
+    "charge-point",
+    "csms",
+    "debugging",
+    "trace-parser",
+    "failure-detection",
+    "replay",
+    "scenario",
+    "report",
+    "cli"
+  ],
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/ocpp-debugkit/ocpp-debugkit.git",
+    "directory": "packages/toolkit"
+  },
+  "homepage": "https://github.com/ocpp-debugkit/ocpp-debugkit/tree/main/packages/toolkit",
+  "bugs": {
+    "url": "https://github.com/ocpp-debugkit/ocpp-debugkit/issues"
+  },
+  "scripts": {
+    "build": "tsc -p tsconfig.json",
+    "typecheck": "tsc --noEmit",
+    "test": "vitest run",
+    "clean": "rm -rf dist .turbo"
+  },
+  "dependencies": {
+    "zod": "^4.4.3",
+    "commander": "^13.0.0"
+  },
+  "peerDependencies": {
+    "react": "^18.0.0 || ^19.0.0",
+    "react-dom": "^18.0.0 || ^19.0.0"
+  },
+  "peerDependenciesMeta": {
+    "react": {
+      "optional": true
+    },
+    "react-dom": {
+      "optional": true
+    }
+  },
+  "devDependencies": {
+    "@types/node": "^22.0.0",
+    "@types/react": "^19",
+    "@types/react-dom": "^19",
+    "execa": "^9.0.0",
+    "typescript": "^5.7.0",
+    "vitest": "^3.0.0"
+  },
+  "publishConfig": {
+    "access": "public"
+  }
+}
diff --git a/packages/cli/src/cli.test.ts b/packages/toolkit/src/cli/cli.test.ts
similarity index 98%
rename from packages/cli/src/cli.test.ts
rename to packages/toolkit/src/cli/cli.test.ts
index 12d36c9..083a4f2 100644
--- a/packages/cli/src/cli.test.ts
+++ b/packages/toolkit/src/cli/cli.test.ts
@@ -3,9 +3,9 @@ import { execa } from 'execa';
 import { writeFileSync, mkdtempSync } from 'node:fs';
 import { tmpdir } from 'node:os';
 import { join } from 'node:path';
-import { fixtures } from '@ocpp-debugkit/core';
+import { fixtures } from '../core/index.js';
 
-const CLI_PATH = join(process.cwd(), 'packages/cli/dist/index.js');
+const CLI_PATH = join(process.cwd(), 'packages/toolkit/dist/cli/index.js');
 
 // Helper: run the CLI with given args
 async function runCli(
diff --git a/packages/cli/src/commands/inspect.ts b/packages/toolkit/src/cli/commands/inspect.ts
similarity index 98%
rename from packages/cli/src/commands/inspect.ts
rename to packages/toolkit/src/cli/commands/inspect.ts
index 6e60907..6f8e9bc 100644
--- a/packages/cli/src/commands/inspect.ts
+++ b/packages/toolkit/src/cli/commands/inspect.ts
@@ -8,7 +8,7 @@ import {
   detectFailures,
   summarizeSessions,
   ParseError,
-} from '@ocpp-debugkit/core';
+} from '../../core/index.js';
 import { CliError, readTraceFile } from '../utils.js';
 
 export interface InspectOptions {
diff --git a/packages/cli/src/commands/report.ts b/packages/toolkit/src/cli/commands/report.ts
similarity index 95%
rename from packages/cli/src/commands/report.ts
rename to packages/toolkit/src/cli/commands/report.ts
index 8bd2990..f5c4128 100644
--- a/packages/cli/src/commands/report.ts
+++ b/packages/toolkit/src/cli/commands/report.ts
@@ -9,8 +9,8 @@ import {
   summarizeSessions,
   ParseError,
   type Trace,
-} from '@ocpp-debugkit/core';
-import { generateMarkdownReport } from '@ocpp-debugkit/reporter';
+} from '../../core/index.js';
+import { generateMarkdownReport } from '../../reporter/index.js';
 import { CliError, readTraceFile } from '../utils.js';
 
 export interface ReportOptions {
diff --git a/packages/cli/src/commands/scenario.ts b/packages/toolkit/src/cli/commands/scenario.ts
similarity index 97%
rename from packages/cli/src/commands/scenario.ts
rename to packages/toolkit/src/cli/commands/scenario.ts
index 3f2a660..7de9cd1 100644
--- a/packages/cli/src/commands/scenario.ts
+++ b/packages/toolkit/src/cli/commands/scenario.ts
@@ -6,8 +6,8 @@
  * live station/CSMS testing, or the v0.2 scenario evaluator.
  */
 
-import { parseTrace, buildSessionTimeline, detectFailures, ParseError } from '@ocpp-debugkit/core';
-import { scenarioNames, getScenario } from '@ocpp-debugkit/scenarios';
+import { parseTrace, buildSessionTimeline, detectFailures, ParseError } from '../../core/index.js';
+import { scenarioNames, getScenario } from '../../scenarios/index.js';
 import { CliError } from '../utils.js';
 
 export function scenarioListCommand(): void {
diff --git a/packages/cli/src/index.ts b/packages/toolkit/src/cli/index.ts
similarity index 100%
rename from packages/cli/src/index.ts
rename to packages/toolkit/src/cli/index.ts
diff --git a/packages/cli/src/utils.ts b/packages/toolkit/src/cli/utils.ts
similarity index 95%
rename from packages/cli/src/utils.ts
rename to packages/toolkit/src/cli/utils.ts
index 284f518..bc83920 100644
--- a/packages/cli/src/utils.ts
+++ b/packages/toolkit/src/cli/utils.ts
@@ -4,7 +4,7 @@
 
 import { readFileSync, statSync } from 'node:fs';
 import { resolve } from 'node:path';
-import { MAX_INPUT_SIZE_BYTES } from '@ocpp-debugkit/core';
+import { MAX_INPUT_SIZE_BYTES } from '../core/index.js';
 
 /** Maximum file size for trace input (10 MB). */
 const MAX_FILE_SIZE = MAX_INPUT_SIZE_BYTES;
diff --git a/packages/core/src/__fixtures__/connector-fault.ts b/packages/toolkit/src/core/__fixtures__/connector-fault.ts
similarity index 100%
rename from packages/core/src/__fixtures__/connector-fault.ts
rename to packages/toolkit/src/core/__fixtures__/connector-fault.ts
diff --git a/packages/core/src/__fixtures__/failed-auth.ts b/packages/toolkit/src/core/__fixtures__/failed-auth.ts
similarity index 100%
rename from packages/core/src/__fixtures__/failed-auth.ts
rename to packages/toolkit/src/core/__fixtures__/failed-auth.ts
diff --git a/packages/core/src/__fixtures__/normal-session.ts b/packages/toolkit/src/core/__fixtures__/normal-session.ts
similarity index 100%
rename from packages/core/src/__fixtures__/normal-session.ts
rename to packages/toolkit/src/core/__fixtures__/normal-session.ts
diff --git a/packages/core/src/detection.test.ts b/packages/toolkit/src/core/detection.test.ts
similarity index 100%
rename from packages/core/src/detection.test.ts
rename to packages/toolkit/src/core/detection.test.ts
diff --git a/packages/core/src/detection.ts b/packages/toolkit/src/core/detection.ts
similarity index 100%
rename from packages/core/src/detection.ts
rename to packages/toolkit/src/core/detection.ts
diff --git a/packages/core/src/fixtures.test.ts b/packages/toolkit/src/core/fixtures.test.ts
similarity index 100%
rename from packages/core/src/fixtures.test.ts
rename to packages/toolkit/src/core/fixtures.test.ts
diff --git a/packages/core/src/fixtures/index.ts b/packages/toolkit/src/core/fixtures/index.ts
similarity index 100%
rename from packages/core/src/fixtures/index.ts
rename to packages/toolkit/src/core/fixtures/index.ts
diff --git a/packages/core/src/index.ts b/packages/toolkit/src/core/index.ts
similarity index 100%
rename from packages/core/src/index.ts
rename to packages/toolkit/src/core/index.ts
diff --git a/packages/core/src/normalizer.test.ts b/packages/toolkit/src/core/normalizer.test.ts
similarity index 100%
rename from packages/core/src/normalizer.test.ts
rename to packages/toolkit/src/core/normalizer.test.ts
diff --git a/packages/core/src/normalizer.ts b/packages/toolkit/src/core/normalizer.ts
similarity index 100%
rename from packages/core/src/normalizer.ts
rename to packages/toolkit/src/core/normalizer.ts
diff --git a/packages/core/src/parser.test.ts b/packages/toolkit/src/core/parser.test.ts
similarity index 100%
rename from packages/core/src/parser.test.ts
rename to packages/toolkit/src/core/parser.test.ts
diff --git a/packages/core/src/parser.ts b/packages/toolkit/src/core/parser.ts
similarity index 100%
rename from packages/core/src/parser.ts
rename to packages/toolkit/src/core/parser.ts
diff --git a/packages/core/src/schemas.ts b/packages/toolkit/src/core/schemas.ts
similarity index 100%
rename from packages/core/src/schemas.ts
rename to packages/toolkit/src/core/schemas.ts
diff --git a/packages/core/src/summarizer.test.ts b/packages/toolkit/src/core/summarizer.test.ts
similarity index 100%
rename from packages/core/src/summarizer.test.ts
rename to packages/toolkit/src/core/summarizer.test.ts
diff --git a/packages/core/src/summarizer.ts b/packages/toolkit/src/core/summarizer.ts
similarity index 100%
rename from packages/core/src/summarizer.ts
rename to packages/toolkit/src/core/summarizer.ts
diff --git a/packages/core/src/timeline.test.ts b/packages/toolkit/src/core/timeline.test.ts
similarity index 100%
rename from packages/core/src/timeline.test.ts
rename to packages/toolkit/src/core/timeline.test.ts
diff --git a/packages/core/src/timeline.ts b/packages/toolkit/src/core/timeline.ts
similarity index 100%
rename from packages/core/src/timeline.ts
rename to packages/toolkit/src/core/timeline.ts
diff --git a/packages/core/src/types.ts b/packages/toolkit/src/core/types.ts
similarity index 100%
rename from packages/core/src/types.ts
rename to packages/toolkit/src/core/types.ts
diff --git a/packages/core/src/validator.test.ts b/packages/toolkit/src/core/validator.test.ts
similarity index 100%
rename from packages/core/src/validator.test.ts
rename to packages/toolkit/src/core/validator.test.ts
diff --git a/packages/core/src/validator.ts b/packages/toolkit/src/core/validator.ts
similarity index 100%
rename from packages/core/src/validator.ts
rename to packages/toolkit/src/core/validator.ts
diff --git a/packages/toolkit/src/index.ts b/packages/toolkit/src/index.ts
new file mode 100644
index 0000000..b7ded7f
--- /dev/null
+++ b/packages/toolkit/src/index.ts
@@ -0,0 +1,74 @@
+/**
+ * @ocpp-debugkit/toolkit — Open-source DevTools for debugging OCPP charging sessions.
+ *
+ * @module @ocpp-debugkit/toolkit
+ *
+ * The root barrel re-exports the most commonly used functions from the
+ * core module. For full modular consumption, use subpath exports:
+ *
+ *   import { parseTrace } from '@ocpp-debugkit/toolkit/core';
+ *   import { builtInScenarios } from '@ocpp-debugkit/toolkit/scenarios';
+ *   import { generateMarkdownReport } from '@ocpp-debugkit/toolkit/reporter';
+ *   import { ReplayEngine } from '@ocpp-debugkit/toolkit/replay';
+ *   import { SessionTimeline } from '@ocpp-debugkit/toolkit/react';
+ */
+
+// Re-export the most commonly used core functions for convenience
+export {
+  parseTrace,
+  ParseError,
+  MAX_INPUT_SIZE_BYTES,
+  MAX_EVENT_COUNT,
+  type TraceFormat,
+} from './core/index.js';
+
+export {
+  normalizeEvents,
+  normalizeTimestamp,
+  inferDirection,
+  reverseDirection,
+  classifyMessageType,
+  extractAction,
+  extractPayload,
+  extractErrorCode,
+  extractErrorDescription,
+} from './core/index.js';
+
+export { buildSessionTimeline } from './core/index.js';
+export { detectFailures } from './core/index.js';
+export { summarizeSession, summarizeSessions } from './core/index.js';
+export { validateMessage, validateMessages } from './core/index.js';
+
+// Re-export core types and schemas
+export type {
+  Trace,
+  Event,
+  Session,
+  Failure,
+  Scenario,
+  SessionSummary,
+  ValidationResult,
+  RawOcppMessage,
+  Direction,
+  MessageType,
+  FailureSeverity,
+  FailureCode,
+  TraceEventInput,
+  TraceMetadata,
+  ParseWarning,
+  ParseResult,
+} from './core/index.js';
+
+// Re-export schemas
+export {
+  directionSchema,
+  messageTypeSchema,
+  rawOcppMessageSchema,
+  traceEventInputSchema,
+  traceMetadataSchema,
+  traceSchema,
+  bareArraySchema,
+} from './core/index.js';
+
+// Re-export fixtures
+export { fixtures, fixtureNames } from './core/index.js';
diff --git a/packages/toolkit/src/react/components.ts b/packages/toolkit/src/react/components.ts
new file mode 100644
index 0000000..c304aea
--- /dev/null
+++ b/packages/toolkit/src/react/components.ts
@@ -0,0 +1,72 @@
+/**
+ * React component stubs for @ocpp-debugkit/toolkit/react.
+ *
+ * v0.2.0: These are stub implementations — presentational components
+ * that will be fully extracted from the Inspector in Phase 4.
+ * They are functional but minimal, using no external CSS framework.
+ */
+
+import type {
+  SessionTimelineProps,
+  MessageInspectorProps,
+  FailureSummaryProps,
+  ReportViewerProps,
+  ReplayControlsProps,
+} from './types.js';
+
+export function SessionTimeline({ events, selectedEventId, onSelectEvent }: SessionTimelineProps) {
+  return {
+    type: 'SessionTimeline',
+    props: { events, selectedEventId, onSelectEvent },
+  } as const;
+}
+
+export function MessageInspector({ event }: MessageInspectorProps) {
+  return {
+    type: 'MessageInspector',
+    props: { event },
+  } as const;
+}
+
+export function FailureSummary({ failures }: FailureSummaryProps) {
+  return {
+    type: 'FailureSummary',
+    props: { failures },
+  } as const;
+}
+
+export function ReportViewer({ html }: ReportViewerProps) {
+  return {
+    type: 'ReportViewer',
+    props: { html },
+  } as const;
+}
+
+export function ReplayControls({
+  isPlaying,
+  currentIndex,
+  totalEvents,
+  onPlay,
+  onPause,
+  onStep,
+  onStepBack,
+  onJump,
+  speed,
+  onSpeedChange,
+}: ReplayControlsProps) {
+  return {
+    type: 'ReplayControls',
+    props: {
+      isPlaying,
+      currentIndex,
+      totalEvents,
+      onPlay,
+      onPause,
+      onStep,
+      onStepBack,
+      onJump,
+      speed,
+      onSpeedChange,
+    },
+  } as const;
+}
diff --git a/packages/toolkit/src/react/index.ts b/packages/toolkit/src/react/index.ts
new file mode 100644
index 0000000..f508653
--- /dev/null
+++ b/packages/toolkit/src/react/index.ts
@@ -0,0 +1,23 @@
+/**
+ * Reusable React components for OCPP DebugKit.
+ *
+ * @module @ocpp-debugkit/toolkit/react
+ *
+ * These components are presentational — they accept data as props and
+ * render UI. They are SSR-safe (no window/document access at module level).
+ */
+
+export type {
+  SessionTimelineProps,
+  MessageInspectorProps,
+  FailureSummaryProps,
+  ReportViewerProps,
+  ReplayControlsProps,
+} from './types.js';
+export {
+  SessionTimeline,
+  MessageInspector,
+  FailureSummary,
+  ReportViewer,
+  ReplayControls,
+} from './components.js';
diff --git a/packages/toolkit/src/react/types.ts b/packages/toolkit/src/react/types.ts
new file mode 100644
index 0000000..a6c6712
--- /dev/null
+++ b/packages/toolkit/src/react/types.ts
@@ -0,0 +1,40 @@
+/**
+ * Prop types for the React components in @ocpp-debugkit/toolkit/react.
+ */
+
+import type { Event, Failure, SessionSummary } from '../core/index.js';
+
+export interface SessionTimelineProps {
+  events: Event[];
+  selectedEventId?: string | null;
+  onSelectEvent?: (eventId: string) => void;
+}
+
+export interface MessageInspectorProps {
+  event: Event | null;
+}
+
+export interface FailureSummaryProps {
+  failures: Failure[];
+}
+
+export interface ReportViewerProps {
+  /** The HTML report string (from generateHtmlReport). */
+  html: string;
+}
+
+export interface ReplayControlsProps {
+  isPlaying: boolean;
+  currentIndex: number;
+  totalEvents: number;
+  onPlay: () => void;
+  onPause: () => void;
+  onStep: () => void;
+  onStepBack: () => void;
+  onJump: (index: number) => void;
+  speed?: number;
+  onSpeedChange?: (speed: number) => void;
+}
+
+// Summary type re-export for consumers
+export type { SessionSummary };
diff --git a/packages/toolkit/src/replay/engine.ts b/packages/toolkit/src/replay/engine.ts
new file mode 100644
index 0000000..b38ecb7
--- /dev/null
+++ b/packages/toolkit/src/replay/engine.ts
@@ -0,0 +1,125 @@
+/**
+ * Replay engine — replays OCPP trace events in timestamp order.
+ *
+ * @module @ocpp-debugkit/toolkit/replay
+ */
+
+import type { Event, Failure } from '../core/index.js';
+
+// ---------------------------------------------------------------------------
+// Types
+// ---------------------------------------------------------------------------
+
+export interface ReplayEvent {
+  /** The normalized event. */
+  event: Event;
+  /** Failures detected at this event's position. */
+  failures: Failure[];
+  /** Zero-based index in the original event array. */
+  index: number;
+}
+
+export interface ReplayState {
+  /** Events replayed so far. */
+  played: ReplayEvent[];
+  /** Events remaining. */
+  remaining: Event[];
+  /** Whether the replay is complete. */
+  complete: boolean;
+}
+
+export interface ReplayOptions {
+  /** Playback speed multiplier (1 = real-time, 2 = 2x, etc.). Default: 1. */
+  speed?: number;
+  /** Start from this event index. Default: 0. */
+  startIndex?: number;
+}
+
+// ---------------------------------------------------------------------------
+// ReplayEngine
+// ---------------------------------------------------------------------------
+
+/**
+ * Deterministic replay engine for OCPP traces.
+ *
+ * The engine is pure — it does not use timers or I/O. The consumer
+ * calls `step()`, `stepBack()`, or `jumpTo()` to advance the replay.
+ */
+export class ReplayEngine {
+  private readonly events: Event[];
+  private readonly failures: Failure[];
+  private currentIndex: number;
+
+  constructor(events: Event[], failures: Failure[] = [], options?: ReplayOptions) {
+    this.events = [...events];
+    this.failures = [...failures];
+    this.currentIndex = options?.startIndex ?? 0;
+  }
+
+  /** Total number of events. */
+  get totalEvents(): number {
+    return this.events.length;
+  }
+
+  /** Current event index (0-based). Returns -1 if no events. */
+  get current(): number {
+    return this.events.length === 0 ? -1 : this.currentIndex;
+  }
+
+  /** Step forward one event. Returns the replay event, or null if complete. */
+  step(): ReplayEvent | null {
+    if (this.currentIndex >= this.events.length) {
+      return null;
+    }
+    const event = this.events[this.currentIndex];
+    if (!event) {
+      return null;
+    }
+    const index = this.currentIndex;
+    const failures = this.failures.filter((f) => f.eventIds.includes(event.id));
+    this.currentIndex++;
+    return { event, failures, index };
+  }
+
+  /** Step backward one event. Returns the replay event, or null if at start. */
+  stepBack(): ReplayEvent | null {
+    if (this.currentIndex <= 1) {
+      return null;
+    }
+    this.currentIndex -= 2;
+    return this.step();
+  }
+
+  /** Jump to a specific event index. Returns the replay event, or null if out of range. */
+  jumpTo(index: number): ReplayEvent | null {
+    if (index < 0 || index >= this.events.length) {
+      return null;
+    }
+    this.currentIndex = index;
+    return this.step();
+  }
+
+  /** Get the current replay state snapshot. */
+  getState(): ReplayState {
+    const played = [];
+    for (let i = 0; i < this.currentIndex && i < this.events.length; i++) {
+      const event = this.events[i];
+      if (!event) continue;
+      played.push({
+        event,
+        failures: this.failures.filter((f) => f.eventIds.includes(event.id)),
+        index: i,
+      });
+    }
+    return {
+      played,
+      remaining: this.events.slice(this.currentIndex),
+      complete: this.currentIndex >= this.events.length,
+    };
+  }
+
+  /** Reset the replay to the beginning. */
+  reset(): void {
+    this.currentIndex = 0;
+  }
+}
diff --git a/packages/toolkit/src/replay/index.ts b/packages/toolkit/src/replay/index.ts
new file mode 100644
index 0000000..fe68902
--- /dev/null
+++ b/packages/toolkit/src/replay/index.ts
@@ -0,0 +1,11 @@
+/**
+ * Replay engine module — v0.2.0.
+ *
+ * The replay engine takes a parsed trace and replays events in timestamp order,
+ * emitting state transitions and failures. It is pure — no timers, no I/O.
+ * The consumer (app or CLI) drives the clock.
+ *
+ * @module @ocpp-debugkit/toolkit/replay
+ */
+
+export { ReplayEngine, type ReplayEvent, type ReplayState, type ReplayOptions } from './engine.js';
diff --git a/packages/reporter/src/index.ts b/packages/toolkit/src/reporter/index.ts
similarity index 100%
rename from packages/reporter/src/index.ts
rename to packages/toolkit/src/reporter/index.ts
diff --git a/packages/reporter/src/markdown.test.ts b/packages/toolkit/src/reporter/markdown.test.ts
similarity index 99%
rename from packages/reporter/src/markdown.test.ts
rename to packages/toolkit/src/reporter/markdown.test.ts
index ead7c26..c21fb5b 100644
--- a/packages/reporter/src/markdown.test.ts
+++ b/packages/toolkit/src/reporter/markdown.test.ts
@@ -1,7 +1,7 @@
 import { describe, it, expect } from 'vitest';
 import { generateMarkdownReport } from './markdown.js';
 import type { AnalysisResult } from './types.js';
-import type { Event, Failure, Session, SessionSummary, RawOcppMessage } from '@ocpp-debugkit/core';
+import type { Event, Failure, Session, SessionSummary, RawOcppMessage } from '../core/index.js';
 
 // Helpers
 function makeEvent(
diff --git a/packages/reporter/src/markdown.ts b/packages/toolkit/src/reporter/markdown.ts
similarity index 99%
rename from packages/reporter/src/markdown.ts
rename to packages/toolkit/src/reporter/markdown.ts
index 0b36085..98c3024 100644
--- a/packages/reporter/src/markdown.ts
+++ b/packages/toolkit/src/reporter/markdown.ts
@@ -11,7 +11,7 @@
  */
 
 import type { AnalysisResult } from './types.js';
-import type { Event } from '@ocpp-debugkit/core';
+import type { Event } from '../core/index.js';
 
 // ---------------------------------------------------------------------------
 // Helpers
diff --git a/packages/reporter/src/types.ts b/packages/toolkit/src/reporter/types.ts
similarity index 91%
rename from packages/reporter/src/types.ts
rename to packages/toolkit/src/reporter/types.ts
index 924f28c..771f4e2 100644
--- a/packages/reporter/src/types.ts
+++ b/packages/toolkit/src/reporter/types.ts
@@ -6,7 +6,7 @@
  * detectFailures pipeline.
  */
 
-import type { Event, Failure, Session, SessionSummary } from '@ocpp-debugkit/core';
+import type { Event, Failure, Session, SessionSummary } from '../core/index.js';
 
 /** Analysis result passed to report generators. */
 export interface AnalysisResult {
diff --git a/packages/scenarios/src/__scenarios__/station-offline.ts b/packages/toolkit/src/scenarios/__scenarios__/station-offline.ts
similarity index 100%
rename from packages/scenarios/src/__scenarios__/station-offline.ts
rename to packages/toolkit/src/scenarios/__scenarios__/station-offline.ts
diff --git a/packages/scenarios/src/__scenarios__/unexpected-stop-reason.ts b/packages/toolkit/src/scenarios/__scenarios__/unexpected-stop-reason.ts
similarity index 100%
rename from packages/scenarios/src/__scenarios__/unexpected-stop-reason.ts
rename to packages/toolkit/src/scenarios/__scenarios__/unexpected-stop-reason.ts
diff --git a/packages/scenarios/src/index.test.ts b/packages/toolkit/src/scenarios/index.test.ts
similarity index 99%
rename from packages/scenarios/src/index.test.ts
rename to packages/toolkit/src/scenarios/index.test.ts
index 64cd0bb..bb77404 100644
--- a/packages/scenarios/src/index.test.ts
+++ b/packages/toolkit/src/scenarios/index.test.ts
@@ -9,7 +9,7 @@ import {
   stationOfflineScenario,
   unexpectedStopReasonScenario,
 } from './index.js';
-import { parseTrace, buildSessionTimeline, detectFailures } from '@ocpp-debugkit/core';
+import { parseTrace, buildSessionTimeline, detectFailures } from '../core/index.js';
 
 // ---------------------------------------------------------------------------
 // Registry tests
diff --git a/packages/scenarios/src/index.ts b/packages/toolkit/src/scenarios/index.ts
similarity index 96%
rename from packages/scenarios/src/index.ts
rename to packages/toolkit/src/scenarios/index.ts
index fa3ba21..7bc1524 100644
--- a/packages/scenarios/src/index.ts
+++ b/packages/toolkit/src/scenarios/index.ts
@@ -5,8 +5,8 @@
  * transaction IDs, idTag values, or personal data.
  */
 
-import type { Scenario, Trace } from '@ocpp-debugkit/core';
-import { fixtures } from '@ocpp-debugkit/core';
+import type { Scenario, Trace } from '../core/index.js';
+import { fixtures } from '../core/index.js';
 
 import stationOffline from './__scenarios__/station-offline.js';
 import unexpectedStopReason from './__scenarios__/unexpected-stop-reason.js';
diff --git a/packages/core/tsconfig.json b/packages/toolkit/tsconfig.json
similarity index 78%
rename from packages/core/tsconfig.json
rename to packages/toolkit/tsconfig.json
index dd1cdcb..060f8e5 100644
--- a/packages/core/tsconfig.json
+++ b/packages/toolkit/tsconfig.json
@@ -2,7 +2,8 @@
   "extends": "../../tsconfig.base.json",
   "compilerOptions": {
     "outDir": "./dist",
-    "rootDir": "./src"
+    "rootDir": "./src",
+    "jsx": "react-jsx"
   },
   "include": ["src"],
   "exclude": ["node_modules", "dist", "**/*.test.ts"]
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index cdb97e4..91ad8f9 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -38,15 +38,9 @@ importers:
 
   apps/web:
     dependencies:
-      '@ocpp-debugkit/core':
+      '@ocpp-debugkit/toolkit':
         specifier: workspace:*
-        version: link:../../packages/core
-      '@ocpp-debugkit/reporter':
-        specifier: workspace:*
-        version: link:../../packages/reporter
-      '@ocpp-debugkit/scenarios':
-        specifier: workspace:*
-        version: link:../../packages/scenarios
+        version: link:../../packages/toolkit
       next:
         specifier: 16.2.10
         version: 16.2.10(@babel/core@7.29.7)(@playwright/test@1.61.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -85,63 +79,33 @@ importers:
         specifier: ^5
         version: 5.9.3
 
-  packages/cli:
+  packages/toolkit:
     dependencies:
-      '@ocpp-debugkit/core':
-        specifier: workspace:*
-        version: link:../core
-      '@ocpp-debugkit/reporter':
-        specifier: workspace:*
-        version: link:../reporter
-      '@ocpp-debugkit/scenarios':
-        specifier: workspace:*
-        version: link:../scenarios
       commander:
         specifier: ^13.0.0
         version: 13.1.0
-    devDependencies:
-      execa:
-        specifier: ^9.0.0
-        version: 9.6.1
-      typescript:
-        specifier: ^5.7.0
-        version: 5.9.3
-      vitest:
-        specifier: ^3.0.0
-        version: 3.2.7(@types/node@22.20.0)(jiti@2.7.0)(lightningcss@1.32.0)
-
-  packages/core:
-    dependencies:
+      react:
+        specifier: ^18.0.0 || ^19.0.0
+        version: 19.2.4
+      react-dom:
+        specifier: ^18.0.0 || ^19.0.0
+        version: 19.2.4(react@19.2.4)
       zod:
         specifier: ^4.4.3
         version: 4.4.3
     devDependencies:
-      typescript:
-        specifier: ^5.7.0
-        version: 5.9.3
-      vitest:
-        specifier: ^3.0.0
-        version: 3.2.7(@types/node@22.20.0)(jiti@2.7.0)(lightningcss@1.32.0)
-
-  packages/reporter:
-    dependencies:
-      '@ocpp-debugkit/core':
-        specifier: workspace:*
-        version: link:../core
-    devDependencies:
-      typescript:
-        specifier: ^5.7.0
-        version: 5.9.3
-      vitest:
-        specifier: ^3.0.0
-        version: 3.2.7(@types/node@22.20.0)(jiti@2.7.0)(lightningcss@1.32.0)
-
-  packages/scenarios:
-    dependencies:
-      '@ocpp-debugkit/core':
-        specifier: workspace:*
-        version: link:../core
-    devDependencies:
+      '@types/node':
+        specifier: ^22.0.0
+        version: 22.20.0
+      '@types/react':
+        specifier: ^19
+        version: 19.2.17
+      '@types/react-dom':
+        specifier: ^19
+        version: 19.2.3(@types/react@19.2.17)
+      execa:
+        specifier: ^9.0.0
+        version: 9.6.1
       typescript:
         specifier: ^5.7.0
         version: 5.9.3
@@ -4364,8 +4328,8 @@ snapshots:
       '@next/eslint-plugin-next': 16.2.10
       eslint: 9.39.4(jiti@2.7.0)
       eslint-import-resolver-node: 0.3.10
-      eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0))
-      eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.63.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0))
+      eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.63.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0))
+      eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.63.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.63.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0))
       eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4(jiti@2.7.0))
       eslint-plugin-react: 7.37.5(eslint@9.39.4(jiti@2.7.0))
       eslint-plugin-react-hooks: 7.1.1(eslint@9.39.4(jiti@2.7.0))
@@ -4387,7 +4351,7 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0)):
+  eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.63.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)):
     dependencies:
       '@nolyfill/is-core-module': 1.0.39
       debug: 4.4.3
@@ -4398,22 +4362,22 @@ snapshots:
       tinyglobby: 0.2.17
       unrs-resolver: 1.12.2
     optionalDependencies:
-      eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.63.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0))
+      eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.63.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.63.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0))
     transitivePeerDependencies:
       - supports-color
 
-  eslint-module-utils@2.14.0(@typescript-eslint/parser@8.63.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)):
+  eslint-module-utils@2.14.0(@typescript-eslint/parser@8.63.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.63.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)):
     dependencies:
       debug: 3.2.7
     optionalDependencies:
       '@typescript-eslint/parser': 8.63.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)
       eslint: 9.39.4(jiti@2.7.0)
       eslint-import-resolver-node: 0.3.10
-      eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0))
+      eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.63.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0))
     transitivePeerDependencies:
       - supports-color
 
-  eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.63.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)):
+  eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.63.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.63.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)):
     dependencies:
       '@rtsao/scc': 1.1.0
       array-includes: 3.1.9
@@ -4424,7 +4388,7 @@ snapshots:
       doctrine: 2.1.0
       eslint: 9.39.4(jiti@2.7.0)
       eslint-import-resolver-node: 0.3.10
-      eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.63.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0))
+      eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.63.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.63.0(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0))
       hasown: 2.0.4
       is-core-module: 2.16.2
       is-glob: 4.0.3