build_cron
Convert natural language schedule descriptions into cron expressions for automated task scheduling. Provides expression, human-readable confirmation, and next run times.
Instructions
Convert natural language schedule descriptions into cron expressions. Examples: 'every weekday at 9am', 'first Monday of each month at noon', 'every 5 minutes'. Returns the expression, human-readable confirmation, and next 5 run times.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| description | Yes | Natural language schedule description | |
| timezone | No | Timezone for context | UTC |
Implementation Reference
- src/tools/cron-builder.ts:106-229 (handler)The buildCron function takes a natural language description and parses it into a cron expression and associated metadata.
function buildCron(desc: string): CronResult { const lower = desc.toLowerCase().trim(); const warnings: string[] = []; let minute = "*"; let hour = "*"; let dayOfMonth = "*"; let month = "*"; let dayOfWeek = "*"; // ---- Every N minutes/hours ---- const everyMinMatch = lower.match(/every\s+(\d+)\s*min/); if (everyMinMatch) { const n = parseInt(everyMinMatch[1], 10); minute = n === 1 ? "*" : `*/${n}`; const expr = `${minute} * * * *`; return formatResult(expr, `Every ${n} minute(s)`, desc, warnings); } const everyHourMatch = lower.match(/every\s+(\d+)\s*hour/); if (everyHourMatch) { const n = parseInt(everyHourMatch[1], 10); hour = n === 1 ? "*" : `*/${n}`; const expr = `0 ${hour} * * *`; return formatResult(expr, `Every ${n} hour(s), on the hour`, desc, warnings); } if (lower.match(/every\s*(minute|min$)/)) { return formatResult("* * * * *", "Every minute", desc, warnings); } if (lower.match(/every\s*hour|hourly/)) { return formatResult("0 * * * *", "Every hour, on the hour", desc, warnings); } // ---- Daily / every day ---- if (lower.match(/every\s*day|daily/) && !lower.includes("weekday")) { const time = parseTime(lower); const expr = `${time.minute} ${time.hour} * * *`; return formatResult(expr, `Every day at ${formatTimeHuman(time.hour, time.minute)}`, desc, warnings); } // ---- Weekly ---- if (lower.includes("weekly") || lower.includes("every week")) { const time = parseTime(lower); const days = parseDays(lower); const dow = days.length > 0 ? days.join(",") : "1"; // default Monday const expr = `${time.minute} ${time.hour} * * ${dow}`; return formatResult(expr, `Weekly on ${daysToHuman(days.length > 0 ? days : [1])} at ${formatTimeHuman(time.hour, time.minute)}`, desc, warnings); } // ---- Monthly ---- const monthlyMatch = lower.match(/(?:every month|monthly)/); if (monthlyMatch) { const time = parseTime(lower); const domMatch = lower.match(/(\d{1,2})(?:st|nd|rd|th)/); const dom = domMatch ? domMatch[1] : "1"; const expr = `${time.minute} ${time.hour} ${dom} * *`; return formatResult(expr, `Monthly on the ${ordinal(parseInt(dom, 10))} at ${formatTimeHuman(time.hour, time.minute)}`, desc, warnings); } // ---- "first/last Monday of each month" pattern ---- const nthDayMatch = lower.match(/(first|second|third|fourth|last)\s+(sunday|monday|tuesday|wednesday|thursday|friday|saturday|sun|mon|tue|wed|thu|fri|sat)/); if (nthDayMatch) { const posMap: Record<string, string> = { first: "1", second: "2", third: "3", fourth: "4", last: "L" }; const pos = posMap[nthDayMatch[1]] || "1"; const dayName = nthDayMatch[2]; const dayNum = DAYS[dayName] ?? 1; const time = parseTime(lower); // Standard cron doesn't support nth weekday; use the closest approximation if (pos === "L") { warnings.push("Standard cron does not support 'last weekday of month'. This approximates using day-of-week only. Consider using a cron library that supports extended syntax like '0L' for last occurrence."); const expr = `${time.minute} ${time.hour} * * ${dayNum}`; return formatResult(expr, `Every ${dayName} at ${formatTimeHuman(time.hour, time.minute)} (approximate — see warnings)`, desc, warnings); } // Approximate: first Monday ≈ days 1-7 on that weekday const dayRanges: Record<string, string> = { "1": "1-7", "2": "8-14", "3": "15-21", "4": "22-28" }; const domRange = dayRanges[pos] || "1-7"; const expr = `${time.minute} ${time.hour} ${domRange} * ${dayNum}`; return formatResult(expr, `${nthDayMatch[1]} ${dayName} of each month at ${formatTimeHuman(time.hour, time.minute)}`, desc, warnings); } // ---- Specific days of the week ---- const days = parseDays(lower); if (days.length > 0) { const time = parseTime(lower); const dow = days.join(","); const expr = `${time.minute} ${time.hour} * * ${dow}`; return formatResult(expr, `${daysToHuman(days)} at ${formatTimeHuman(time.hour, time.minute)}`, desc, warnings); } // ---- Specific months ---- for (const [name, num] of Object.entries(MONTH_NAMES)) { if (lower.includes(name)) { const time = parseTime(lower); const domMatch = lower.match(/(\d{1,2})(?:st|nd|rd|th)?/); const dom = domMatch ? domMatch[1] : "1"; const expr = `${time.minute} ${time.hour} ${dom} ${num} *`; return formatResult(expr, `${name.charAt(0).toUpperCase() + name.slice(1)} ${ordinal(parseInt(dom, 10))} at ${formatTimeHuman(time.hour, time.minute)}`, desc, warnings); } } // ---- Yearly / annually ---- if (lower.includes("yearly") || lower.includes("annually") || lower.includes("every year")) { const time = parseTime(lower); const expr = `${time.minute} ${time.hour} 1 1 *`; return formatResult(expr, `Yearly on January 1st at ${formatTimeHuman(time.hour, time.minute)}`, desc, warnings); } // ---- Fallback: try to find just a time ---- const timeFallback = lower.match(/\d{1,2}(:\d{2})?\s*(am|pm)/); if (timeFallback) { const time = parseTime(lower); const expr = `${time.minute} ${time.hour} * * *`; warnings.push("Could not determine a specific schedule pattern — defaulting to daily at the specified time."); return formatResult(expr, `Daily at ${formatTimeHuman(time.hour, time.minute)}`, desc, warnings); } // ---- Couldn't parse ---- warnings.push("Could not parse the schedule description. Returning a midnight daily default. Please try being more specific, e.g., 'every weekday at 9am' or 'every 5 minutes'."); return formatResult("0 0 * * *", "Daily at midnight (default — could not parse input)", desc, warnings); } - src/tools/cron-builder.ts:330-335 (handler)The tool handler which executes the buildCron function.
// ----- Handler ----- async function handler(input: Input): Promise<CronResult> { const result = buildCron(input.description); result.timezone = input.timezone; return result; }