From 8b174976e6381396a6fa8614a676563b73ad34ed Mon Sep 17 00:00:00 2001 From: Agent Date: Sat, 7 Feb 2026 13:59:59 +0000 Subject: [PATCH] Add brain-dump CLI for token-efficient task management MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- TOOLS.md | 33 ++++++- bin/brain-dump | 211 +++++++++++++++++++++++++++++++++++++++++ memory/brain-dump.json | 32 +++---- 3 files changed, 253 insertions(+), 23 deletions(-) create mode 100755 bin/brain-dump diff --git a/TOOLS.md b/TOOLS.md index 1d9961a..5d0d3a8 100644 --- a/TOOLS.md +++ b/TOOLS.md @@ -8,10 +8,10 @@ When creating tasks, jobs, sub-agents, or hooks, choose the appropriate model: | Model | Use For | |-------|---------| -| **Opus 4.5** | Everything (default) - best quality, same limits as Sonnet in Max plan | +| **Opus 4.6** | Everything (default) - best quality, same limits as Sonnet in Max plan | | **Haiku 4.5** | Simple bulk tasks only - news summaries, quick lookups, translations | -**Default (main session):** Opus 4.5 +**Default (main session):** Opus 4.6 **Sonnet 4.5:** Not used (Opus has same limits but better quality) ## Home Assistant Integration @@ -135,9 +135,36 @@ ainews reset # Clear seen history --- +## Brain Dump CLI + +Helper script: `~/clawd/bin/brain-dump` + +```bash +brain-dump list [--priority now,soon] [--due] [--limit N] +brain-dump add --text "..." --priority soon [--context "..."] +brain-dump edit [--text "..."] [--priority ...] [--context "..."] +brain-dump done +brain-dump show +brain-dump nudged ,,... +brain-dump recurring [--when evening] +``` + +- Data: `memory/brain-dump.json` +- `--due` filters by nudge interval: now=1d, soon=3d, someday=7d +- `nudged` marks tasks as just-nudged (resets due timer) +- `recurring` lists recurring reminders (nose shower etc.) +- Output is tab-separated for minimal tokens + +**Heartbeat workflow:** +1. `brain-dump list --due --limit 2` → get tasks needing a nudge +2. Mention them conversationally +3. `brain-dump nudged ,` → mark as nudged + +--- + ## Der Standard RSS Summaries -- **Schedule:** 4× daily: 10:00, 14:00, 18:00, 22:00 (Vienna time) +- **Schedule:** 4× daily: 10:00, 14:00, 18:00, 22:00 Vienna (1 cron job with `0 10,14,18,22 * * *`) - **Model:** Haiku 4.5 - **Workflow:** `derstandard items` → pick relevant → `derstandard articles` → write briefing - **Focus:** International politics, technology, science, economics diff --git a/bin/brain-dump b/bin/brain-dump new file mode 100755 index 0000000..7394c08 --- /dev/null +++ b/bin/brain-dump @@ -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 [--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 '); 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 '); 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 ,,...'); 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 + +Commands: + list [--priority now,soon] [--due] [--limit N] + add --text "..." --priority soon [--context "..."] + edit [--text "..."] [--priority ...] [--context "..."] + done + show + nudged ,,... + recurring [--when evening]`); +} diff --git a/memory/brain-dump.json b/memory/brain-dump.json index 4154340..0bd1eee 100644 --- a/memory/brain-dump.json +++ b/memory/brain-dump.json @@ -1,5 +1,5 @@ { - "description": "Captured thoughts, ideas, tasks — nudge periodically", + "description": "Task tracking — auto-captured from chat or manually added. Done tasks get deleted.", "recurring": [ { "id": "nose-shower", @@ -9,36 +9,28 @@ "note": "Remind in evening, whether work just wrapped up or already relaxing" } ], - "items": [ + "tasks": [ { "id": "forgejo-script", - "added": "2026-01-31T18:13:00Z", + "added": "2026-01-31", "text": "Create script to auto-update workflows to Forgejo monorepo actions", - "context": "Deferred from evening of Jan 31 - monorepo is done, using GitHub actions as fallback for now", - "status": "pending" - }, - { - "id": "airpods-willhaben", - "added": "2026-01-31T19:30:00Z", - "text": "Put AirPods on Willhaben to sell", - "context": "Deferred from Feb 1", - "remindOn": "2026-02-02", - "status": "pending" + "context": "Monorepo is done, using GitHub actions as fallback for now", + "priority": "someday" }, { "id": "forgejo-mcp-server", - "added": "2026-01-31T20:08:00Z", + "added": "2026-01-31", "text": "Set up MCP server for Forgejo instance", - "context": "Phase 1: MCP server for interactive Claude web sessions (fork + PR workflow). Phase 2: Automated issue-solving pipeline with clarification comments. Steps: 1) Create Claude user with read rights on repos, 2) Claude can fork repos and make PRs, 3) Create workflows that deploy to dev instance on PR for testing", - "remindOn": "2026-02-03", - "status": "pending" + "context": "Phase 1: MCP server for interactive Claude web sessions (fork + PR workflow). Phase 2: Automated issue-solving pipeline with clarification comments.", + "priority": "soon", + "lastNudged": "2026-02-07T13:59:36.313Z" }, { "id": "typo3-v13-gbv", - "added": "2026-02-01T20:31:00Z", + "added": "2026-02-01", "text": "TYPO3 v13 upgrade for GBV", - "remindOn": "2026-02-03", - "status": "pending" + "priority": "soon", + "lastNudged": "2026-02-07T13:59:36.313Z" } ] }