Clark Industrial Process Environment (IPE) is a manufacturing facility operations platform. It gives technicians, supervisors, and integrated tools a unified interface for tracking jobs, managing notes and communications, flagging issues, and running AI-assisted workflows — all in real time across a connected facility floor.
Clarkware is a manufacturing facility operations platform. Technicians, supervisors, and integrated tools get a unified browser-based shell for tracking jobs, posting notes, managing communications, flagging issues, and running AI-assisted workflows. Built on a Fastify REST+WebSocket backend, an Eclipse Theia browser shell, PostgreSQL, MinIO, XMPP (Prosody), and optional IPC-CFX AMQP integration for machine-level event streams.
Factory floors run on a mix of paper, spreadsheets, WhatsApp, and legacy MES systems that don't talk to each other. Technicians have no single place to see the state of a job, post a note, flag an issue, and get an AI summary of what happened last shift — simultaneously, in real time, from any workstation. The tools that exist are either too heavyweight (SAP, Oracle) or too lightweight (shared folders, group chats).
A Turborepo monorepo with two applications (api, ipe) and eight shared packages. The API handles REST routes and a per-job WebSocket event stream. The IPE shell (Eclipse Theia) renders Clarkware widgets as IDE-style panels. The event store is append-only; every state transition is a domain event. AI routes connect to Claude for summarisation, note drafting, and alert routing.
P1 V1 — core job lifecycle (draft → active → paused → complete/void), notes, real-time WebSocket event stream, auth (JWT + RBAC), artifact storage (MinIO), and AI routes all working. Issues, conversations, shifts, and presence are scaffolded with DB tables and route stubs but no UI yet. XMPP messaging package complete but not wired to the Messages panel.
An operations platform for industrial environments that treats the factory floor like a software engineering environment — with real-time event streams, AI assistance, structured job records, and full observability. The goal is a tool where a technician can open a job, see everything that happened, post a note, and get an AI-drafted summary of the shift — all without leaving a single interface.
Manufacturing operations software ranges from $500K SAP implementations to whiteboards and group chats. The middle is underserved. Small and mid-size manufacturers — job shops, fabricators, assembly operations — need a connected operations platform they can self-host, configure, and extend without an enterprise software contract. Clarkware targets that gap.
Clarkware is structured as a pnpm + Turborepo monorepo with two applications and eight shared packages.
clark/
├── apps/
│ ├── api/ — REST + WebSocket backend (Fastify)
│ └── ipe/ — Browser-based operator shell (Eclipse Theia)
└── packages/
├── core/ — Domain model: types, enums, events, identity
├── db/ — PostgreSQL connection pool and query helpers
├── identity/ — Auth: JWT signing/verification, password hashing, RBAC
├── events/ — Append-only domain event store
├── ai/ — Anthropic Claude integration: summarisation, note drafting, alert routing
├── cfx/ — IPC-CFX AMQP publisher: CFXClient, CFX_MESSAGES constants
├── storage/ — MinIO object storage client and presigned URL helpers
├── messaging/ — XMPP client, room manager, sync engine
└── reporting/ — Job summary queries (foundation for reporting views)
Browser (IPE shell)
│ HTTP/REST WebSocket
▼ ▼
apps/api ─────────────────────────
│ │ │ │
@clark/ @clark/ @clark/ @clark/
db identity events storage
│
PostgreSQL (Docker) MinIO (Docker)
External services (all containerised):
| Service | Purpose | Port |
|---|---|---|
| PostgreSQL 16 | Primary database | 5432 |
| MinIO | Object / artifact storage | 9000 (API), 9001 (console) |
| Prosody | XMPP messaging server | 5222 |
| OpenSearch | Full-text search and log indexing | 9200 |
| OpenSearch Dashboards | Search analytics UI | 5601 |
| RabbitMQ 3.13 | IPC-CFX AMQP message broker | 5672 (AMQP), 15672 (management UI) |
| Tool | Version | Role |
|---|---|---|
| Node.js | 18.x | JavaScript runtime |
| pnpm | 10.x | Package manager with workspace support |
| Turborepo | 2.x | Monorepo build orchestration and caching |
| TypeScript | ~5.4 | Primary language across all packages |
| tsx | ^4 | TypeScript execution for the API dev server (no compile step needed) |
| webpack | 5 | Frontend bundle compilation for the IPE shell |
| Package | Role |
|---|---|
| Fastify 5 | HTTP server, plugin system, request/response schema validation |
@fastify/cors |
Cross-origin request handling |
@fastify/helmet |
HTTP security headers |
@fastify/websocket |
WebSocket upgrade and connection management |
@fastify/env |
Environment variable validation on startup |
@fastify/jwt |
JWT utility (token logic lives in @clark/identity) |
fastify-plugin |
Plugin encapsulation utility |
uuid |
ID generation (v4 random, v7 time-ordered) |
| Package | Key dependencies | Role |
|---|---|---|
@clark/core |
— | Pure TypeScript domain model. All branded ID types, enums, object shapes, event envelope types, and RBAC definitions. Zero runtime dependencies. |
@clark/db |
pg |
PostgreSQL pool singleton and typed query / queryOne helpers. |
@clark/identity |
jose, argon2 |
Password hashing (Argon2id), JWT access and refresh token sign and verify, RBAC can() permission checker. |
@clark/events |
@clark/core, @clark/db |
Append-only event store with optimistic concurrency (sequence number check). Immutability enforced by a DB trigger. |
@clark/ai |
@anthropic-ai/sdk |
Claude Sonnet integration — job and issue summarisation, note drafting, alert routing, AI review state management. Lazy-initialised client (API key loaded at first call). |
@clark/storage |
minio |
MinIO client wrapper — object upload, presigned PUT/GET URL generation, SHA-256 checksum verification, bucket bootstrapping. |
@clark/messaging |
@xmpp/client, @xmpp/debug |
XMPP client, MUC room manager, stanza builders, and an event-driven sync engine for offline-first messaging. |
@clark/sync |
@clark/core, @clark/db, @clark/events |
Conflict handler and sync queue for offline-first event reconciliation. |
@clark/reporting |
@clark/core, @clark/db |
Job summary read-model queries (foundation for reporting dashboards). |
| Package | Role |
|---|---|
| Eclipse Theia 1.69 | IDE-style browser shell — multi-panel layout, widget system, contribution points |
@theia/core |
Shell, widget manager, dependency injection (InversifyJS), messaging |
@theia/messages |
Status bar and notification integration |
| React 18 | Widget rendering inside Theia's ReactWidget base class |
reflect-metadata |
Required by InversifyJS decorators |
IPE extensions (local Theia extensions in apps/ipe/src/extensions/):
| Extension | Widgets | Description |
|---|---|---|
clark-core-extension |
Clark IPE (main panel), Job Context, Notes | Job list, create/manage jobs, job details, inline edit, notes |
clark-messaging-extension |
Messages | Real-time WebSocket event stream per job |
All routes below /v1/* (except auth) require Authorization: Bearer <token>.
| Method | Path | Description |
|---|---|---|
| POST | /v1/auth/login |
Username + password → access token + refresh token |
| POST | /v1/auth/refresh |
Refresh token → new access token |
| POST | /v1/auth/logout |
Invalidate session |
| Method | Path | Description |
|---|---|---|
| GET | /v1/jobs |
List all jobs |
| GET | /v1/jobs/:id |
Get job detail |
| POST | /v1/jobs |
Create job (starts in draft status) |
| POST | /v1/jobs/:id/start |
draft → active; writes job.started event |
| POST | /v1/jobs/:id/resume |
paused → active; writes job.resumed event |
| POST | /v1/jobs/:id/reopen |
completed|voided → draft; writes job.reopened event |
| PATCH | /v1/jobs/:id |
Update status (paused/completed/voided) and/or title, description, priority |
| Method | Path | Description |
|---|---|---|
| GET | /v1/notes?jobId=X |
List notes for a job |
| POST | /v1/notes |
Create note |
| POST | /v1/notes/:id/revise |
Append a revision to a note chain |
| Method | Path | Description |
|---|---|---|
| GET | /v1/facilities |
List facilities |
| GET | /v1/facilities/:id |
Get facility detail |
| POST | /v1/facilities |
Create facility |
| GET | /v1/workstations |
List all active workstations |
| Method | Path | Description |
|---|---|---|
| POST | /v1/ai/summarize-job |
Generate AI summary of a job |
| POST | /v1/ai/summarize-issue |
Generate AI summary of an issue |
| POST | /v1/ai/draft-note |
AI-draft a note for a given context |
| POST | /v1/ai/route-alert |
Route an alert to the correct actor or channel |
| POST | /v1/ai/review |
Update AI review state on a note or message |
| Method | Path | Description |
|---|---|---|
| POST | /v1/artifacts/upload-url |
Get a presigned MinIO PUT URL (15-minute window) |
| GET | /v1/artifacts/* |
Get a presigned MinIO GET URL for a stored object |
/v1/events, /v1/issues, /v1/conversations, /v1/presence, /v1/shifts, /v1/permissions
| Protocol | Path | Description |
|---|---|---|
| WebSocket | /ws?stream=job:<id> |
Subscribe to domain events for a job stream. Receives JSON-serialised DomainEvent objects as they are written by any route handler. |
PostgreSQL 16 with an append-only event store. Key tables:
| Table | Description |
|---|---|
actors |
Unified identity — all humans, AI agents, and automation services share one row |
persons |
Human user profile, Argon2id password hash, facility assignment |
agents |
AI agent actor specialisation |
facilities, zones, workstations |
Physical location hierarchy |
jobs |
Primary work unit — belongs to facility / zone / workstation |
tasks |
Sub-steps within a job |
issues |
Problems flagged against a job or workstation |
notes |
Append-only notes with revision chains (revision_chain_id + supersedes_note_id) |
conversations, messages |
XMPP-backed threaded conversations |
events |
Append-only domain event store — immutability enforced by a DB trigger |
artifacts |
Metadata for files stored in MinIO (presigned URL pattern) |
shifts |
Technician clock-in/out records |
presence_states |
Live actor presence (at-workstation, idle, offline, etc.) |
machine_sessions |
Tool/instrument connection sessions |
permission_grants |
Explicit RBAC grants with scope hierarchy (facility > zone > workstation > job) and expiry |
refresh_tokens |
Active JWT refresh token records |
- Authentication — login, token refresh, logout; sessions persist in
localStorage - Job list — load all jobs, display with type / priority / status badge
- Job creation — inline form: title, work order #, type, priority, workstation, description
- Job lifecycle — Start → Pause → Resume → Complete / Void → Reopen, all with domain events appended to the event store and broadcast over WebSocket
- Job editing — inline edit of title, description, priority for non-terminal jobs
- Job context panel — live detail view, refreshes after every action
- Notes — view and post notes per job; append-only with author ID and timestamp
- Real-time events — WebSocket stream per job; Messages panel shows all
DomainEventpayloads as they arrive - AI routes — all five
/v1/ai/*endpoints registered and reachable (requireANTHROPIC_API_KEYin.env) - Artifact routes — presigned upload and download URL generation via MinIO
- RBAC enforcement —
can()checks on all protected routes
- Issues — route registered, DB table exists
- Conversations — route registered, DB table exists
- Shifts — route registered, DB table exists (clock-in/out)
- Presence — route registered, DB table exists
- Permissions — route registered (explicit grant management)
@clark/messaging— XMPP client and room manager are complete; the Messages panel currently shows WebSocketDomainEventpayloads rather than true XMPP chat@clark/sync— Conflict handler and sync queue are implemented; offline-first mode not yet activated@clark/reporting— Job summary read-model queries exist; no reporting dashboard
Panel layout is now handled by ClarkFrontendContribution.onStart() in clark-core-extension/src/frontend-module.ts.
apps/ipe/src-gen/ is gitignored — theia build regenerates it cleanly without any manual patch required.
What: On load the IPE shell automatically logs in as admin / admin_dev_password. There is no login screen yet.
Location: apps/ipe/src/extensions/clark-core-extension/src/clark-widget.tsx, doLogin() method.
What: apps/ipe/lib/frontend/bundle.js (and .gz / .map variants) are checked into the repository. This allows the IPE to be launched without a full webpack build from scratch, but it means the bundle must be manually rebuilt and re-committed after any source change to the Theia extensions.
What: The API dev server runs TypeScript directly with tsx watch rather than compiling to dist/ first. pnpm --filter @clark/api start (the compiled path) is not regularly tested and dist/ may be stale.
| Tool | Minimum version | Notes |
|---|---|---|
| Node.js | 18.x LTS | Use nvm install 18 or https://nodejs.org |
| pnpm | 10.x | npm install -g pnpm@10 |
| Docker | 20.x | https://docs.docker.com/get-docker/ |
| Docker Compose | v2 | Bundled with Docker Desktop; on Linux: apt install docker-compose-plugin |
No global tsx, webpack, or typescript installation is required — all tooling is resolved from workspace node_modules.
Linux note: If you see Docker API version errors, prefix compose commands with
DOCKER_API_VERSION=1.41.
1. Install all dependencies
pnpm install2. Start infrastructure
DOCKER_API_VERSION=1.41 docker compose -f docker/compose.yml up -dWait a few seconds for Postgres to pass its healthcheck before continuing.
3. Build all shared packages
pnpm build4. Compile the IPE Theia extensions
cd apps/ipe/src/extensions/clark-core-extension && npx tsc && cd -
cd apps/ipe/src/extensions/clark-messaging-extension && npx tsc && cd -5. Bundle the IPE frontend
cd apps/ipe && ./node_modules/.bin/webpack --config webpack.config.js && cd -This takes 2–3 minutes. Output lands in apps/ipe/lib/frontend/bundle.js.
6. (Optional) Set your Anthropic API key for AI routes
echo "ANTHROPIC_API_KEY=sk-ant-..." >> apps/api/.envTerminal 1 — API
export PATH="$HOME/.local/bin:$PATH"
pnpm --filter @clark/api devRuns on http://localhost:3000. Auto-reloads on source changes.
Terminal 2 — IPE
cd apps/ipe && node lib/backend/main.js --port 3001Open http://localhost:3001 in any modern browser.
Only re-run steps 4–5 if you changed files under
apps/ipe/src/extensions/.
DOCKER_API_VERSION=1.41 docker compose -f docker/compose.yml down -v
DOCKER_API_VERSION=1.41 docker compose -f docker/compose.yml up -dThe -v flag removes named volumes, triggering a fresh init.sql run on next start.
| Resource | ID | Credentials / Value |
|---|---|---|
| Admin actor | actor_admin_01 |
username: admin, password: admin_dev_password |
| Facility | facility_dev_01 |
Development Facility |
| Zone | zone_dev_01 |
Dev Zone |
| Workstation | ws_dev_01 |
Dev Workstation |
| Job | job_dev_01 |
Hydraulic Pump Rebuild (active) |
| Job | job_dev_02 |
PCB Calibration (draft) |
| Port | Service |
|---|---|
| 3000 | Clark API (REST + WebSocket) |
| 3001 | Clark IPE browser shell |
| 5432 | PostgreSQL |
| 5672 | RabbitMQ AMQP (IPC-CFX broker) |
| 9000 | MinIO S3 API |
| 9001 | MinIO web console |
| 5222 | XMPP — Prosody client connections |
| 5280 | XMPP HTTP — Prosody HTTP API |
| 9200 | OpenSearch |
| 5601 | OpenSearch Dashboards |
| 15672 | RabbitMQ management UI (dev only) |