@zvk/process-kit
v0.1.3
Published
Node-oriented process lifecycle, command policy, log buffer, and process manager helpers for local-first ZVK applications.
Readme
@zvk/process-kit
Local-first process orchestration primitives for Node/Bun applications: safe command descriptors, command policy checks, bounded process logs, and process manager adapter boundaries.
Package boundary
- Node/Bun only. Do not import this package in browser-only code.
- Keep command allow-lists, project lookup, persistence, job IDs, authz, and user-facing security defaults in the app.
- Keep policy checks and lifecycle machinery reusable and deterministic in the package.
- The root export contains pure command, policy, log, and port types. Runtime adapters stay on explicit subpaths.
See ../../docs/package-boundary-matrix.md for the package-family runtime
boundary matrix. This package is Node/Bun-oriented; fake process helpers are
test-only and the process manager adapter stays on @zvk/process-kit/node.
Public imports
import { describePackageScript } from "@zvk/process-kit/commands";
import { assertProcessCommandAllowed, evaluateProcessCommandPolicy } from "@zvk/process-kit/security";
import { createProcessLogBuffer } from "@zvk/process-kit/logs";
import { createNodeProcessManager } from "@zvk/process-kit/node";
import type { ProcessManagerPort } from "@zvk/process-kit/ports";Use only package public entry points above (@zvk/process-kit/*). Do not import from src, dist, or private relative paths.
Commands module
@zvk/process-kit/commands provides deterministic command descriptors.
import { describePackageScript, type ProcessCommandDescriptor } from "@zvk/process-kit/commands";
const cmd = describePackageScript({
packageManager: "bun",
script: "dev",
cwd: "/workspace/apps/my-app",
args: ["--port", "3000"],
});
const descriptor: ProcessCommandDescriptor = {
...cmd,
env: { NODE_ENV: "development" },
label: "bun run dev",
};describePackageScript normalizes package scripts into command + args descriptors and never shells out.
Security module
Use /security to keep command execution constrained.
import {
evaluateProcessCommandPolicy,
assertProcessCommandAllowed,
} from "@zvk/process-kit/security";
const policy = {
allowShell: false,
allowedCommands: ["bun", "npm", "pnpm", "yarn"],
blockedCommands: ["sh", "bash"],
maxArgs: 8,
};
const violations = evaluateProcessCommandPolicy(descriptor, policy);
assertProcessCommandAllowed(descriptor, policy);evaluateProcessCommandPolicyreturns machine-readable violation objects (code,message, optionalfield).assertProcessCommandAllowedthrows with a concise violation summary and is intended for request-time guard rails.- Keep your app's final policy decision in one place (route/service layer).
Logs module
@zvk/process-kit/logs provides a bounded in-memory process log buffer.
import { createProcessLogBuffer } from "@zvk/process-kit/logs";
const buffer = createProcessLogBuffer({ maxEntries: 500 });
buffer.append({ stream: "stdout", text: "server started" });
const latest = buffer.list({ afterSequence: 10, limit: 20 });Use this for local process status feeds and polling snapshots.
Process manager ports module
@zvk/process-kit/ports defines process lifecycle boundaries (start/stop/get/list shape).
- Keep persistence decisions in your app layer.
- Return snapshots/metadata from your app-owned persistence layer and map to the port contract here.
- Treat adapter output as transport-neutral; do not couple UI state to adapter internals.
Node adapter
@zvk/process-kit/node is the runtime adapter boundary for Node/Bun process execution.
import { spawn } from "node:child_process";
import { createNodeProcessManager } from "@zvk/process-kit/node";
import { createProcessLogBuffer } from "@zvk/process-kit/logs";
import { describePackageScript } from "@zvk/process-kit/commands";
const logs = createProcessLogBuffer({ maxEntries: 500 });
const manager = createNodeProcessManager({
policy: { allowedCommands: ["bun", "npm", "pnpm", "yarn"], allowShell: false },
spawn: (command, args, options) =>
spawn(command, [...args], {
cwd: options.cwd,
env: { ...process.env, ...options.env },
shell: false,
}),
now: () => Date.now(),
onOutput: logs.append,
stop: {
gracefulSignal: "SIGTERM",
forceSignal: "SIGKILL",
timeoutMs: 5_000,
},
});
const command = describePackageScript({
packageManager: "bun",
script: "dev",
cwd: "/workspace/apps/my-app",
});
await manager.start(command);
const lines = logs.list({ limit: 50 });onOutput emits normalized { processId, stream, text, timestamp } events for
stdout, stderr, and lifecycle system messages. It is intentionally
compatible with createProcessLogBuffer().append; persist or redact logs in the
app layer when needed. stop.timeoutMs is opt-in: without it, stop() preserves
the legacy single-signal behavior.
The adapter should stay small and avoid policy decisions beyond enforcement boundaries.
Test utilities
@zvk/process-kit/test-utils provides deterministic, framework-neutral doubles for fake processes, fake spawn functions, and fake process managers so you can test policy and lifecycle behavior without running real commands.
Local-first app example
import { describePackageScript } from "@zvk/process-kit/commands";
import { evaluateProcessCommandPolicy } from "@zvk/process-kit/security";
import { createProcessLogBuffer } from "@zvk/process-kit/logs";
const command = describePackageScript({
packageManager: "bun",
script: "dev",
cwd: "/workspace/apps/my-app",
args: ["--host", "127.0.0.1", "--port", "3000"],
});
const policy = {
allowShell: false,
allowedCommands: ["bun", "npm", "pnpm", "yarn"],
blockedCommands: ["rm", "sudo"],
maxArgs: 6,
};
const violations = evaluateProcessCommandPolicy(command, policy);
if (violations.length > 0) {
throw new Error(violations[0]?.message);
}
const logs = createProcessLogBuffer({ maxEntries: 200 });
logs.append({ stream: "system", text: `approved command: ${command.label}` });Use this pattern for request handlers that launch or restart local scripts in response to workspace actions.
Migration toward Promptliano
- Put project lookup and process persistence in Promptliano application services.
- Validate commands using
@zvk/process-kit/securityonly after you apply app-level authz and workspace ownership checks. - Persist only canonical process snapshots (ids, status, timestamps, exit code) in app models.
- Keep long-lived command history and UI state in app-owned stores; keep package helpers pure.
- Map package policy violations to app contract error codes at route boundaries.
Repo Skill
Use .codex/skills/use-zvk-process-kit/SKILL.md when maintaining this package.
See ../../docs/package-boundary-matrix.md for the Node-only and test-only subpaths.
