Skip to content

Commit e121ecf

Browse files
committed
Scheduler (vibe-kanban b0c872b9)
A partir de agora, considere a funcionalidade **/agendar** (ou **/schedule**) como uma diretiva prioritária para manipulação do Agendador de Tarefas do Windows via PowerShell. Quando eu utilizar este comando, você deve seguir este protocolo: 1. **Sintaxe Esperada:** `/agendar "mensagem" "HH:mm"` ou `/agendar "comando/script" "HH:mm"`. 2. **Ação:** Se o alvo for uma mensagem, gere um script PowerShell temporário que execute a `ToastNotification` (visto anteriormente) e agende-o. Se for um comando, agende-o diretamente. 3. **Execução Técnica:** Utilize o comando `schtasks` com os seguintes parâmetros padrão: - `/create` para novas tarefas. - `/tn` com o prefixo "Smolerclaw\_" seguido de um identificador único. - `/sc once` (para horários específicos hoje) ou `/sc daily` conforme o contexto. - `/st` para o horário. - `/f` para forçar a criação/sobrescrita. **Comando de Ativação:** "Sempre que eu usar `/agendar`, execute silenciosamente o comando `schtasks` via PowerShell para garantir persistência no Windows. Confirme apenas com: 'Tarefa [Nome] agendada para [Horário]'." **Teste agora:** `/agendar "Reunião de Alinhamento Aldeia Viva" "14:00"`
1 parent d48f753 commit e121ecf

File tree

5 files changed

+1252
-0
lines changed

5 files changed

+1252
-0
lines changed

.test-context-1774970318072/Cargo.toml

Whitespace-only changes.

src/index.ts

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ import { initMaterials, saveMaterial, searchMaterials, listMaterials, deleteMate
2929
import { isFirstRunToday, markMorningDone, generateMorningBriefing } from './morning'
3030
import { openEmailDraft, formatDraftPreview } from './email'
3131
import { initPomodoro, startPomodoro, stopPomodoro, pomodoroStatus, stopPomodoroTimer } from './pomodoro'
32+
import {
33+
initScheduler, stopScheduler, scheduleJob, removeJob, enableJob, disableJob,
34+
listJobs, getJob, runJobNow, clearAllJobs,
35+
formatJobList, formatJobDetail,
36+
parseScheduleTime, parseScheduleDate, parseWeekDay,
37+
type ScheduleType, type JobAction,
38+
} from './scheduler'
3239
import { initFinance, addTransaction, getMonthSummary, getRecentTransactions, removeTransaction } from './finance'
3340
import { initDecisions, logDecision, searchDecisions, listDecisions, formatDecisionList, formatDecisionDetail } from './decisions'
3441
import { initWorkflows, runWorkflow, listWorkflows, getWorkflow, createWorkflow, deleteWorkflow, updateWorkflow, formatWorkflowList, formatWorkflowDetail, type WorkflowStep } from './workflows'
@@ -226,6 +233,7 @@ async function runInteractive(
226233
tui.showSystem(`\n*** Meta-Insight: ${insight.title} ***\n${insight.recommendation}\n`)
227234
})
228235
initMonitor((msg) => tui.showSystem(`\n*** ${msg} ***\n`))
236+
initScheduler(config.dataDir, (msg) => tui.showSystem(`\n*** ${msg} ***\n`))
229237
initTasks(config.dataDir, (task: Task) => {
230238
tui.showSystem(`\n*** LEMBRETE: ${task.title} ***\n`)
231239
})
@@ -1337,6 +1345,156 @@ async function runInteractive(
13371345
break
13381346
}
13391347

