(cronExpr, claudeCommand)
| 69 | // --- Windows: schtasks ------------------------------------------------------- |
| 70 | |
| 71 | function winAdd(cronExpr, claudeCommand) { |
| 72 | const parts = cronExpr.split(/\s+/); |
| 73 | const [minute, hour] = parts; |
| 74 | const id = newId(); |
| 75 | const invocation = claudeInvocation(claudeCommand); |
| 76 | // schtasks supports basic minute/hourly/daily triggers. For arbitrary cron, |
| 77 | // we map common cases. Complex expressions are approximated to MINUTE. |
| 78 | let schtasksArgs; |
| 79 | if (parts.join(' ') === '* * * * *') { |
| 80 | schtasksArgs = ['/Create', '/SC', 'MINUTE', '/MO', '1', '/TN', id, '/TR', `cmd /c cd /d "${ROOT}" && ${invocation}`, '/F']; |
| 81 | } else if (/^\*\/\d+$/.test(minute) && hour === '*') { |
| 82 | const mo = minute.slice(2); |
| 83 | schtasksArgs = ['/Create', '/SC', 'MINUTE', '/MO', mo, '/TN', id, '/TR', `cmd /c cd /d "${ROOT}" && ${invocation}`, '/F']; |
| 84 | } else if (minute === '0' && /^\*\/\d+$/.test(hour)) { |
| 85 | schtasksArgs = ['/Create', '/SC', 'HOURLY', '/MO', hour.slice(2), '/TN', id, '/TR', `cmd /c cd /d "${ROOT}" && ${invocation}`, '/F']; |
| 86 | } else if (minute === '0' && /^\d+$/.test(hour)) { |
| 87 | schtasksArgs = ['/Create', '/SC', 'DAILY', '/ST', `${hour.padStart(2, '0')}:00`, '/TN', id, '/TR', `cmd /c cd /d "${ROOT}" && ${invocation}`, '/F']; |
| 88 | } else { |
| 89 | throw new Error(`Windows Task Scheduler mapping not supported for "${cronExpr}". Use: every {N}m, every {N}h, or daily at {H}.`); |
| 90 | } |
| 91 | execFileSync('schtasks', schtasksArgs, { stdio: 'inherit' }); |
| 92 | console.log(`Scheduled. ID: ${id}`); |
| 93 | console.log(`Remove with: node scripts/local-schedule.js remove ${id}`); |
| 94 | } |
| 95 | |
| 96 | function winList() { |
| 97 | const out = spawnSync('schtasks', ['/Query', '/FO', 'CSV', '/NH'], { encoding: 'utf8' }); |
no test coverage detected