#!/usr/bin/env tsx // Lightweight cron client for the scheduled sync system. Cron runs this on a // fixed heartbeat (e.g. `*/5 * * * *`); it just POSTs to the editor's // /api/scheduler/tick and the SERVER decides which channels are due and queues // them through the existing job machinery. Keeping the heartbeat dumb is the // whole point — see common/jobs/syncScheduler.ts and editor/app/scheduler/. // // Env: // SYNC_TICK_URL full URL of the tick endpoint. Default targets the editor's // real dev/start port (EDITOR_PORT in lib/ports.mjs, 3001): // http://127.0.0.1:3001/api/scheduler/tick // SYNC_TICK_TOKEN optional bearer token. When set, it must match the token in // the editor server's environment or the request is rejected. // // Exits non-zero on a network/HTTP error so cron surfaces failures (e.g. mails // the output); a normal tick prints a one-line summary for the cron log. import { runIfEntryPoint } from "./_cli"; import { PORT_BASES } from "../lib/ports.mjs"; // The primary's editor, never a worktree's: cron runs this from the primary. const DEFAULT_URL = `http://127.0.0.1:${PORT_BASES.EDITOR_PORT}/api/scheduler/tick`; // The exit code: 1 on an HTTP error (cron mails it), 0 otherwise. A network // failure throws. export async function main( opts: { url?: string; token?: string } = {}, ): Promise { const url = opts.url ?? process.env.SYNC_TICK_URL ?? DEFAULT_URL; const token = opts.token ?? process.env.SYNC_TICK_TOKEN; const headers: Record = { "content-type": "application/json" }; if (token) headers.authorization = `Bearer ${token}`; const res = await fetch(url, { method: "POST", headers }); const text = await res.text(); if (!res.ok) { console.error(`[sync-tick] ${res.status} ${res.statusText}: ${text}`); return 1; } try { const r = JSON.parse(text) as { queued?: string[]; skipped?: unknown[]; reason?: string; }; const queued = r.queued ?? []; const skipped = r.skipped ?? []; const reason = r.reason ? ` (${r.reason})` : ""; const detail = queued.length ? `: ${queued.join(", ")}` : ""; console.log( `[sync-tick] queued ${queued.length}, skipped ${skipped.length}${reason}${detail}`, ); } catch { console.log(`[sync-tick] ${text}`); } return 0; } // Same message as ever for a network failure, which throws out of fetch(). export async function tick(): Promise { try { return await main(); } catch (err) { console.error(`[sync-tick] request failed: ${(err as Error).message}`); return 1; } } runIfEntryPoint(import.meta.url, tick);