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.
Aller à l'installation Skills Marketplace Découvrez et explorez les compétences IA créées par la communauté.
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Copier le promptAfficher les détails du prompt Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
npx skills add https://github.com/outfitter-dev/outfitter --skill outfitter-daemonLa commande reste sur une seule ligne. Faites défiler horizontalement pour la vérifier avant de la copier.
Vous préférez une copie locale ? Téléchargez les fichiers actuellement disponibles dans SkillsMP.
Télécharger Zip Téléchargement... Métiers associés SOC
Basé sur la classification professionnelle SOC
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" ));
}
},
});
});