Add brain-dump CLI for token-efficient task management

- list/add/edit/done/show/nudged/recurring commands
- --due flag with priority-based nudge intervals (now=1d, soon=3d, someday=7d)
- Tab-separated output for minimal token usage
- Consolidated news cron jobs (8→2) using multi-time cron expressions
This commit is contained in:
Agent 2026-02-07 13:59:59 +00:00
parent d4173ba0e0
commit 8b174976e6
3 changed files with 253 additions and 23 deletions

211
bin/brain-dump Executable file
View file

@ -0,0 +1,211 @@
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const DUMP_PATH = path.join(__dirname, '..', 'memory', 'brain-dump.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;
}
// --- Commands ---
function cmdList(args) {
const data = load();
let tasks = data.tasks || [];
// Filter by priority
const prioArg = getFlag(args, '--priority');
if (prioArg) {
const prios = prioArg.split(',').map(s => s.trim());
tasks = tasks.filter(t => prios.includes(t.priority));
}
// Filter by due
if (args.includes('--due')) {
tasks = tasks.filter(isDue);
}
// Sort: now > soon > someday
const prioOrder = { now: 0, soon: 1, someday: 2 };
tasks.sort((a, b) => (prioOrder[a.priority] ?? 3) - (prioOrder[b.priority] ?? 3));
// Limit
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 text = getFlag(args, '--text');
if (!text) { console.error('--text required'); process.exit(1); }
const priority = getFlag(args, '--priority') || 'soon';
const context = getFlag(args, '--context') || undefined;
const data = load();
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: brain-dump edit <id> [--text ...] [--priority ...] [--context ...]'); process.exit(1); }
const data = load();
const task = (data.tasks || []).find(t => t.id === id);
if (!task) { console.error(`Task ${id} not found.`); process.exit(1); }
const text = getFlag(args, '--text');
const priority = getFlag(args, '--priority');
const context = getFlag(args, '--context');
if (text) task.text = text;
if (priority) task.priority = priority;
if (context !== null && context !== undefined) task.context = context;
save(data);
console.log(`Updated: ${formatTask(task)}`);
}
function cmdDone(args) {
const id = args[0];
if (!id) { console.error('Usage: brain-dump done <id>'); process.exit(1); }
const data = load();
const idx = (data.tasks || []).findIndex(t => t.id === id);
if (idx === -1) { console.error(`Task ${id} not found.`); process.exit(1); }
const removed = data.tasks.splice(idx, 1)[0];
save(data);
console.log(`Done: ${removed.text}`);
}
function cmdShow(args) {
const id = args[0];
if (!id) { console.error('Usage: brain-dump show <id>'); process.exit(1); }
const data = load();
const task = (data.tasks || []).find(t => t.id === id);
if (!task) { console.error(`Task ${id} not found.`); process.exit(1); }
console.log(JSON.stringify(task, null, 2));
}
function cmdNudged(args) {
const ids = (args[0] || '').split(',').map(s => s.trim()).filter(Boolean);
if (ids.length === 0) { console.error('Usage: brain-dump nudged <id1>,<id2>,...'); 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(args) {
const data = load();
const when = getFlag(args, '--when');
let items = data.recurring || [];
if (when) {
items = items.filter(r => r.when === when);
}
if (items.length === 0) {
console.log('No recurring items.');
return;
}
for (const r of items) {
console.log(`${r.id}\t${r.frequency}\t${r.when || '-'}\t${r.text}`);
}
}
// --- 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(args); break;
default:
console.log(`Usage: brain-dump <command>
Commands:
list [--priority now,soon] [--due] [--limit N]
add --text "..." --priority soon [--context "..."]
edit <id> [--text "..."] [--priority ...] [--context "..."]
done <id>
show <id>
nudged <id1>,<id2>,...
recurring [--when evening]`);
}