Your First Job
Init the project, create your first job, see it register with launchd.
You're going to get a job running on launchd before you understand how any of it works. Empty directory to working scheduler in a few minutes.
Init the Project
mkdir launchd-ai-scheduler && cd launchd-ai-scheduler
bun init -yInstall Dependencies
bun add -d @types/bun typescriptAdd to your tsconfig.json:
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"types": ["@types/bun"]
}
}Create Your First Job
A job is a directory with two files — a schedule and a run script:
mkdir -p system-jobs/hello-worldThe schedule — run every 60 seconds:
echo '60' > system-jobs/hello-world/scheduleThe run script — log a timestamp:
cat > system-jobs/hello-world/run << 'EOF'
#!/bin/bash
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Hello from launchd! Job: ${JOB_LABEL:-unknown}"
EOF
chmod 755 system-jobs/hello-world/runThat's a complete job definition. A directory, a schedule file, an executable script.
Build the Manager
Create src/manager.ts — this is the core. It discovers job directories, generates launchd plists, and registers them. Your companion can help you build this step by step, or you can write it yourself. The key functions:
Ask your companion: "teach me how to build the manager"
Build the Sync Entrypoint
Create src/sync.ts:
import { syncAllJobs } from "./manager";
const report = await syncAllJobs();
console.log(`[jobs] Synced ${report.synced.length} jobs`);
if (report.synced.length > 0) console.log(`[jobs] Updated: ${report.synced.join(", ")}`);
if (report.removed.length > 0) console.log(`[jobs] Removed: ${report.removed.join(", ")}`);Add to package.json:
{
"scripts": {
"sync": "bun run src/sync.ts"
}
}Run It
bun run sync[jobs] Synced 1 jobs
[jobs] Updated: aijs.system.hello-worldVerify
launchctl list | grep aijs- 0 aijs.system.hello-worldYour job is registered with launchd. It will run every 60 seconds. It survives reboots. The OS manages it.
Kick It
Force-run now instead of waiting:
launchctl kickstart gui/$(id -u)/aijs.system.hello-worldCheck the output:
cat workdir/logs/hello-world/stdout.log[2026-03-13 17:57:42] Hello from launchd! Job: aijs.system.hello-worldIt works. You built a job scheduler. The rest of this course is understanding how each piece works and making it do real AI work.