#!/usr/bin/env node const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); const DUMP_PATH = path.join(__dirname, '..', 'memory', 'tasks.json'); const NUDGE_INTERVALS = { now: 24 * 60 * 60 * 1000, // 1 day soon: 3 * 24 * 60 * 60 * 1000, // 3 days someday: 7 * 24 * 60 * 60 * 1000 // 7 days }; function load() { try { return JSON.parse(fs.readFileSync(DUMP_PATH, 'utf8')); } catch { return { description: "Task tracking", recurring: [], tasks: [] }; } } function save(data) { fs.writeFileSync(DUMP_PATH, JSON.stringify(data, null, 2) + '\n'); } function shortId() { return crypto.randomBytes(4).toString('hex'); } function isDue(task) { const interval = NUDGE_INTERVALS[task.priority] || NUDGE_INTERVALS.someday; const last = task.lastNudged ? new Date(task.lastNudged).getTime() : 0; return Date.now() - last >= interval; } function formatTask(t) { let line = `${t.id}\t${t.priority}\t${t.text}`; if (t.context) line += `\t${t.context}`; if (t.lastNudged) line += `\tnudged:${t.lastNudged}`; return line; } function formatRecurring(r) { let line = `${r.id}\t${r.frequency}\t${r.when || '-'}\t${r.text}`; if (r.context) line += `\t${r.context}`; return line; } // --- Commands --- function cmdList(args) { const data = load(); let tasks = data.tasks || []; const prioArg = getFlag(args, '--priority'); if (prioArg) { const prios = prioArg.split(',').map(s => s.trim()); tasks = tasks.filter(t => prios.includes(t.priority)); } if (args.includes('--due')) { tasks = tasks.filter(isDue); } const prioOrder = { now: 0, soon: 1, someday: 2 }; tasks.sort((a, b) => (prioOrder[a.priority] ?? 3) - (prioOrder[b.priority] ?? 3)); const limitArg = getFlag(args, '--limit'); if (limitArg) { tasks = tasks.slice(0, parseInt(limitArg, 10)); } if (tasks.length === 0) { console.log('No tasks found.'); return; } for (const t of tasks) { console.log(formatTask(t)); } } function cmdAdd(args) { const isRecurring = args.includes('--recurring'); const text = getFlag(args, '--text'); if (!text) { console.error('--text required'); process.exit(1); } const context = getFlag(args, '--context') || undefined; const data = load(); if (isRecurring) { const frequency = getFlag(args, '--frequency') || 'daily'; const when = getFlag(args, '--when') || undefined; const item = { id: shortId(), text, frequency }; if (when) item.when = when; if (context) item.context = context; data.recurring = data.recurring || []; data.recurring.push(item); save(data); console.log(`Added recurring: ${formatRecurring(item)}`); } else { const priority = getFlag(args, '--priority') || 'soon'; const task = { id: shortId(), added: new Date().toISOString().slice(0, 10), text, priority }; if (context) task.context = context; data.tasks = data.tasks || []; data.tasks.push(task); save(data); console.log(`Added: ${task.id}\t${priority}\t${text}`); } } function cmdEdit(args) { const id = args[0]; if (!id) { console.error('Usage: tasks edit [--text ...] [--priority ...] [--context ...] [--frequency ...] [--when ...]'); process.exit(1); } const data = load(); // Check tasks first, then recurring let item = (data.tasks || []).find(t => t.id === id); let isRecurring = false; if (!item) { item = (data.recurring || []).find(r => r.id === id); isRecurring = true; } if (!item) { console.error(`Item ${id} not found.`); process.exit(1); } const text = getFlag(args, '--text'); const context = getFlag(args, '--context'); if (text) item.text = text; if (context !== null && context !== undefined) item.context = context; if (isRecurring) { const frequency = getFlag(args, '--frequency'); const when = getFlag(args, '--when'); if (frequency) item.frequency = frequency; if (when) item.when = when; save(data); console.log(`Updated recurring: ${formatRecurring(item)}`); } else { const priority = getFlag(args, '--priority'); if (priority) item.priority = priority; save(data); console.log(`Updated: ${formatTask(item)}`); } } function cmdDone(args) { const id = args[0]; if (!id) { console.error('Usage: tasks done '); process.exit(1); } const data = load(); // Check tasks let idx = (data.tasks || []).findIndex(t => t.id === id); if (idx !== -1) { const removed = data.tasks.splice(idx, 1)[0]; save(data); console.log(`Done: ${removed.text}`); return; } // Check recurring idx = (data.recurring || []).findIndex(r => r.id === id); if (idx !== -1) { const removed = data.recurring.splice(idx, 1)[0]; save(data); console.log(`Removed recurring: ${removed.text}`); return; } console.error(`Item ${id} not found.`); process.exit(1); } function cmdShow(args) { const id = args[0]; if (!id) { console.error('Usage: tasks show '); process.exit(1); } const data = load(); const item = (data.tasks || []).find(t => t.id === id) || (data.recurring || []).find(r => r.id === id); if (!item) { console.error(`Item ${id} not found.`); process.exit(1); } console.log(JSON.stringify(item, null, 2)); } function cmdNudged(args) { const ids = (args[0] || '').split(',').map(s => s.trim()).filter(Boolean); if (ids.length === 0) { console.error('Usage: tasks nudged ,,...'); process.exit(1); } const data = load(); const now = new Date().toISOString(); let count = 0; for (const id of ids) { const task = (data.tasks || []).find(t => t.id === id); if (task) { task.lastNudged = now; count++; } } save(data); console.log(`Marked ${count} task(s) as nudged.`); } function cmdRecurring() { const data = load(); const items = data.recurring || []; if (items.length === 0) { console.log('No recurring items.'); return; } for (const r of items) { console.log(formatRecurring(r)); } } // --- Helpers --- function getFlag(args, flag) { const idx = args.indexOf(flag); if (idx === -1) return null; return args[idx + 1] || null; } // --- Main --- const [cmd, ...args] = process.argv.slice(2); switch (cmd) { case 'list': cmdList(args); break; case 'add': cmdAdd(args); break; case 'edit': cmdEdit(args); break; case 'done': cmdDone(args); break; case 'show': cmdShow(args); break; case 'nudged': cmdNudged(args); break; case 'recurring': cmdRecurring(); break; default: console.log(`Usage: tasks Commands: list [--priority now,soon] [--due] [--limit N] add --text "..." --priority soon [--context "..."] add --recurring --text "..." --frequency daily [--when evening] [--context "..."] edit [--text "..."] [--priority|--frequency|--when|--context "..."] done show nudged ,,... recurring`); }