Skip to main content
outfitter-daemon Patterns for @outfitter/daemon including lifecycle management, IPC communication, health checks, and PID files. Use when building background services, daemons, or when "daemon", "IPC", "health check", "background service", or "@outfitter/daemon" are mentioned.
Jump to install Skills Marketplace Discover and explore AI skills built by the community.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Copy promptShow prompt details A direct command skips the review prompt. Inspect the source before running it.
npx skills add https://github.com/outfitter-dev/outfitter --skill outfitter-daemonThe command stays on one line. Scroll horizontally to inspect it before copying.
Prefer a local copy? Download the files currently available to SkillsMP.
Download Zip Downloading... More from this repository Creates handlers, CLI commands, MCP tools, and daemon services following Outfitter Dev Kit conventions. Use when adding new components to a project, scaffolding code, or when "create handler", "new command", "add tool", or "daemon service" are mentioned.
Deep patterns for @outfitter/mcp including tool registration, Zod schemas, resources, and server configuration. Use when building MCP servers, registering tools, defining resources, or when "MCP server", "MCP tool", "registerTool", or "@outfitter/mcp" are mentioned.
Generates patterns, templates, and guides for @outfitter/* packages. Covers transport-agnostic handler systems, Result types, error taxonomy, and package APIs. Use when working with @outfitter/*, Result types, Handler contract, error taxonomy, or when Result, Handler, ValidationError, NotFoundError, OutfitterError, or package names like contracts, cli, mcp, schema, tui, daemon, config, logging are mentioned.
name outfitter-daemon version 0.1.0 description Patterns for @outfitter/daemon including lifecycle management, IPC communication, health checks, and PID files. Use when building background services, daemons, or when "daemon", "IPC", "health check", "background service", or "@outfitter/daemon" are mentioned. allowed-tools Read Write Edit Glob Grep Bash(bun *)
Daemon Patterns
Deep dive into @outfitter/daemon patterns.
Creating a Daemon
import { createDaemon, getPidPath } from "@outfitter/daemon" ;
import { createLogger } from "@outfitter/logging" ;
const logger = createLogger ({ name : "my-daemon" });
const daemon = createDaemon ({
name : "my-daemon" ,
pidFile : getPidPath ("my-daemon" ),
logger,
shutdownTimeout : 10000 ,
});
Lifecycle
daemon.onShutdown (async () => {
logger.info ("Shutting down..." );
await closeConnections ();
await flushBuffers ();
});
const result = await daemon.start ();
if (result.isErr ()) {
logger.error ("Failed to start" , { error : result.error });
process.exit (1 );
}
Communication Patterns Two approaches for daemon communication, each suited to different use cases.
HTTP on Unix Socket (recommended for scaffolds) Simpler pattern using Bun.serve() on a Unix socket. Standard REST-style endpoints — any tool can curl them. Best for health checks, metrics, admin APIs.
import { getSocketPath } from "@outfitter/daemon" ;
const socketPath = getSocketPath ("my-daemon" );
const server = Bun .serve ({
unix : socketPath,
fetch (request ) {
const url = new URL (request.url );
if (url.pathname === "/health" ) {
return Response .json ({
status : "ok" ,
uptime : process.uptime (),
});
}
if (url.pathname === "/shutdown" && request.method === "POST" ) {
setTimeout (() => void daemon.stop (), 100 );
return new Response ("Shutting down" );
}
return new Response ("Not found" , { status : 404 });
},
});
daemon.onShutdown (async () => {
server.stop ();
});
CLI commands use fetch over the Unix socket:
await fetch (`http://unix:${socketPath} :/shutdown` , { method : "POST" });
const health = await fetch (`http://unix:${socketPath} :/health` );
IPC (structured messaging) Lower-level pattern using createIpcServer/createIpcClient for typed bidirectional messaging over Unix sockets. Best for language servers, editor plugins, agent coordination, or high-frequency communication.
import { createIpcServer, getSocketPath } from "@outfitter/daemon" ;
const ipcServer = createIpcServer (getSocketPath ("my-daemon" ));
ipcServer.onMessage (async (msg) => {
const message = msg as { type : string ; payload ?: unknown };
switch (message.type ) {
case "status" :
return { status : "ok" , uptime : process.uptime () };
case "reload" :
await reloadConfig ();
return { success : true };
case "metrics" :
return getMetrics ();
default :
return { error : "Unknown command" };
}
});
daemon.onShutdown (async () => {
await ipcServer.close ();
});
await ipcServer.listen ();
CLI commands use the IPC client:
import { createIpcClient, getSocketPath } from "@outfitter/daemon" ;
const client = createIpcClient (getSocketPath ("my-daemon" ));
await client.connect ();
const status = await client.send <{ status : string }>({ type : "status" });
console .log ("Status:" , status);
client.close ();
When to Use Each Concern HTTP IPC Simplicity Simpler — standard REST patterns More complex — message framing, correlation External access Any tool can curl Requires the IPC client library Type safety Untyped (parse JSON manually) Typed request/response protocol Bidirectional Request/response only Full bidirectional messaging Best for Admin APIs, health, metrics Language servers, editor plugins, agents
The scaffold daemon preset uses HTTP by default. Use --example ipc for the IPC pattern.
Health Checks import { createHealthChecker } from "@outfitter/daemon" ;
import { Result } from "@outfitter/contracts" ;
const healthChecker = createHealthChecker ([
{
name : "memory" ,
check : async () => {
const used = process.memoryUsage ().heapUsed / 1024 / 1024 ;
return used < 500
? Result .ok (undefined )
: Result .err (new Error (`High memory: ${used.toFixed(2 )} MB` ));
},
},
{
name : "database" ,
check : async () => {
try {
await db.ping ();
return Result .ok (undefined );
} catch {
return Result .err (new Error ("Database unreachable" ));
}
},
},
]);
Exposing Health via HTTP if (url.pathname === "/health" ) {
const result = await healthChecker.check ();
return Response .json ({
healthy : result.isOk (),
checks : result.isOk () ? result.value : result.error ,
});
}
PID File Management
XDG Paths import { getPidPath, getSocketPath, getDaemonDir } from "@outfitter/daemon" ;
const pidPath = getPidPath ("my-daemon" );
const socketPath = getSocketPath ("my-daemon" );
const daemonDir = getDaemonDir ("my-daemon" );
Checking if Running import { isDaemonAlive, getPidPath } from "@outfitter/daemon" ;
const pidPath = getPidPath ("my-daemon" );
if (await isDaemonAlive (pidPath)) {
console .log ("Daemon already running" );
process.exit (1 );
}
CLI Integration
Start Command import { command } from "@outfitter/cli/command" ;
import { runHandler } from "@outfitter/cli/envelope" ;
import { ConflictError , Result } from "@outfitter/contracts" ;
import { getPidPath, isDaemonAlive } from "@outfitter/daemon" ;
import { spawn } from "node:child_process" ;
const pidPath = getPidPath ("my-daemon" );
command ("start" )
.description ("Start the daemon" )
.option ("-f, --foreground" , "Run in foreground" )
.action (async ({ flags }) => {
const foreground = Boolean (flags["foreground" ]);
await runHandler ({
command : "start" ,
input : { foreground },
handler : async ({ foreground : fg }) => {
if (await isDaemonAlive (pidPath)) {
return Result .err (ConflictError .create ("Daemon is already running" ));
}
if (fg) {
const { runDaemon } = await import ("./daemon-main.js" );
await runDaemon ();
return Result .ok ({ status : "exited" , mode : "foreground" });
}
const daemon = spawn (
process.execPath ,
[import .meta .dir + "/daemon.js" ],
{
detached : true ,
stdio : "ignore" ,
}
);
daemon.unref ();
return Result .ok ({ status : "started" , pid : daemon.pid ?? null });
},
});
});
Stop Command (HTTP pattern) import { NotFoundError , NetworkError , Result } from "@outfitter/contracts" ;
import { getSocketPath, getPidPath, isDaemonAlive } from "@outfitter/daemon" ;
const socketPath = getSocketPath ("my-daemon" );
const pidPath = getPidPath ("my-daemon" );
command ("stop" )
.description ("Stop the daemon" )
.action (async () => {
await runHandler ({
command : "stop" ,
input : {},
handler : async () => {
if (!(await isDaemonAlive (pidPath))) {
return Result .err (NotFoundError .create ("daemon" , "process" ));
}
try {
const response = await fetch (`http://unix:${socketPath} :/shutdown` , {
method : "POST" ,
});
if (!response.ok ) {
return Result .err (NetworkError .create ("Failed to stop daemon" ));
}
return Result .ok ({ status : "stopped" });
} catch {
return Result .err (NetworkError .create ("Failed to reach daemon" ));
}
},
});
});
Related occupations SOC
Based on SOC occupation classification