claude-home 1.5.26 → 1.5.31
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 +56 -14
- package/package.json +1 -1
- package/public/index.html +161 -1
- package/server.js +97 -2
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
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
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
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:8px">
|
|
2580
|
+
<div style="font-size:10px;color:var(--ink-3);margin-bottom:4px">↩ 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 0;border-bottom:1px solid var(--rule)">
|
|
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;">
|
|
@@ -4338,6 +4428,11 @@
|
|
|
4338
4428
|
planExportMsg: '',
|
|
4339
4429
|
planExportOpen: false,
|
|
4340
4430
|
planShareMsg: '',
|
|
4431
|
+
todayData: null,
|
|
4432
|
+
todayLoading: false,
|
|
4433
|
+
todayNewTask: '',
|
|
4434
|
+
todaySaveTimer: null,
|
|
4435
|
+
todayCopied: false,
|
|
4341
4436
|
personalNotes: [],
|
|
4342
4437
|
personalNotesLoading: false,
|
|
4343
4438
|
selectedNote: null,
|
|
@@ -4518,6 +4613,7 @@
|
|
|
4518
4613
|
this.loadStatus();
|
|
4519
4614
|
this.loadInsights();
|
|
4520
4615
|
// Load counts for nav badges without blocking
|
|
4616
|
+
this.loadToday();
|
|
4521
4617
|
this.loadPlans();
|
|
4522
4618
|
this.loadAgents();
|
|
4523
4619
|
this.loadTools();
|
|
@@ -4529,7 +4625,8 @@
|
|
|
4529
4625
|
const mNote = hash.match(/^#\/note\/(.+)$/);
|
|
4530
4626
|
if (mSession) { this.view = 'sessions'; await this.openSessionById(mSession[2], mSession[1]); }
|
|
4531
4627
|
if (mNote) {
|
|
4532
|
-
await
|
|
4628
|
+
const notes = await fetch('/api/notes').then(r => r.json()).catch(() => []);
|
|
4629
|
+
this.personalNotes = notes;
|
|
4533
4630
|
const note = this.personalNotes.find(n => n.filename === mNote[1]);
|
|
4534
4631
|
if (note) { this.view = 'notes'; this.selectedNote = note; }
|
|
4535
4632
|
}
|
|
@@ -4539,6 +4636,7 @@
|
|
|
4539
4636
|
},
|
|
4540
4637
|
|
|
4541
4638
|
initView(v) {
|
|
4639
|
+
if (v === 'today') { this.loadToday(); }
|
|
4542
4640
|
if (v === 'dashboard') { this.loadStats(); this.loadInsights(); }
|
|
4543
4641
|
if (v === 'projects') { this.loadProjects(); }
|
|
4544
4642
|
if (v === 'memory') { this.loadMemory(); }
|
|
@@ -5492,6 +5590,68 @@
|
|
|
5492
5590
|
this.selectedSession = null;
|
|
5493
5591
|
},
|
|
5494
5592
|
|
|
5593
|
+
async loadToday() {
|
|
5594
|
+
this.todayLoading = true;
|
|
5595
|
+
this.todayData = await fetch('/api/today').then(r => r.json()).catch(() => null);
|
|
5596
|
+
this.todayLoading = false;
|
|
5597
|
+
},
|
|
5598
|
+
|
|
5599
|
+
saveToday() {
|
|
5600
|
+
clearTimeout(this.todaySaveTimer);
|
|
5601
|
+
this.todaySaveTimer = setTimeout(async () => {
|
|
5602
|
+
if (!this.todayData) return;
|
|
5603
|
+
await fetch('/api/today', {
|
|
5604
|
+
method: 'PUT',
|
|
5605
|
+
headers: { 'Content-Type': 'application/json' },
|
|
5606
|
+
body: JSON.stringify({ context: this.todayData.context, tasks: this.todayData.tasks }),
|
|
5607
|
+
});
|
|
5608
|
+
}, 400);
|
|
5609
|
+
},
|
|
5610
|
+
|
|
5611
|
+
addTodayTask() {
|
|
5612
|
+
const text = this.todayNewTask.trim();
|
|
5613
|
+
if (!text) return;
|
|
5614
|
+
if (!this.todayData) return;
|
|
5615
|
+
this.todayData.tasks.push({
|
|
5616
|
+
id: Math.random().toString(36).slice(2),
|
|
5617
|
+
text,
|
|
5618
|
+
done: false,
|
|
5619
|
+
carriedOver: false,
|
|
5620
|
+
createdAt: new Date().toISOString(),
|
|
5621
|
+
});
|
|
5622
|
+
this.todayNewTask = '';
|
|
5623
|
+
this.saveToday();
|
|
5624
|
+
},
|
|
5625
|
+
|
|
5626
|
+
toggleTodayTask(id) {
|
|
5627
|
+
const task = this.todayData?.tasks.find(t => t.id === id);
|
|
5628
|
+
if (task) { task.done = !task.done; this.saveToday(); }
|
|
5629
|
+
},
|
|
5630
|
+
|
|
5631
|
+
deleteTodayTask(id) {
|
|
5632
|
+
if (!this.todayData) return;
|
|
5633
|
+
this.todayData.tasks = this.todayData.tasks.filter(t => t.id !== id);
|
|
5634
|
+
this.saveToday();
|
|
5635
|
+
},
|
|
5636
|
+
|
|
5637
|
+
copyTodayForClaude() {
|
|
5638
|
+
if (!this.todayData) return;
|
|
5639
|
+
const date = this.todayData.date;
|
|
5640
|
+
const ctx = this.todayData.context ? `\nContext: ${this.todayData.context}` : '';
|
|
5641
|
+
const carried = this.todayData.tasks.filter(t => t.carriedOver && !t.done);
|
|
5642
|
+
const fresh = this.todayData.tasks.filter(t => !t.carriedOver && !t.done);
|
|
5643
|
+
const done = this.todayData.tasks.filter(t => t.done);
|
|
5644
|
+
let text = `## My day — ${date}${ctx}\n`;
|
|
5645
|
+
if (carried.length) text += `\n### Carried over from yesterday:\n${carried.map(t => `- [ ] ${t.text}`).join('\n')}\n`;
|
|
5646
|
+
if (fresh.length) text += `\n### Today's tasks:\n${fresh.map(t => `- [ ] ${t.text}`).join('\n')}\n`;
|
|
5647
|
+
if (done.length) text += `\n### Already done:\n${done.map(t => `- [x] ${t.text}`).join('\n')}\n`;
|
|
5648
|
+
text += `\nPlease prioritize my pending tasks given the context and suggest a realistic order for today.`;
|
|
5649
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
5650
|
+
this.todayCopied = true;
|
|
5651
|
+
setTimeout(() => this.todayCopied = false, 2000);
|
|
5652
|
+
});
|
|
5653
|
+
},
|
|
5654
|
+
|
|
5495
5655
|
async saveSettings() {
|
|
5496
5656
|
this.settingsSaving = true; this.settingsMsg = '';
|
|
5497
5657
|
try {
|
package/server.js
CHANGED
|
@@ -1906,10 +1906,35 @@ When the user asks you to "save a note", "add to notes", "guarda esto como nota"
|
|
|
1906
1906
|
|
|
1907
1907
|
<the content the user wants to save>
|
|
1908
1908
|
\`\`\`
|
|
1909
|
-
2. Use the
|
|
1909
|
+
2. Use the Write tool to create the file (not Bash).
|
|
1910
1910
|
3. Confirm with: "Saved to Notes: http://localhost:3141/#/note/<filename>"
|
|
1911
1911
|
|
|
1912
|
-
The notes directory may not exist yet —
|
|
1912
|
+
The notes directory may not exist yet — the app creates it automatically on first load.
|
|
1913
|
+
|
|
1914
|
+
## Daily TODOs (Today view)
|
|
1915
|
+
|
|
1916
|
+
When the user asks to add a task "for today", "for tomorrow", "to review later", or similar:
|
|
1917
|
+
1. Determine the target date (today = current date, tomorrow = current date + 1 day)
|
|
1918
|
+
2. Read the existing file if it exists: \`~/.claude/claude-home/todos/YYYY-MM-DD.json\`
|
|
1919
|
+
3. Use the Write tool to save the updated file with this format:
|
|
1920
|
+
\`\`\`json
|
|
1921
|
+
{
|
|
1922
|
+
"date": "YYYY-MM-DD",
|
|
1923
|
+
"context": "",
|
|
1924
|
+
"tasks": [
|
|
1925
|
+
{
|
|
1926
|
+
"id": "<random 8 char alphanumeric>",
|
|
1927
|
+
"text": "<task description>",
|
|
1928
|
+
"done": false,
|
|
1929
|
+
"carriedOver": false,
|
|
1930
|
+
"createdAt": "<current ISO date>"
|
|
1931
|
+
}
|
|
1932
|
+
]
|
|
1933
|
+
}
|
|
1934
|
+
\`\`\`
|
|
1935
|
+
4. Confirm with: "Added to Today: http://localhost:3141 (Today section)"
|
|
1936
|
+
|
|
1937
|
+
The todos directory may not exist yet — the app creates it automatically on first load.
|
|
1913
1938
|
`;
|
|
1914
1939
|
|
|
1915
1940
|
app.get('/api/notes/claude-md-status', (req, res) => {
|
|
@@ -1922,10 +1947,30 @@ app.get('/api/notes/claude-md-status', (req, res) => {
|
|
|
1922
1947
|
|
|
1923
1948
|
app.post('/api/notes/setup-claude', (req, res) => {
|
|
1924
1949
|
const claudeMdPath = path.join(os.homedir(), '.claude', 'CLAUDE.md');
|
|
1950
|
+
const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
|
|
1925
1951
|
try {
|
|
1926
1952
|
const current = fs.existsSync(claudeMdPath) ? fs.readFileSync(claudeMdPath, 'utf8') : '';
|
|
1927
1953
|
if (current.includes('Personal Notes')) return res.json({ ok: true, alreadyInstalled: true });
|
|
1954
|
+
// Append CLAUDE.md snippet
|
|
1928
1955
|
fs.writeFileSync(claudeMdPath, current + '\n' + NOTES_CLAUDE_MD_SNIPPET, 'utf8');
|
|
1956
|
+
// Add Write permission for notes dir to settings.json
|
|
1957
|
+
try {
|
|
1958
|
+
const settings = fs.existsSync(settingsPath) ? JSON.parse(fs.readFileSync(settingsPath, 'utf8')) : {};
|
|
1959
|
+
if (!settings.permissions) settings.permissions = {};
|
|
1960
|
+
if (!settings.permissions.allow) settings.permissions.allow = [];
|
|
1961
|
+
const rules = [
|
|
1962
|
+
`Write(${path.join(os.homedir(), '.claude', 'claude-home', 'notes', '*')})`,
|
|
1963
|
+
`Write(${path.join(os.homedir(), '.claude', 'claude-home', 'todos', '*')})`,
|
|
1964
|
+
];
|
|
1965
|
+
let changed = false;
|
|
1966
|
+
for (const rule of rules) {
|
|
1967
|
+
if (!settings.permissions.allow.includes(rule)) {
|
|
1968
|
+
settings.permissions.allow.push(rule);
|
|
1969
|
+
changed = true;
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
if (changed) fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
|
|
1973
|
+
} catch {}
|
|
1929
1974
|
res.json({ ok: true });
|
|
1930
1975
|
} catch (e) { res.status(500).json({ error: e.message }); }
|
|
1931
1976
|
});
|
|
@@ -1982,6 +2027,56 @@ app.delete('/api/notes/:filename', (req, res) => {
|
|
|
1982
2027
|
} catch (e) { res.status(500).json({ error: e.message }); }
|
|
1983
2028
|
});
|
|
1984
2029
|
|
|
2030
|
+
// ─── Today / Daily TODOs ──────────────────────────────────────────────────────
|
|
2031
|
+
const TODOS_DIR = path.join(DATA_DIR, 'todos');
|
|
2032
|
+
function ensureTodosDir() { if (!fs.existsSync(TODOS_DIR)) fs.mkdirSync(TODOS_DIR, { recursive: true }); }
|
|
2033
|
+
|
|
2034
|
+
function todayStr() { return new Date().toISOString().slice(0, 10); }
|
|
2035
|
+
|
|
2036
|
+
function readTodosFile(dateStr) {
|
|
2037
|
+
const filePath = path.join(TODOS_DIR, `${dateStr}.json`);
|
|
2038
|
+
try { return JSON.parse(fs.readFileSync(filePath, 'utf8')); } catch { return null; }
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
function writeTodosFile(dateStr, data) {
|
|
2042
|
+
ensureTodosDir();
|
|
2043
|
+
fs.writeFileSync(path.join(TODOS_DIR, `${dateStr}.json`), JSON.stringify(data, null, 2), 'utf8');
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
app.get('/api/today', (req, res) => {
|
|
2047
|
+
ensureTodosDir();
|
|
2048
|
+
const today = todayStr();
|
|
2049
|
+
let data = readTodosFile(today);
|
|
2050
|
+
if (!data) {
|
|
2051
|
+
// Carry over undone tasks from most recent previous day
|
|
2052
|
+
const files = fs.readdirSync(TODOS_DIR)
|
|
2053
|
+
.filter(f => f.endsWith('.json') && f.slice(0, 10) < today)
|
|
2054
|
+
.sort().reverse();
|
|
2055
|
+
const carriedTasks = [];
|
|
2056
|
+
if (files.length > 0) {
|
|
2057
|
+
const prev = readTodosFile(files[0].slice(0, 10));
|
|
2058
|
+
if (prev?.tasks) {
|
|
2059
|
+
prev.tasks.filter(t => !t.done).forEach(t => {
|
|
2060
|
+
carriedTasks.push({ ...t, carriedOver: true, done: false });
|
|
2061
|
+
});
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
data = { date: today, context: '', tasks: carriedTasks };
|
|
2065
|
+
writeTodosFile(today, data);
|
|
2066
|
+
}
|
|
2067
|
+
res.json(data);
|
|
2068
|
+
});
|
|
2069
|
+
|
|
2070
|
+
app.put('/api/today', (req, res) => {
|
|
2071
|
+
const today = todayStr();
|
|
2072
|
+
const { context, tasks } = req.body;
|
|
2073
|
+
const current = readTodosFile(today) || { date: today, context: '', tasks: [] };
|
|
2074
|
+
if (context !== undefined) current.context = context;
|
|
2075
|
+
if (tasks !== undefined) current.tasks = tasks;
|
|
2076
|
+
writeTodosFile(today, current);
|
|
2077
|
+
res.json(current);
|
|
2078
|
+
});
|
|
2079
|
+
|
|
1985
2080
|
// ─── Start ────────────────────────────────────────────────────────────────────
|
|
1986
2081
|
|
|
1987
2082
|
function startServer(port) {
|