# How Nodeterm Separates Core Services from Shell Implementations

> Discover how Nodeterm separates core services from shell implementations using a strict core-vs-shell architecture. Learn about the abstract CorePlatform interface and runtime API injection.

- Repository: [eneskirca/nodeterm](https://github.com/eneskirca/nodeterm)
- Tags: architecture
- Published: 2026-08-23

---

**Nodeterm enforces a strict "core‑vs‑shell" architecture by defining an abstract `CorePlatform` interface in [`src/shared/platform.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/platform.ts) that core services consume, while concrete implementations for Electron and headless Node.js environments inject platform‑specific APIs at runtime through separate shell modules.**

Nodeterm’s codebase maintains a rigorous separation between reusable business logic and platform‑dependent code. This architectural pattern allows terminal management, workspace persistence, and agent handling to run identically across desktop and server deployments. By analyzing how nodeterm separates core services from shell implementations, developers can adopt a testable, portable pattern for cross‑platform TypeScript applications.

## The Three-Layer Architecture

Nodeterm organizes its codebase into three distinct layers, each with a specific responsibility and directory structure.

**Core services** reside in `src/core/` and contain pure TypeScript modules implementing business logic. These files—such as [`src/core/pty-devices.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/pty-devices.ts), [`src/core/workspace-store.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/workspace-store.ts), and [`src/core/agent-status-mirror.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/agent-status-mirror.ts)—handle terminal emulation, tmux integration, and state persistence without importing Electron or browser APIs.

**Desktop shell** implementations live in `src/main/` and provide the Electron main‑process integration. The concrete `PlatformElectron` class in [`src/main/platform-electron.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/main/platform-electron.ts) wires core services to the OS, managing native menus, clipboard access, and window handling through Electron APIs.

**Server edition shell** provides a headless Node.js runtime in [`src/server/platform-server.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/server/platform-server.ts). This implementation satisfies the same `CorePlatform` interface using standard Node.js HTTP/WebSocket servers and `node:child_process`, enabling browser‑based clients to connect without Electron dependencies.

## The Platform Interface Contract

The separation hinges on a **platform seam** defined in [`src/shared/platform.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/platform.ts). This file exports the abstract `CorePlatform` interface, which declares methods such as `getEnv()`, `spawnPty()`, and `readFile()`.

Core modules import only this abstract type. They never reference concrete Electron or Node.js server implementations directly. Instead, they receive a platform instance through constructor injection, delegating all platform‑specific operations to this interface.

```typescript
// src/core/pty-manager.ts (core service)
import { CorePlatform } from '@shared/platform';

export class PtyManager {
  constructor(private readonly platform: CorePlatform) {}

  async createTerminal(cmd: string) {
    // Delegates to shell‑specific implementation
    const pty = await this.platform.spawnPty({ command: cmd });
    return pty;
  }
}

```

Because [`src/core/pty-manager.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/pty-manager.ts) depends only on the `CorePlatform` abstraction, it remains agnostic to whether it runs inside an Electron window or a headless Docker container.

## Shell‑Specific Implementations

Concrete platform implementations reside in separate directories, allowing the same core code to execute in fundamentally different environments.

### Electron Desktop Shell

The [`src/main/platform-electron.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/main/platform-electron.ts) file implements `CorePlatform` using Electron‑specific APIs and native Node modules. It handles PTY creation through `node-pty` compiled for Electron’s ABI and manages system‑level features like native dialogs and clipboard access.

```typescript
// src/main/platform-electron.ts
import { CorePlatform } from '@shared/platform';
import { execFile } from 'node:child_process';

export class PlatformElectron implements CorePlatform {
  async spawnPty(opts) {
    // Uses node-pty compiled for Electron's ABI
    const pty = await import('node-pty');
    return pty.spawn(opts.command, [], { cwd: opts.cwd });
  }

  // Additional methods for clipboard, dialogs, etc.
}

```

### Headless Server Shell

The [`src/server/platform-server.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/server/platform-server.ts) file provides a lightweight alternative for server deployments. It implements the same interface using standard Node.js APIs, falling back to plain shell processes when tmux is unavailable and exposing functionality through WebSocket connections.

```typescript
// src/server/platform-server.ts
import { CorePlatform } from '@shared/platform';
import { spawn } from 'node:child_process';

export class PlatformServer implements CorePlatform {
  async spawnPty(opts) {
    // Fallback to standard spawn in headless environments
    return spawn(opts.command, [], { cwd: opts.cwd });
  }

  // Minimal implementations for server contexts
}

```

## Enforcement and Testing

Nodeterm actively prevents platform leakage through architectural tests. The file [`src/core/no-electron.test.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/no-electron.test.ts) contains assertions that verify core modules never import Electron or browser‑specific APIs.

This boundary enables **unit testing in isolation**. Because `src/core/` modules depend only on the abstract `CorePlatform` interface, developers can inject mock implementations during testing without loading heavy Electron contexts or native binaries.

## Summary

- **Abstract interface**: [`src/shared/platform.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/platform.ts) defines the `CorePlatform` contract that core services consume.
- **Pure core layer**: Modules in `src/core/` (including [`src/core/pty-devices.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/pty-devices.ts) and [`src/core/workspace-store.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/workspace-store.ts)) remain free of Electron and browser dependencies.
- **Desktop implementation**: [`src/main/platform-electron.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/main/platform-electron.ts) provides the Electron‑specific `CorePlatform` implementation for native desktop features.
- **Server implementation**: [`src/server/platform-server.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/server/platform-server.ts) delivers a headless Node.js implementation for browser‑based deployments.
- **Boundary testing**: [`src/core/no-electron.test.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/no-electron.test.ts) enforces the architectural boundary, ensuring core services remain portable.

## Frequently Asked Questions

### What is the platform seam in nodeterm?

The platform seam is the abstract `CorePlatform` interface defined in [`src/shared/platform.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/platform.ts). It acts as a boundary between platform‑agnostic core logic and platform‑specific shell implementations, allowing core modules to call methods like `spawnPty()` without knowing whether they run in Electron or a headless server.

### How does nodeterm prevent Electron dependencies from leaking into core services?

Nodeterm enforces the boundary through a combination of architectural discipline and automated testing. Core modules reside in `src/core/` and import only the abstract `CorePlatform` type, while [`src/core/no-electron.test.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/no-electron.test.ts) fails if any core file imports Electron APIs, ensuring the separation remains intact.

### Can nodeterm run as a purely headless server?

Yes. By using the `PlatformServer` implementation in [`src/server/platform-server.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/server/platform-server.ts), nodeterm can run the same core services found in [`src/core/agent-status-mirror.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/agent-status-mirror.ts) and [`src/core/workspace-store.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/core/workspace-store.ts) without any GUI components, serving terminal sessions over WebSocket connections to browser‑based clients.

### Where are the concrete platform implementations located in the nodeterm repository?

The desktop implementation lives in [`src/main/platform-electron.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/main/platform-electron.ts), which uses Electron APIs and native Node modules. The server implementation resides in [`src/server/platform-server.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/server/platform-server.ts), which uses standard Node.js libraries. Both files implement the `CorePlatform` interface exported from [`src/shared/platform.ts`](https://github.com/eneskirca/nodeterm/blob/main/src/shared/platform.ts).