Skip to content

Instantly share code, notes, and snippets.

@jlongster
Created March 7, 2026 18:58
Show Gist options
  • Select an option

  • Save jlongster/99c15e40c7978404bb97b5171df0e645 to your computer and use it in GitHub Desktop.

Select an option

Save jlongster/99c15e40c7978404bb97b5171df0e645 to your computer and use it in GitHub Desktop.
import {
chmod,
mkdir,
readdir,
readFile,
rm,
stat,
writeFile,
} from "node:fs/promises";
import { existsSync } from "node:fs";
import { basename, join, resolve } from "node:path";
export const QBOT_PREFIX = "qbot";
export const DATA_DIR = "/Users/james/kraken";
export const USER_JOBS_DIR = join(DATA_DIR, "jobs");
export const LAUNCH_AGENTS_DIR = "/Users/james/Library/LaunchAgents";
export const PROJECT_JOBS_DIR = resolve(import.meta.dir, "./system-jobs");
type Scope = "system" | "user";
type ParsedSchedule =
| { kind: "periodic"; seconds: number; runAtLoad?: boolean }
| { kind: "scheduled"; calendar: unknown; runAtLoad?: boolean }
| { kind: "on-change"; paths: string[]; runAtLoad?: boolean };
interface ParsedScheduleFile {
schedule?: ParsedSchedule;
program?: string;
disabled?: boolean;
}
export interface JobDefinition {
scope: Scope;
name: string;
label: string;
directory: string;
runScriptPath: string;
schedule: ParsedSchedule;
disabled: false;
}
export interface DisabledJobDefinition {
scope: Scope;
name: string;
label: string;
directory: string;
disabled: true;
schedule?: ParsedSchedule;
}
export type DiscoveredJobDefinition = JobDefinition | DisabledJobDefinition;
interface LaunchctlResult {
ok: boolean;
stdout: string;
stderr: string;
}
function sanitizeName(value: string): string {
return value
.toLowerCase()
.replace(/[^a-z0-9._-]+/g, "-")
.replace(/^-+|-+$/g, "");
}
function labelFor(scope: Scope, name: string): string {
return `${QBOT_PREFIX}.${scope}.${sanitizeName(name)}`;
}
function decodeBytes(bytes: Uint8Array<ArrayBufferLike>): string {
return new TextDecoder().decode(bytes);
}
function runLaunchctl(args: string[]): LaunchctlResult {
const proc = Bun.spawnSync(["launchctl", ...args]);
return {
ok: proc.exitCode === 0,
stdout: decodeBytes(proc.stdout),
stderr: decodeBytes(proc.stderr),
};
}
function currentUid(): string {
const uid = typeof process.getuid === "function" ? process.getuid() : 0;
return String(uid);
}
function parseIntegerSchedule(text: string): ParsedSchedule | null {
if (!/^\d+$/.test(text)) return null;
const seconds = Number.parseInt(text, 10);
if (!Number.isFinite(seconds) || seconds <= 0) {
throw new Error(`Invalid periodic interval in schedule: ${text}`);
}
return { kind: "periodic", seconds };
}
function parseScheduleText(raw: string): ParsedScheduleFile {
const text = raw.trim();
const integerSchedule = parseIntegerSchedule(text);
if (integerSchedule) return { schedule: integerSchedule };
let parsed: unknown;
try {
parsed = JSON.parse(text);
} catch {
throw new Error("Schedule file must be either an integer or JSON");
}
if (typeof parsed === "number") {
const seconds = Math.trunc(parsed);
if (seconds <= 0) throw new Error("Periodic seconds must be > 0");
return { schedule: { kind: "periodic", seconds } };
}
if (!parsed || typeof parsed !== "object") {
throw new Error("Schedule JSON must be an object");
}
if (Array.isArray(parsed)) {
return { schedule: { kind: "scheduled", calendar: parsed } };
}
const obj = parsed as Record<string, unknown>;
const runAtLoad = typeof obj.runAtLoad === "boolean" ? obj.runAtLoad : undefined;
const program = typeof obj.program === "string" ? obj.program : undefined;
const disabled = typeof obj.disabled === "boolean" ? obj.disabled : undefined;
const type = typeof obj.type === "string" ? obj.type : null;
if (type === "periodic") {
const seconds = Number(obj.seconds);
if (!Number.isFinite(seconds) || seconds <= 0) {
throw new Error("periodic schedule requires positive numeric 'seconds'");
}
return { schedule: { kind: "periodic", seconds: Math.trunc(seconds), runAtLoad }, program, disabled };
}
if (type === "scheduled") {
const calendar = obj.calendar;
if (!calendar) {
throw new Error("scheduled schedule requires 'calendar'");
}
return { schedule: { kind: "scheduled", calendar, runAtLoad }, program, disabled };
}
if (type === "on-change") {
const rawPaths = obj.paths;
const paths = Array.isArray(rawPaths)
? rawPaths.filter((p): p is string => typeof p === "string" && p.length > 0)
: typeof rawPaths === "string"
? [rawPaths]
: [];
if (paths.length === 0) {
throw new Error("on-change schedule requires 'paths' (string or string[])");
}
return { schedule: { kind: "on-change", paths, runAtLoad }, program, disabled };
}
if (disabled) {
return { program, disabled };
}
throw new Error("Unknown schedule format");
}
function normalizeCalendarKey(key: string): string {
const normalized = key.trim().toLowerCase();
const keyMap: Record<string, string> = {
minute: "Minute",
hour: "Hour",
day: "Day",
weekday: "Weekday",
month: "Month",
year: "Year",
second: "Second",
};
return keyMap[normalized] ?? key;
}
function normalizeCalendarForLaunchd(value: unknown): unknown {
if (Array.isArray(value)) {
return value.map(entry => normalizeCalendarForLaunchd(entry));
}
if (value && typeof value === "object") {
const out: Record<string, unknown> = {};
for (const [key, entry] of Object.entries(value as Record<string, unknown>)) {
out[normalizeCalendarKey(key)] = normalizeCalendarForLaunchd(entry);
}
return out;
}
return value;
}
function escapeXml(value: string): string {
return value
.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&apos;");
}
function plistValue(value: unknown, indent = " "): string {
if (typeof value === "string") return `${indent}<string>${escapeXml(value)}</string>`;
if (typeof value === "number") return `${indent}<integer>${Math.trunc(value)}</integer>`;
if (typeof value === "boolean") return `${indent}<${value ? "true" : "false"}/>`;
if (Array.isArray(value)) {
const entries = value.map(entry => plistValue(entry, `${indent} `)).join("\n");
return `${indent}<array>\n${entries}\n${indent}</array>`;
}
if (value && typeof value === "object") {
const entries = Object.entries(value as Record<string, unknown>)
.map(([key, entry]) => `${indent} <key>${escapeXml(key)}</key>\n${plistValue(entry, `${indent} `)}`)
.join("\n");
return `${indent}<dict>\n${entries}\n${indent}</dict>`;
}
return `${indent}<string></string>`;
}
function plistForJob(job: JobDefinition): string {
const logsDir = join(DATA_DIR, "logs", "launchd");
const base: Record<string, unknown> = {
Label: job.label,
ProgramArguments: [job.runScriptPath],
WorkingDirectory: job.directory,
StandardOutPath: join(logsDir, `${job.label}.out.log`),
StandardErrorPath: join(logsDir, `${job.label}.err.log`),
ProcessType: "Background",
KeepAlive: false,
RunAtLoad: job.schedule.runAtLoad ?? false,
EnvironmentVariables: {
QBOT_JOB_LABEL: job.label,
},
};
if (job.schedule.kind === "periodic") {
base.StartInterval = job.schedule.seconds;
}
if (job.schedule.kind === "scheduled") {
base.StartCalendarInterval = normalizeCalendarForLaunchd(job.schedule.calendar);
}
if (job.schedule.kind === "on-change") {
base.WatchPaths = job.schedule.paths;
}
const dict = plistValue(base, " ");
return [
'<?xml version="1.0" encoding="UTF-8"?>',
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
'<plist version="1.0">',
dict,
"</plist>",
"",
].join("\n");
}
function isEnabledJobDefinition(job: DiscoveredJobDefinition): job is JobDefinition {
return !job.disabled;
}
async function ensureJobDefinition(scope: Scope, jobDir: string): Promise<DiscoveredJobDefinition> {
const name = basename(jobDir);
const schedulePath = join(jobDir, "schedule");
const label = labelFor(scope, name);
const scheduleRaw = await readFile(schedulePath, "utf-8");
const parsedSchedule = parseScheduleText(scheduleRaw);
if (parsedSchedule.disabled) {
return {
scope,
name,
label,
directory: jobDir,
disabled: true,
schedule: parsedSchedule.schedule,
};
}
if (!parsedSchedule.schedule) {
throw new Error(`Job '${name}' schedule is missing a valid schedule definition`);
}
const programName = parsedSchedule.program ?? "run";
const runScriptPath = join(jobDir, programName);
await stat(runScriptPath);
await chmod(runScriptPath, 0o755);
return {
scope,
name,
label,
directory: jobDir,
runScriptPath,
schedule: parsedSchedule.schedule,
disabled: false,
};
}
export async function installJob(job: JobDefinition): Promise<boolean> {
await mkdir(LAUNCH_AGENTS_DIR, { recursive: true });
await mkdir(join(DATA_DIR, "logs", "launchd"), { recursive: true });
const plistPath = join(LAUNCH_AGENTS_DIR, `${job.label}.plist`);
const plist = plistForJob(job);
try {
const existingPlist = await readFile(plistPath, "utf-8");
if (existingPlist === plist) {
return false;
}
} catch {
// No existing plist yet, continue with install.
}
await writeFile(plistPath, plist, "utf-8");
await chmod(plistPath, 0o644);
const uid = currentUid();
runLaunchctl(["bootout", `gui/${uid}`, plistPath]);
const bootstrap = runLaunchctl(["bootstrap", `gui/${uid}`, plistPath]);
if (!bootstrap.ok) {
throw new Error(`Failed to bootstrap ${job.label}: ${bootstrap.stderr || bootstrap.stdout}`);
}
runLaunchctl(["enable", `gui/${uid}/${job.label}`]);
return true;
}
export async function uninstallLabel(label: string): Promise<void> {
const uid = currentUid();
const plistPath = join(LAUNCH_AGENTS_DIR, `${label}.plist`);
runLaunchctl(["bootout", `gui/${uid}`, plistPath]);
runLaunchctl(["bootout", `gui/${uid}/${label}`]);
await rm(plistPath, { force: true });
}
async function discoverFromRoot(root: string, scope: Scope): Promise<DiscoveredJobDefinition[]> {
if (!existsSync(root)) return [];
const entries = await readdir(root, { withFileTypes: true });
const jobs: DiscoveredJobDefinition[] = [];
for (const entry of entries) {
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
const dir = join(root, entry.name);
const schedulePath = join(dir, "schedule");
if (!existsSync(schedulePath)) continue;
jobs.push(await ensureJobDefinition(scope, dir));
}
return jobs;
}
export async function discoverAllJobDefinitions(): Promise<DiscoveredJobDefinition[]> {
const [systemJobs, userJobs] = await Promise.all([
discoverFromRoot(PROJECT_JOBS_DIR, "system"),
discoverFromRoot(USER_JOBS_DIR, "user"),
]);
return [...systemJobs, ...userJobs];
}
async function labelsFromLaunchAgentsDir(): Promise<string[]> {
if (!existsSync(LAUNCH_AGENTS_DIR)) return [];
const entries = await readdir(LAUNCH_AGENTS_DIR, { withFileTypes: true });
return entries
.filter(entry => entry.isFile() && entry.name.startsWith(`${QBOT_PREFIX}.`) && entry.name.endsWith(".plist"))
.map(entry => entry.name.slice(0, -6));
}
export async function listNativeQbotLabels(): Promise<string[]> {
return (await labelsFromLaunchAgentsDir()).sort();
}
export interface SyncReport {
synced: string[];
removed: string[];
unchanged: string[];
desired: string[];
existing: string[];
}
export async function syncAllJobs(): Promise<SyncReport> {
const definitions = await discoverAllJobDefinitions();
const enabledDefinitions = definitions.filter(isEnabledJobDefinition);
const desiredLabels = enabledDefinitions.map(job => job.label);
const existingLabels = await listNativeQbotLabels();
const synced: string[] = [];
const unchanged: string[] = [];
for (const job of enabledDefinitions) {
const changed = await installJob(job);
if (changed) {
synced.push(job.label);
} else {
unchanged.push(job.label);
}
}
const staleLabels = existingLabels.filter(label => !desiredLabels.includes(label));
for (const label of staleLabels) {
await uninstallLabel(label);
}
return {
synced,
removed: staleLabels,
unchanged,
desired: desiredLabels,
existing: existingLabels,
};
}
export async function loadDefinitionFromDirectory(dir: string, scope: Scope): Promise<DiscoveredJobDefinition> {
return ensureJobDefinition(scope, dir);
}
import { syncAllJobs } from "./manager";
async function main(): Promise<void> {
const report = await syncAllJobs();
console.log(`[jobs] Synced ${report.synced.length} jobs`);
if (report.synced.length > 0) {
console.log(`[jobs] Updated jobs: ${report.synced.join(", ")}`);
}
if (report.removed.length > 0) {
console.log(`[jobs] Removed stale jobs: ${report.removed.join(", ")}`);
}
}
main().catch((error) => {
console.error("[jobs] Sync failed:", error);
process.exit(1);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment