claude-home 1.5.27 → 1.5.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,28 +2,53 @@
2
2
 
3
3
  A local web dashboard for [Claude Code](https://claude.ai/code) power users.
4
4
 
5
- Browse your session history, manage skills, agents, hooks, commands, plans, and discover community skills — all from a clean UI. No tokens consumed. Runs entirely on your machine.
5
+ Browse your session history, manage skills, agents, hooks, commands, plans, and notes — all from a clean UI. No tokens consumed. Runs entirely on your machine.
6
6
 
7
7
  ---
8
8
 
9
9
  ## Why claude-home?
10
10
 
11
- Claude Code is powerful but headless. Everything lives in files scattered across `~/.claude/` and your project directories. claude-home gives you a visual interface to explore and manage all of it:
11
+ Claude Code is powerful but headless. Everything lives in files scattered across `~/.claude/` and your project directories. claude-home gives you a visual interface to explore and manage all of it — and a **second brain** layer that persists what you build with Claude day to day.
12
12
 
13
- - **Session history** — browse every conversation you've had with Claude, search by content, resume from where you left off
14
- - **Skills** — view, create, edit and delete your user and project skills. Discover and install new ones from the community marketplace or any GitHub URL
15
- - **Agents** — manage your custom agent definitions with full CRUD
16
- - **Instructions** — read and edit your `CLAUDE.md` files (global and per-project), the instructions that shape Claude's behavior in every session
17
- - **Permissions** — inspect the allow/deny lists that control what Claude can do in each project
18
- - **Hooks** view and manage all your configured hooks across global and project settings
19
- - **Commands** — browse your slash command library
20
- - **Plans** — read and search your GSD/planning files
21
- - **Memory** inspect your auto-memory entries across projects
22
- - **Configuration** — view Claude settings per project
13
+ ---
14
+
15
+ ## Features
16
+
17
+ ### Sessions
18
+ Browse every conversation you've had with Claude. Search by content, filter by project or branch, resume from where you left off, export as Markdown, or publish as a public GitHub Gist. Direct shareable links (`#/session/...`) let you bookmark or share specific conversations.
19
+
20
+ ### Today
21
+ A daily task list that lives alongside your Claude work. Add tasks, provide context (hours available, meetings, energy), and hit **Copy for Claude** to get a formatted prompt ready to paste for prioritization. Uncompleted tasks carry over automatically to the next day.
22
+
23
+ Claude can add tasks directly from any session — just say "add this to today" or "remind me tomorrow to…"
24
+
25
+ ### Notes
26
+ Your personal notepad — separate from Claude's memory. Notes are for you, not for Claude's context. Capture decisions, TILs, bug solutions, runbooks, snippets, or anything worth keeping.
27
+
28
+ Claude can save notes directly from any session — just say "save this as a note". One-click setup from **Config → Integrations** adds the instruction to your `CLAUDE.md` automatically and grants the necessary write permissions.
29
+
30
+ Direct links (`#/note/filename`) let you open a specific note instantly.
31
+
32
+ ### Projects
33
+ Visual overview of all your Claude projects with session count, token usage, cost, and memory files. Drill into any project to browse its sessions, memory entries, and `CLAUDE.md` files.
34
+
35
+ ### Plans
36
+ Read and search your GSD/planning files from `~/.claude/plans/`. Export or publish as a Gist with one click.
37
+
38
+ ### Memory
39
+ Inspect your auto-memory entries across all projects.
40
+
41
+ ### Skills & Agents
42
+ View, create, edit and delete your user and project skills and custom agent definitions. Discover and install new skills from the community marketplace or any GitHub URL.
23
43
 
24
- ### No tokens. No cloud. No tracking.
44
+ ### Instructions & Config
45
+ Read and edit your `CLAUDE.md` files (global and per-project). Inspect permissions, hooks, and Claude settings per project.
25
46
 
26
- claude-home is a local Express server that reads your `~/.claude/` directory directly. It never calls the Claude API, never sends data anywhere, and works completely offline (except for the marketplace feature, which optionally fetches from GitHub).
47
+ ---
48
+
49
+ ## No tokens. No cloud. No tracking.
50
+
51
+ claude-home is a local Express server that reads your `~/.claude/` directory directly. It never calls the Claude API, never sends data anywhere, and works completely offline (except for the marketplace and Gist sharing features).
27
52
 
28
53
  ---
29
54
 
@@ -91,6 +116,23 @@ Commands:
91
116
 
92
117
  ---
93
118
 
119
+ ## Claude integration (Notes & Today)
120
+
121
+ claude-home can receive content directly from Claude during active sessions. Go to **Config → Integrations** and click **Set up** — this adds instructions to your `~/.claude/CLAUDE.md` and grants the necessary write permissions automatically.
122
+
123
+ Once set up, from any Claude session you can say:
124
+ - *"Save this as a note"* → creates a note in the Notes view
125
+ - *"Add this to today's tasks"* → adds a task to the Today view
126
+ - *"Remind me tomorrow to…"* → adds a task to tomorrow's list
127
+
128
+ ---
129
+
130
+ ## Sharing
131
+
132
+ Publish sessions or plans as public GitHub Gists. Go to **Config → Sharing**, add a GitHub personal access token with the `gist` scope, and use the **Gist** button in any session or plan.
133
+
134
+ ---
135
+
94
136
  ## Skills Marketplace
95
137
 
96
138
  claude-home includes a marketplace to discover and install skills from GitHub repositories.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-home",
3
- "version": "1.5.27",
3
+ "version": "1.5.35",
4
4
  "description": "Web dashboard for Claude Code — browse sessions, manage skills, hooks, commands, and agents",
5
5
  "main": "server.js",
6
6
  "bin": {
@@ -8,7 +8,7 @@
8
8
  },
9
9
  "files": [
10
10
  "bin/",
11
- "server.js",
11
+ "server.js",
12
12
  "public/",
13
13
  "marketplace.default.json",
14
14
  "README.md"
package/public/index.html CHANGED
@@ -1519,6 +1519,18 @@
1519
1519
  </div>
1520
1520
 
1521
1521
  <div class="nav-section">
1522
+ <div class="nav-item" :class="{ active: view === 'today' }" @click="view='today';selectedSession=null;loadToday()">
1523
+ <span class="nav-icon">
1524
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5">
1525
+ <rect x="2" y="2" width="12" height="12" rx="1"/>
1526
+ <line x1="5" y1="2" x2="5" y2="5"/><line x1="11" y1="2" x2="11" y2="5"/>
1527
+ <line x1="2" y1="7" x2="14" y2="7"/>
1528
+ <text x="5" y="13" font-size="5" fill="currentColor" stroke="none" font-weight="bold" x-text="new Date().getDate()"></text>
1529
+ </svg>
1530
+ </span>
1531
+ Today
1532
+ <span class="nav-count" x-show="todayData && todayData.tasks.filter(t=>!t.done).length > 0" x-text="todayData ? todayData.tasks.filter(t=>!t.done).length : ''"></span>
1533
+ </div>
1522
1534
  <div class="nav-item" :class="{ active: view === 'dashboard' }" @click="view='dashboard';selectedSession=null;loadStats();loadInsights()">
1523
1535
  <span class="nav-icon">
1524
1536
  <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke-width="1.5">
@@ -2534,6 +2546,84 @@
2534
2546
  </div>
2535
2547
  </template>
2536
2548
 
2549
+ <!-- Today view -->
2550
+ <template x-if="view === 'today' && !selectedSession">
2551
+ <div style="display:flex;flex-direction:column;height:100%;overflow:hidden;">
2552
+ <div class="topbar">
2553
+ <span class="topbar-title">Today</span>
2554
+ <span style="font-size:11px;color:var(--ink-3)" x-text="new Date().toLocaleDateString('en-US',{weekday:'long',month:'long',day:'numeric'})"></span>
2555
+ <div style="margin-left:auto;display:flex;gap:8px;align-items:center">
2556
+ <span x-show="todayCopied" style="font-size:12px;color:var(--green)" x-transition>Copied!</span>
2557
+ <button class="btn btn-sm" style="background:var(--canvas-2);color:var(--ink);border:1px solid var(--rule)" @click="copyTodayForClaude()" title="Copy tasks + context formatted for Claude">Copy for Claude</button>
2558
+ </div>
2559
+ </div>
2560
+ <div style="flex:1;overflow-y:auto;padding:24px;max-width:680px;width:100%">
2561
+ <template x-if="todayLoading"><div class="loading"><div class="spinner"></div> Loading…</div></template>
2562
+ <template x-if="!todayLoading && todayData">
2563
+ <div style="display:flex;flex-direction:column;gap:20px">
2564
+
2565
+ <!-- Context -->
2566
+ <div>
2567
+ <div style="font-size:11px;font-weight:600;color:var(--ink-3);text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px">Context for today</div>
2568
+ <textarea x-model="todayData.context" @input="saveToday()" placeholder="Hours available, meetings, energy level, deadlines…" rows="2" style="width:100%;background:var(--canvas-2);border:1px solid var(--rule);border-radius:6px;padding:8px 10px;font-size:12px;color:var(--ink);font-family:inherit;resize:vertical;box-sizing:border-box"></textarea>
2569
+ </div>
2570
+
2571
+ <!-- Task list -->
2572
+ <div>
2573
+ <div style="font-size:11px;font-weight:600;color:var(--ink-3);text-transform:uppercase;letter-spacing:.05em;margin-bottom:10px">
2574
+ Tasks — <span x-text="todayData.tasks.filter(t=>!t.done).length"></span> pending
2575
+ </div>
2576
+
2577
+ <!-- Carried over -->
2578
+ <template x-if="todayData.tasks.some(t => t.carriedOver && !t.done)">
2579
+ <div style="margin-bottom:12px">
2580
+ <div style="font-size:10px;font-weight:600;color:var(--ink-3);text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px">↩ Carried over</div>
2581
+ <template x-for="task in todayData.tasks.filter(t => t.carriedOver && !t.done)" :key="task.id">
2582
+ <div style="display:flex;align-items:center;gap:8px;padding:6px 10px;margin-bottom:4px;background:var(--canvas-2);border:1px solid var(--rule);border-left:3px solid var(--ink-3);border-radius:4px">
2583
+ <input type="checkbox" :checked="task.done" @change="toggleTodayTask(task.id)" style="flex-shrink:0;cursor:pointer" />
2584
+ <span x-text="task.text" style="flex:1;font-size:13px;color:var(--ink-2)"></span>
2585
+ <button @click="deleteTodayTask(task.id)" style="background:none;border:none;color:var(--ink-3);cursor:pointer;font-size:14px;padding:0 2px;line-height:1" title="Remove">×</button>
2586
+ </div>
2587
+ </template>
2588
+ </div>
2589
+ </template>
2590
+
2591
+ <!-- Today's tasks -->
2592
+ <template x-for="task in todayData.tasks.filter(t => !t.carriedOver && !t.done)" :key="task.id">
2593
+ <div style="display:flex;align-items:center;gap:8px;padding:6px 0;border-bottom:1px solid var(--rule)">
2594
+ <input type="checkbox" :checked="task.done" @change="toggleTodayTask(task.id)" style="flex-shrink:0;cursor:pointer" />
2595
+ <span x-text="task.text" style="flex:1;font-size:13px;color:var(--ink)"></span>
2596
+ <button @click="deleteTodayTask(task.id)" style="background:none;border:none;color:var(--ink-3);cursor:pointer;font-size:14px;padding:0 2px;line-height:1" title="Remove">×</button>
2597
+ </div>
2598
+ </template>
2599
+
2600
+ <!-- Done tasks -->
2601
+ <template x-if="todayData.tasks.some(t => t.done)">
2602
+ <div style="margin-top:12px">
2603
+ <div style="font-size:10px;color:var(--ink-3);margin-bottom:4px">✓ Done</div>
2604
+ <template x-for="task in todayData.tasks.filter(t => t.done)" :key="task.id">
2605
+ <div style="display:flex;align-items:center;gap:8px;padding:6px 0;border-bottom:1px solid var(--rule);opacity:0.5">
2606
+ <input type="checkbox" :checked="task.done" @change="toggleTodayTask(task.id)" style="flex-shrink:0;cursor:pointer" />
2607
+ <span x-text="task.text" style="flex:1;font-size:13px;text-decoration:line-through;color:var(--ink-3)"></span>
2608
+ <button @click="deleteTodayTask(task.id)" style="background:none;border:none;color:var(--ink-3);cursor:pointer;font-size:14px;padding:0 2px;line-height:1" title="Remove">×</button>
2609
+ </div>
2610
+ </template>
2611
+ </div>
2612
+ </template>
2613
+
2614
+ <!-- Add task -->
2615
+ <div style="display:flex;gap:8px;margin-top:12px">
2616
+ <input class="search-input" type="text" x-model="todayNewTask" placeholder="Add a task…" style="flex:1;font-size:13px" @keydown.enter="addTodayTask()" />
2617
+ <button class="btn btn-primary btn-sm" @click="addTodayTask()" :disabled="!todayNewTask.trim()">Add</button>
2618
+ </div>
2619
+ </div>
2620
+
2621
+ </div>
2622
+ </template>
2623
+ </div>
2624
+ </div>
2625
+ </template>
2626
+
2537
2627
  <!-- Plans view -->
2538
2628
  <template x-if="view === 'plans' && !selectedSession">
2539
2629
  <div style="display:flex;flex-direction:column;height:100%;overflow:hidden;">
@@ -2599,7 +2689,7 @@
2599
2689
  <template x-if="view === 'notes' && !selectedSession">
2600
2690
  <div style="display:flex;flex-direction:column;height:100%;overflow:hidden;">
2601
2691
  <div class="topbar">
2602
- <span class="topbar-title">Notes</span>
2692
+ <span class="topbar-title" @click="selectedNote=null;noteEditing=false;noteCreating=false" style="cursor:pointer" title="Back to Notes home">Notes</span>
2603
2693
  <span style="font-size:11px;color:var(--ink-3);padding:2px 8px;background:var(--canvas-2);border:1px solid var(--rule);border-radius:4px" title="These are your personal notes — Claude doesn't read or use them as context. They're only for you.">Your notepad · not Claude's memory</span>
2604
2694
  <input class="search-input" type="text" placeholder="Search notes…" x-model="noteSearch" style="font-size:12px;width:200px;padding:4px 10px" />
2605
2695
  <span style="font-size:11px;color:var(--ink-3)" x-show="personalNotes.length>0" x-text="personalNotes.length + ' notes'"></span>
@@ -2649,7 +2739,24 @@
2649
2739
  </div>
2650
2740
  </template>
2651
2741
  <template x-if="!personalNotesLoading && personalNotes.length === 0">
2652
- <div class="empty"><div class="empty-mark">✎</div>No notes yet. Create one or ask Claude to save one for you.</div>
2742
+ <div style="max-width:480px;margin:40px auto;padding:0 24px;text-align:center">
2743
+ <div style="font-size:28px;margin-bottom:12px">✎</div>
2744
+ <div style="font-size:15px;font-weight:600;margin-bottom:8px">Your personal notepad</div>
2745
+ <div style="font-size:13px;color:var(--ink-3);margin-bottom:24px;line-height:1.6">Notes are <strong>for you</strong>, not for Claude. Claude won't read them as context or memory — they're a place to capture what matters to you across your sessions.</div>
2746
+ <div style="background:var(--canvas-2);border:1px solid var(--rule);border-radius:8px;padding:16px;text-align:left;margin-bottom:20px">
2747
+ <div style="font-size:11px;font-weight:600;color:var(--ink-3);text-transform:uppercase;letter-spacing:.05em;margin-bottom:10px">Try saying to Claude</div>
2748
+ <div style="display:flex;flex-direction:column;gap:7px">
2749
+ <div style="font-size:12px"><span style="font-family:monospace;background:var(--canvas);border:1px solid var(--rule);padding:2px 6px;border-radius:4px">"Save this as a note"</span></div>
2750
+ <div style="font-size:12px"><span style="font-family:monospace;background:var(--canvas);border:1px solid var(--rule);padding:2px 6px;border-radius:4px">"Save this last output as a note"</span></div>
2751
+ <div style="font-size:12px"><span style="font-family:monospace;background:var(--canvas);border:1px solid var(--rule);padding:2px 6px;border-radius:4px">"Add this decision to my notes"</span></div>
2752
+ <div style="font-size:12px"><span style="font-family:monospace;background:var(--canvas);border:1px solid var(--rule);padding:2px 6px;border-radius:4px">"Save this as a TIL"</span></div>
2753
+ <div style="font-size:12px"><span style="font-family:monospace;background:var(--canvas);border:1px solid var(--rule);padding:2px 6px;border-radius:4px">"Show me my last note"</span></div>
2754
+ <div style="font-size:12px"><span style="font-family:monospace;background:var(--canvas);border:1px solid var(--rule);padding:2px 6px;border-radius:4px">"What notes do I have?"</span></div>
2755
+ <div style="font-size:12px"><span style="font-family:monospace;background:var(--canvas);border:1px solid var(--rule);padding:2px 6px;border-radius:4px">"Show me my notes"</span> <span style="font-size:11px;color:var(--ink-3)">→ http://localhost:3141</span></div>
2756
+ </div>
2757
+ </div>
2758
+ <div style="font-size:11px;color:var(--ink-3)">Or create a note manually with the <strong>+ New note</strong> button above.</div>
2759
+ </div>
2653
2760
  </template>
2654
2761
  <template x-if="!personalNotesLoading && personalNotes.length > 0">
2655
2762
  <div class="memory-layout">
@@ -2666,7 +2773,21 @@
2666
2773
  </div>
2667
2774
  <div class="resize-handle" @mousedown.prevent="startResize($event)"></div>
2668
2775
  <div class="memory-detail">
2669
- <template x-if="!selectedNote"><div class="memory-empty"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M5 12h14M12 5l7 7-7 7"/></svg>Select a note</div></template>
2776
+ <template x-if="!selectedNote">
2777
+ <div style="padding:24px;max-width:380px">
2778
+ <div style="font-size:13px;font-weight:600;margin-bottom:6px;color:var(--ink)">Your personal notepad</div>
2779
+ <div style="font-size:12px;color:var(--ink-3);margin-bottom:16px;line-height:1.6">Notes are <strong>for you</strong>, not for Claude. He won't read them as context — they're yours to capture, review and revisit.</div>
2780
+ <div style="font-size:11px;font-weight:600;color:var(--ink-3);text-transform:uppercase;letter-spacing:.05em;margin-bottom:8px">Ask Claude to save one</div>
2781
+ <div style="display:flex;flex-direction:column;gap:6px">
2782
+ <code style="font-size:11px;background:var(--canvas-2);border:1px solid var(--rule);padding:3px 7px;border-radius:4px;display:block">"Save this as a note"</code>
2783
+ <code style="font-size:11px;background:var(--canvas-2);border:1px solid var(--rule);padding:3px 7px;border-radius:4px;display:block">"Save this last output as a note"</code>
2784
+ <code style="font-size:11px;background:var(--canvas-2);border:1px solid var(--rule);padding:3px 7px;border-radius:4px;display:block">"Add this decision to my notes"</code>
2785
+ <code style="font-size:11px;background:var(--canvas-2);border:1px solid var(--rule);padding:3px 7px;border-radius:4px;display:block">"Save this as a TIL"</code>
2786
+ <code style="font-size:11px;background:var(--canvas-2);border:1px solid var(--rule);padding:3px 7px;border-radius:4px;display:block">"Show me my last note"</code>
2787
+ <code style="font-size:11px;background:var(--canvas-2);border:1px solid var(--rule);padding:3px 7px;border-radius:4px;display:block">"What notes do I have?"</code>
2788
+ </div>
2789
+ </div>
2790
+ </template>
2670
2791
  <template x-if="selectedNote && !noteEditing">
2671
2792
  <div>
2672
2793
  <div class="memory-detail-title" x-text="selectedNote.title"></div>
@@ -4338,6 +4459,11 @@
4338
4459
  planExportMsg: '',
4339
4460
  planExportOpen: false,
4340
4461
  planShareMsg: '',
4462
+ todayData: null,
4463
+ todayLoading: false,
4464
+ todayNewTask: '',
4465
+ todaySaveTimer: null,
4466
+ todayCopied: false,
4341
4467
  personalNotes: [],
4342
4468
  personalNotesLoading: false,
4343
4469
  selectedNote: null,
@@ -4518,6 +4644,7 @@
4518
4644
  this.loadStatus();
4519
4645
  this.loadInsights();
4520
4646
  // Load counts for nav badges without blocking
4647
+ this.loadToday();
4521
4648
  this.loadPlans();
4522
4649
  this.loadAgents();
4523
4650
  this.loadTools();
@@ -4540,6 +4667,7 @@
4540
4667
  },
4541
4668
 
4542
4669
  initView(v) {
4670
+ if (v === 'today') { this.loadToday(); }
4543
4671
  if (v === 'dashboard') { this.loadStats(); this.loadInsights(); }
4544
4672
  if (v === 'projects') { this.loadProjects(); }
4545
4673
  if (v === 'memory') { this.loadMemory(); }
@@ -5493,6 +5621,68 @@
5493
5621
  this.selectedSession = null;
5494
5622
  },
5495
5623
 
5624
+ async loadToday() {
5625
+ this.todayLoading = true;
5626
+ this.todayData = await fetch('/api/today').then(r => r.json()).catch(() => null);
5627
+ this.todayLoading = false;
5628
+ },
5629
+
5630
+ saveToday() {
5631
+ clearTimeout(this.todaySaveTimer);
5632
+ this.todaySaveTimer = setTimeout(async () => {
5633
+ if (!this.todayData) return;
5634
+ await fetch('/api/today', {
5635
+ method: 'PUT',
5636
+ headers: { 'Content-Type': 'application/json' },
5637
+ body: JSON.stringify({ context: this.todayData.context, tasks: this.todayData.tasks }),
5638
+ });
5639
+ }, 400);
5640
+ },
5641
+
5642
+ addTodayTask() {
5643
+ const text = this.todayNewTask.trim();
5644
+ if (!text) return;
5645
+ if (!this.todayData) return;
5646
+ this.todayData.tasks.push({
5647
+ id: Math.random().toString(36).slice(2),
5648
+ text,
5649
+ done: false,
5650
+ carriedOver: false,
5651
+ createdAt: new Date().toISOString(),
5652
+ });
5653
+ this.todayNewTask = '';
5654
+ this.saveToday();
5655
+ },
5656
+
5657
+ toggleTodayTask(id) {
5658
+ const task = this.todayData?.tasks.find(t => t.id === id);
5659
+ if (task) { task.done = !task.done; this.saveToday(); }
5660
+ },
5661
+
5662
+ deleteTodayTask(id) {
5663
+ if (!this.todayData) return;
5664
+ this.todayData.tasks = this.todayData.tasks.filter(t => t.id !== id);
5665
+ this.saveToday();
5666
+ },
5667
+
5668
+ copyTodayForClaude() {
5669
+ if (!this.todayData) return;
5670
+ const date = this.todayData.date;
5671
+ const ctx = this.todayData.context ? `\nContext: ${this.todayData.context}` : '';
5672
+ const carried = this.todayData.tasks.filter(t => t.carriedOver && !t.done);
5673
+ const fresh = this.todayData.tasks.filter(t => !t.carriedOver && !t.done);
5674
+ const done = this.todayData.tasks.filter(t => t.done);
5675
+ let text = `## My day — ${date}${ctx}\n`;
5676
+ if (carried.length) text += `\n### Carried over from yesterday:\n${carried.map(t => `- [ ] ${t.text}`).join('\n')}\n`;
5677
+ if (fresh.length) text += `\n### Today's tasks:\n${fresh.map(t => `- [ ] ${t.text}`).join('\n')}\n`;
5678
+ if (done.length) text += `\n### Already done:\n${done.map(t => `- [x] ${t.text}`).join('\n')}\n`;
5679
+ text += `\nPlease prioritize my pending tasks given the context and suggest a realistic order for today.`;
5680
+ navigator.clipboard.writeText(text).then(() => {
5681
+ this.todayCopied = true;
5682
+ setTimeout(() => this.todayCopied = false, 2000);
5683
+ });
5684
+ },
5685
+
5496
5686
  async saveSettings() {
5497
5687
  this.settingsSaving = true; this.settingsMsg = '';
5498
5688
  try {
package/server.js CHANGED
@@ -18,6 +18,33 @@ function ensureDataDir() {
18
18
  }
19
19
  ensureDataDir();
20
20
 
21
+ // ─── Claude Code permissions (auto-allow notes/todos writes) ──────────────────
22
+ const CLAUDE_SETTINGS_PATH = path.join(os.homedir(), '.claude', 'settings.json');
23
+ const CLAUDE_HOME_PERMISSIONS = [
24
+ 'Write(~/.claude/claude-home/notes/*)',
25
+ 'Write(~/.claude/claude-home/todos/*)',
26
+ ];
27
+
28
+ function ensureClaudePermissions() {
29
+ try {
30
+ let settings = {};
31
+ if (fs.existsSync(CLAUDE_SETTINGS_PATH)) {
32
+ try { settings = JSON.parse(fs.readFileSync(CLAUDE_SETTINGS_PATH, 'utf8')); } catch { /* corrupt, overwrite */ }
33
+ }
34
+ if (!settings.permissions) settings.permissions = {};
35
+ if (!Array.isArray(settings.permissions.allow)) settings.permissions.allow = [];
36
+
37
+ const before = settings.permissions.allow.length;
38
+ for (const perm of CLAUDE_HOME_PERMISSIONS) {
39
+ if (!settings.permissions.allow.includes(perm)) settings.permissions.allow.push(perm);
40
+ }
41
+ if (settings.permissions.allow.length !== before) {
42
+ fs.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2) + '\n');
43
+ }
44
+ } catch { /* never crash the server over this */ }
45
+ }
46
+ ensureClaudePermissions();
47
+
21
48
  // Load .env manually (no dotenv dependency)
22
49
  try {
23
50
  const envPath = path.join(DATA_DIR, '.env');
@@ -1909,7 +1936,32 @@ When the user asks you to "save a note", "add to notes", "guarda esto como nota"
1909
1936
  2. Use the Write tool to create the file (not Bash).
1910
1937
  3. Confirm with: "Saved to Notes: http://localhost:3141/#/note/<filename>"
1911
1938
 
1912
- The notes directory may not exist yet — create it if needed with \`mkdir -p\`.
1939
+ The notes directory may not exist yet — the app creates it automatically on first load.
1940
+
1941
+ ## Daily TODOs (Today view)
1942
+
1943
+ When the user asks to add a task "for today", "for tomorrow", "to review later", or similar:
1944
+ 1. Determine the target date (today = current date, tomorrow = current date + 1 day)
1945
+ 2. Read the existing file if it exists: \`~/.claude/claude-home/todos/YYYY-MM-DD.json\`
1946
+ 3. Use the Write tool to save the updated file with this format:
1947
+ \`\`\`json
1948
+ {
1949
+ "date": "YYYY-MM-DD",
1950
+ "context": "",
1951
+ "tasks": [
1952
+ {
1953
+ "id": "<random 8 char alphanumeric>",
1954
+ "text": "<task description>",
1955
+ "done": false,
1956
+ "carriedOver": false,
1957
+ "createdAt": "<current ISO date>"
1958
+ }
1959
+ ]
1960
+ }
1961
+ \`\`\`
1962
+ 4. Confirm with: "Added to Today: http://localhost:3141 (Today section)"
1963
+
1964
+ The todos directory may not exist yet — the app creates it automatically on first load.
1913
1965
  `;
1914
1966
 
1915
1967
  app.get('/api/notes/claude-md-status', (req, res) => {
@@ -1933,11 +1985,18 @@ app.post('/api/notes/setup-claude', (req, res) => {
1933
1985
  const settings = fs.existsSync(settingsPath) ? JSON.parse(fs.readFileSync(settingsPath, 'utf8')) : {};
1934
1986
  if (!settings.permissions) settings.permissions = {};
1935
1987
  if (!settings.permissions.allow) settings.permissions.allow = [];
1936
- const rule = `Write(${path.join(os.homedir(), '.claude', 'claude-home', 'notes', '*')})`;
1937
- if (!settings.permissions.allow.includes(rule)) {
1938
- settings.permissions.allow.push(rule);
1939
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
1988
+ const rules = [
1989
+ `Write(${path.join(os.homedir(), '.claude', 'claude-home', 'notes', '*')})`,
1990
+ `Write(${path.join(os.homedir(), '.claude', 'claude-home', 'todos', '*')})`,
1991
+ ];
1992
+ let changed = false;
1993
+ for (const rule of rules) {
1994
+ if (!settings.permissions.allow.includes(rule)) {
1995
+ settings.permissions.allow.push(rule);
1996
+ changed = true;
1997
+ }
1940
1998
  }
1999
+ if (changed) fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
1941
2000
  } catch {}
1942
2001
  res.json({ ok: true });
1943
2002
  } catch (e) { res.status(500).json({ error: e.message }); }
@@ -1995,6 +2054,60 @@ app.delete('/api/notes/:filename', (req, res) => {
1995
2054
  } catch (e) { res.status(500).json({ error: e.message }); }
1996
2055
  });
1997
2056
 
2057
+ // ─── Today / Daily TODOs ──────────────────────────────────────────────────────
2058
+ const TODOS_DIR = path.join(DATA_DIR, 'todos');
2059
+ function ensureTodosDir() { if (!fs.existsSync(TODOS_DIR)) fs.mkdirSync(TODOS_DIR, { recursive: true }); }
2060
+
2061
+ function todayStr() { return new Date().toISOString().slice(0, 10); }
2062
+
2063
+ function readTodosFile(dateStr) {
2064
+ const filePath = path.join(TODOS_DIR, `${dateStr}.json`);
2065
+ try { return JSON.parse(fs.readFileSync(filePath, 'utf8')); } catch { return null; }
2066
+ }
2067
+
2068
+ function writeTodosFile(dateStr, data) {
2069
+ ensureTodosDir();
2070
+ fs.writeFileSync(path.join(TODOS_DIR, `${dateStr}.json`), JSON.stringify(data, null, 2), 'utf8');
2071
+ }
2072
+
2073
+ app.get('/api/today', (req, res) => {
2074
+ ensureTodosDir();
2075
+ const today = todayStr();
2076
+ let data = readTodosFile(today) || { date: today, context: '', tasks: [] };
2077
+
2078
+ // Collect undone tasks from ALL previous days not already present in today's file
2079
+ const existingIds = new Set(data.tasks.map(t => t.id));
2080
+ const prevFiles = fs.readdirSync(TODOS_DIR)
2081
+ .filter(f => f.endsWith('.json') && f.slice(0, 10) < today)
2082
+ .sort();
2083
+ const toCarry = [];
2084
+ for (const f of prevFiles) {
2085
+ const prev = readTodosFile(f.slice(0, 10));
2086
+ if (!prev?.tasks) continue;
2087
+ prev.tasks.filter(t => !t.done && !existingIds.has(t.id)).forEach(t => {
2088
+ existingIds.add(t.id);
2089
+ toCarry.push({ ...t, carriedOver: true, done: false });
2090
+ });
2091
+ }
2092
+
2093
+ if (toCarry.length > 0) {
2094
+ data.tasks = [...toCarry, ...data.tasks];
2095
+ writeTodosFile(today, data);
2096
+ }
2097
+
2098
+ res.json(data);
2099
+ });
2100
+
2101
+ app.put('/api/today', (req, res) => {
2102
+ const today = todayStr();
2103
+ const { context, tasks } = req.body;
2104
+ const current = readTodosFile(today) || { date: today, context: '', tasks: [] };
2105
+ if (context !== undefined) current.context = context;
2106
+ if (tasks !== undefined) current.tasks = tasks;
2107
+ writeTodosFile(today, current);
2108
+ res.json(current);
2109
+ });
2110
+
1998
2111
  // ─── Start ────────────────────────────────────────────────────────────────────
1999
2112
 
2000
2113
  function startServer(port) {