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:
parent
d4173ba0e0
commit
8b174976e6
3 changed files with 253 additions and 23 deletions
33
TOOLS.md
33
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 <id> [--text "..."] [--priority ...] [--context "..."]
|
||||
brain-dump done <id>
|
||||
brain-dump show <id>
|
||||
brain-dump nudged <id1>,<id2>,...
|
||||
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 <id1>,<id2>` → 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
|
||||
|
|
|
|||
211
bin/brain-dump
Executable file
211
bin/brain-dump
Executable 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]`);
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue