@@ -29,6 +29,13 @@ import { initMaterials, saveMaterial, searchMaterials, listMaterials, deleteMate
2929import { isFirstRunToday , markMorningDone , generateMorningBriefing } from './morning'
3030import { openEmailDraft , formatDraftPreview } from './email'
3131import { 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'
3239import { initFinance , addTransaction , getMonthSummary , getRecentTransactions , removeTransaction } from './finance'
3340import { initDecisions , logDecision , searchDecisions , listDecisions , formatDecisionList , formatDecisionDetail } from './decisions'
3441import { 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