1348+
// ── Scheduler commands ──────────────────────────────────
1349+
1350+
case 'agendar':
1351+
case 'schedule': {
1352+
// /agendar "mensagem" "14:00" [data] [tipo]
1353+
// /agendar "comando" "14:00" daily command
1354+
const sub = args[0]?.toLowerCase()
1355+
1356+
if (!sub || sub === 'list' || sub === 'listar') {
1357+
const jobs = listJobs(args[1] === 'all' || args[1] === 'todos')
1358+
tui.showSystem(formatJobList(jobs))
1359+
break
1360+
}
1361+
1362+
if (sub === 'remove' || sub === 'remover' || sub === 'delete') {
1363+
if (!args[1]) {
1364+
tui.showSystem('Uso: /agendar remove <id ou nome>')
1365+
break
1366+
}
1367+
const removed = await removeJob(args[1])
1368+
tui.showSystem(removed ? 'Agendamento removido.' : 'Agendamento nao encontrado.')
1369+
break
1370+
}
1371+
1372+
if (sub === 'enable' || sub === 'ativar') {
1373+
if (!args[1]) {
1374+
tui.showSystem('Uso: /agendar ativar <id ou nome>')
1375+
break
1376+
}
1377+
const job = await enableJob(args[1])
1378+
tui.showSystem(job ? `Agendamento "${job.name}" ativado.` : 'Agendamento nao encontrado.')
1379+
break
1380+
}
1381+
1382+
if (sub === 'disable' || sub === 'desativar') {
1383+
if (!args[1]) {
1384+
tui.showSystem('Uso: /agendar desativar <id ou nome>')
1385+
break
1386+
}
1387+
const job = await disableJob(args[1])
1388+
tui.showSystem(job ? `Agendamento "${job.name}" desativado.` : 'Agendamento nao encontrado.')
1389+
break
1390+
}
1391+
1392+
if (sub === 'run' || sub === 'executar') {
1393+
if (!args[1]) {
1394+
tui.showSystem('Uso: /agendar executar <id ou nome>')
1395+
break
1396+
}
1397+
tui.showSystem(await runJobNow(args[1]))
1398+
break
1399+
}
1400+
1401+
if (sub === 'clear' || sub === 'limpar') {
1402+
tui.showSystem(await clearAllJobs())
1403+
break
1404+
}
1405+
1406+
if (sub === 'detail' || sub === 'detalhe' || sub === 'info') {
1407+
if (!args[1]) {
1408+
tui.showSystem('Uso: /agendar info <id ou nome>')
1409+
break
1410+
}
1411+
const job = getJob(args[1])
1412+
tui.showSystem(job ? formatJobDetail(job) : 'Agendamento nao encontrado.')
1413+
break
1414+
}
1415+
1416+
// Create new schedule: /agendar "mensagem" "14:00" [once|daily|weekly] [data/dia]
1417+
// Parse quoted strings
1418+
const fullText = args.join(' ')
1419+
const quotedParts = fullText.match(/"([^"]+)"/g)
1420+
1421+
if (!quotedParts || quotedParts.length < 2) {
1422+
tui.showSystem(
1423+
'Uso: /agendar "<mensagem>" "<horario>" [once|daily|weekly] [data/dia]\n' +
1424+
'Exemplos:\n' +
1425+
' /agendar "Reuniao" "14:00"\n' +
1426+
' /agendar "Standup" "09:00" daily\n' +
1427+
' /agendar "Review" "15:00" weekly sexta\n' +
1428+
' /agendar "Dentista" "10:00" once 15/04/2026',
1429+
)
1430+
break
1431+
}
1432+
1433+
const message = quotedParts[0].slice(1, -1) // Remove quotes
1434+
const timeStr = quotedParts[1].slice(1, -1)
1435+
const parsedTime = parseScheduleTime(timeStr)
1436+
1437+
if (!parsedTime) {
1438+
tui.showSystem(`Horario invalido: "${timeStr}". Use formato HH:MM ou HHh.`)
1439+
break
1440+
}
1441+
1442+
// Parse remaining args after quoted strings
1443+
const afterQuotes = fullText.replace(/"[^"]+"/g, '').trim().split(/\s+/).filter(Boolean)
1444+
let scheduleType: ScheduleType = 'once'
1445+
let dateOrDay: string | undefined
1446+
1447+
for (const arg of afterQuotes) {
1448+
const lower = arg.toLowerCase()
1449+
if (lower === 'daily' || lower === 'diario') {
1450+
scheduleType = 'daily'
1451+
} else if (lower === 'weekly' || lower === 'semanal') {
1452+
scheduleType = 'weekly'
1453+
} else if (lower === 'once' || lower === 'uma-vez') {
1454+
scheduleType = 'once'
1455+
} else if (scheduleType === 'weekly') {
1456+
const day = parseWeekDay(arg)
1457+
if (day) dateOrDay = day
1458+
} else if (scheduleType === 'once') {
1459+
const date = parseScheduleDate(arg)
1460+
if (date) dateOrDay = date
1461+
}
1462+
}
1463+
1464+
// Default date for 'once' if not specified
1465+
if (scheduleType === 'once' && !dateOrDay) {
1466+
const now = new Date()
1467+
const [h, m] = parsedTime.split(':').map(Number)
1468+
const scheduleTime = new Date(now)
1469+
scheduleTime.setHours(h, m, 0, 0)
1470+
1471+
// If time already passed today, schedule for tomorrow
1472+
if (scheduleTime <= now) {
1473+
scheduleTime.setDate(scheduleTime.getDate() + 1)
1474+
}
1475+
dateOrDay = [
1476+
String(scheduleTime.getMonth() + 1).padStart(2, '0'),
1477+
String(scheduleTime.getDate()).padStart(2, '0'),
1478+
String(scheduleTime.getFullYear()),
1479+
].join('/')
1480+
}
1481+
1482+
try {
1483+
const job = await scheduleJob(message, scheduleType, parsedTime, 'toast', message, dateOrDay)
1484+
tui.showSystem(`Tarefa "${job.name}" agendada para ${formatJobDetail(job).split('\n').slice(2, 5).join(', ').replace(/\n/g, '')}`)
1485+
} catch (err) {
1486+
tui.showError(`Erro ao agendar: ${err instanceof Error ? err.message : String(err)}`)
1487+
}
1488+
break
1489+
}
1490+
1491+
case 'agendamentos':
1492+
case 'schedules': {
1493+
const jobs = listJobs(args[0] === 'all' || args[0] === 'todos')
1494+
tui.showSystem(formatJobList(jobs))
1495+
break
1496+
}
1497+
13401498
// ── Finance commands ────────────────────────────────────
13411499

13421500
case 'entrada':
@@ -1997,6 +2155,7 @@ async function runInteractive(
19972155
stopTasks()
19982156
stopPomodoroTimer()
19992157
stopAllMonitors()
2158+
stopScheduler()
20002159
stopAutoBackup()
20012160

20022161
// Run self-reflection asynchronously before exit (non-blocking)

0 commit comments

Comments
 (0)