How Nodeterm Separates Core Services from Shell Implementations
Nodeterm enforces a strict "core‑vs‑shell" architecture by defining an abstract CorePlatform interface in 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, src/core/workspace-store.ts, and 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 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. 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. 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.
// 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 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 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.
// 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 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.
// 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 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.tsdefines theCorePlatformcontract that core services consume. - Pure core layer: Modules in
src/core/(includingsrc/core/pty-devices.tsandsrc/core/workspace-store.ts) remain free of Electron and browser dependencies. - Desktop implementation:
src/main/platform-electron.tsprovides the Electron‑specificCorePlatformimplementation for native desktop features. - Server implementation:
src/server/platform-server.tsdelivers a headless Node.js implementation for browser‑based deployments. - Boundary testing:
src/core/no-electron.test.tsenforces 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. 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 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, nodeterm can run the same core services found in src/core/agent-status-mirror.ts and 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, which uses Electron APIs and native Node modules. The server implementation resides in src/server/platform-server.ts, which uses standard Node.js libraries. Both files implement the CorePlatform interface exported from src/shared/platform.ts.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →