claude-home 1.7.3 → 1.7.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-home",
3
- "version": "1.7.3",
3
+ "version": "1.7.4",
4
4
  "description": "Web dashboard for Claude Code — browse sessions, manage skills, hooks, commands, and agents",
5
5
  "main": "server.js",
6
6
  "bin": {
package/public/index.html CHANGED
@@ -53,7 +53,44 @@
53
53
  display: flex;
54
54
  flex-direction: column;
55
55
  overflow: hidden;
56
+ position: relative;
57
+ }
58
+ .sidebar.nav-animating { transition: width 0.18s ease, min-width 0.18s ease; }
59
+ .nav-resize-handle {
60
+ position: absolute;
61
+ top: 0; right: -3px;
62
+ width: 6px; height: 100%;
63
+ cursor: col-resize;
64
+ z-index: 10;
56
65
  }
66
+ .nav-resize-handle:hover, .nav-resize-handle.dragging { background: var(--blue); opacity: 0.35; }
67
+ .nav-collapse-btn {
68
+ margin-left: auto;
69
+ background: none;
70
+ border: none;
71
+ cursor: pointer;
72
+ color: var(--ink-3);
73
+ padding: 2px 4px;
74
+ border-radius: 4px;
75
+ display: flex;
76
+ align-items: center;
77
+ flex-shrink: 0;
78
+ }
79
+ .nav-collapse-btn:hover { color: var(--ink); background: var(--canvas); }
80
+ /* ── Icon-only mode ──────────────────────────────────── */
81
+ .nav-icon-only .brand { padding: 16px 0; justify-content: center; gap: 0; }
82
+ .nav-icon-only .brand > :not(.brand-mark) { display: none !important; }
83
+ .nav-icon-only .brand-mark { cursor: pointer; }
84
+ .nav-icon-only .brand-mark:hover { opacity: 0.75; }
85
+ .nav-icon-only .nav-section-label { display: none !important; }
86
+ .nav-icon-only .nav-divider { margin: 4px 8px; }
87
+ .nav-icon-only .nav-item { padding: 9px 0; justify-content: center; gap: 0; border-left-width: 0; }
88
+ .nav-icon-only .nav-item.active { background: var(--canvas); }
89
+ .nav-icon-only .nav-item .nav-icon { width: 44px; height: 16px; }
90
+ .nav-icon-only .nav-text { display: none !important; }
91
+ .nav-icon-only .nav-count { display: none !important; }
92
+ .nav-icon-only .live-dot { display: none !important; }
93
+ .nav-icon-only .sidebar-footer { display: none !important; }
57
94
 
58
95
  .brand {
59
96
  padding: 20px 18px 16px;
@@ -741,6 +778,8 @@
741
778
  margin-top: 6px;
742
779
  opacity: 0;
743
780
  transition: opacity 0.15s;
781
+ white-space: nowrap;
782
+ flex-wrap: nowrap;
744
783
  }
745
784
  .message:hover .message-footer { opacity: 1; }
746
785
 
@@ -1992,9 +2031,10 @@
1992
2031
  <body x-data="app()" x-init="init()">
1993
2032
 
1994
2033
  <!-- ── Sidebar ── -->
1995
- <aside class="sidebar">
2034
+ <aside class="sidebar" :class="{ 'nav-icon-only': navWidth <= 56 }" :style="`width:${navWidth}px;min-width:${navWidth}px`">
2035
+ <div class="nav-resize-handle" :class="{ dragging: _navDragging }" @mousedown.prevent="startNavResize($event)"></div>
1996
2036
  <div class="brand">
1997
- <div class="brand-mark">
2037
+ <div class="brand-mark" @click="if(navWidth <= 56) toggleNav()" :style="navWidth <= 56 ? 'cursor:pointer' : ''">
1998
2038
  <svg width="12" height="12" viewBox="0 0 12 12" fill="white">
1999
2039
  <rect x="0" y="0" width="5" height="5"/>
2000
2040
  <rect x="7" y="0" width="5" height="5"/>
@@ -2002,10 +2042,15 @@
2002
2042
  <circle cx="9.5" cy="9.5" r="2.5"/>
2003
2043
  </svg>
2004
2044
  </div>
2005
- <span style="display:flex;flex-direction:column;line-height:1">
2006
- <span class="brand-name">CLAUDE HOME</span>
2007
- <span style="font-size:9px;font-weight:400;color:var(--ink-3);letter-spacing:0.02em" x-text="status?.appVersion ? 'v'+status.appVersion : ''"></span>
2008
- </span>
2045
+ <span class="nav-text" style="display:flex;flex-direction:column;line-height:1">
2046
+ <span class="brand-name">CLAUDE HOME</span>
2047
+ <span style="font-size:9px;font-weight:400;color:var(--ink-3);letter-spacing:0.02em" x-text="status?.appVersion ? 'v'+status.appVersion : ''"></span>
2048
+ </span>
2049
+ <button class="nav-collapse-btn" @click="toggleNav()" :title="navWidth <= 56 ? 'Expandir sidebar' : 'Colapsar sidebar'">
2050
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5">
2051
+ <polyline :points="navWidth <= 56 ? '5,2 10,7 5,12' : '9,2 4,7 9,12'"/>
2052
+ </svg>
2053
+ </button>
2009
2054
  </div>
2010
2055
 
2011
2056
  <div class="nav-section">
@@ -2016,7 +2061,7 @@
2016
2061
  <line x1="5" y1="6" x2="11" y2="6"/><line x1="5" y1="10" x2="8.5" y2="10"/>
2017
2062
  </svg>
2018
2063
  </span>
2019
- Sessions
2064
+ <span class="nav-text">Sessions</span>
2020
2065
  <template x-if="activeSessions.length > 0">
2021
2066
  <span class="live-dot" title="Active session"></span>
2022
2067
  </template>
@@ -2029,7 +2074,7 @@
2029
2074
  <line x1="5" y1="6" x2="11" y2="6"/><line x1="5" y1="9" x2="11" y2="9"/><line x1="5" y1="12" x2="8" y2="12"/>
2030
2075
  </svg>
2031
2076
  </span>
2032
- Plans
2077
+ <span class="nav-text">Plans</span>
2033
2078
  <span class="nav-count" x-show="plans.length>0" x-text="plans.length"></span>
2034
2079
  </div>
2035
2080
  </div>
@@ -2044,7 +2089,7 @@
2044
2089
  <circle cx="4" cy="5" r="1.5" stroke-dasharray="2,1"/>
2045
2090
  </svg>
2046
2091
  </span>
2047
- Agents
2092
+ <span class="nav-text">Agents</span>
2048
2093
  <span class="nav-count" x-show="agents.length>0" x-text="agents.length"></span>
2049
2094
  </div>
2050
2095
  <div class="nav-item" :class="{ active: view === 'skills' }" @click="view='skills';selectedSession=null;loadTools()">
@@ -2053,7 +2098,7 @@
2053
2098
  <path d="M10 2L6 9h4l-2 5L14 7h-4z"/>
2054
2099
  </svg>
2055
2100
  </span>
2056
- Skills
2101
+ <span class="nav-text">Skills</span>
2057
2102
  <span class="nav-count" x-show="toolSkills.length>0" x-text="toolSkills.length"></span>
2058
2103
  </div>
2059
2104
  <div class="nav-item" :class="{ active: view === 'commands' }" @click="view='commands';selectedSession=null;loadTools()">
@@ -2063,7 +2108,7 @@
2063
2108
  <polyline points="5,7 7,9 5,11"/><line x1="8" y1="11" x2="12" y2="11"/>
2064
2109
  </svg>
2065
2110
  </span>
2066
- Commands
2111
+ <span class="nav-text">Commands</span>
2067
2112
  <span class="nav-count" x-show="toolCommands.length>0" x-text="toolCommands.length"></span>
2068
2113
  </div>
2069
2114
  </div>
@@ -2076,7 +2121,7 @@
2076
2121
  <line x1="6" y1="7" x2="10" y2="7"/><line x1="6" y1="10" x2="10" y2="10"/>
2077
2122
  </svg>
2078
2123
  </span>
2079
- Memory
2124
+ <span class="nav-text">Memory</span>
2080
2125
  <span class="nav-count" x-show="memoryFiles.length>0" x-text="memoryFiles.length"></span>
2081
2126
  </div>
2082
2127
  <div class="nav-item" :class="{ active: view === 'instructions' }" @click="view='instructions';selectedSession=null;loadConfig()">
@@ -2085,7 +2130,7 @@
2085
2130
  <path d="M3 2h10v12H3z"/><line x1="5.5" y1="5.5" x2="10.5" y2="5.5"/><line x1="5.5" y1="8" x2="10.5" y2="8"/><line x1="5.5" y1="10.5" x2="8.5" y2="10.5"/>
2086
2131
  </svg>
2087
2132
  </span>
2088
- Instructions
2133
+ <span class="nav-text">Instructions</span>
2089
2134
  </div>
2090
2135
  <div class="nav-item" :class="{ active: view === 'permissions' }" @click="view='permissions';selectedSession=null;loadConfig()">
2091
2136
  <span class="nav-icon">
@@ -2093,7 +2138,7 @@
2093
2138
  <rect x="4" y="7" width="8" height="7" rx="1"/><path d="M5.5 7V5a2.5 2.5 0 0 1 5 0v2"/>
2094
2139
  </svg>
2095
2140
  </span>
2096
- Permissions
2141
+ <span class="nav-text">Permissions</span>
2097
2142
  </div>
2098
2143
  <div class="nav-item" :class="{ active: view === 'hooks' }" @click="view='hooks';selectedSession=null;loadConfig()">
2099
2144
  <span class="nav-icon">
@@ -2103,7 +2148,7 @@
2103
2148
  <path d="M2.5 5a8 8 0 0 1 11 0"/>
2104
2149
  </svg>
2105
2150
  </span>
2106
- Hooks
2151
+ <span class="nav-text">Hooks</span>
2107
2152
  </div>
2108
2153
  </div>
2109
2154
  <div class="nav-divider"></div>
@@ -2116,15 +2161,18 @@
2116
2161
  <line x1="1.5" y1="8" x2="5" y2="8"/><line x1="11" y1="8" x2="14.5" y2="8"/>
2117
2162
  </svg>
2118
2163
  </span>
2119
- Config
2164
+ <span class="nav-text">Config</span>
2120
2165
  </div>
2121
2166
  </div>
2122
2167
 
2123
2168
  <template x-if="showToday || showInsights || showNotes">
2124
2169
  <div>
2125
2170
  <div class="nav-divider"></div>
2126
- <div class="nav-section-label">Widgets</div>
2127
- <div class="nav-section">
2171
+ <div class="nav-section-label" @click="widgetsOpen=!widgetsOpen;localStorage.setItem('cs:widgetsOpen',widgetsOpen)" style="cursor:pointer;display:flex;align-items:center;justify-content:space-between;padding-right:12px;user-select:none">
2172
+ Widgets
2173
+ <svg :style="widgetsOpen ? '' : 'transform:rotate(-90deg)'" style="transition:transform 0.15s" width="10" height="10" viewBox="0 0 10 10" fill="none" stroke="currentColor" stroke-width="1.5"><polyline points="2,3 5,7 8,3"/></svg>
2174
+ </div>
2175
+ <div class="nav-section" x-show="widgetsOpen || navWidth <= 56">
2128
2176
  <div x-show="showToday" class="nav-item nav-widget" :class="{ active: view === 'today' }" @click="view='today';selectedSession=null;loadToday()">
2129
2177
  <span class="nav-icon">
2130
2178
  <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5">
@@ -2134,7 +2182,7 @@
2134
2182
  <text x="5" y="13" font-size="5" fill="currentColor" stroke="none" font-weight="bold" x-text="new Date().getDate()"></text>
2135
2183
  </svg>
2136
2184
  </span>
2137
- Today
2185
+ <span class="nav-text">Today</span>
2138
2186
  <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>
2139
2187
  </div>
2140
2188
  <div x-show="showInsights" class="nav-item nav-widget" :class="{ active: view === 'dashboard' }" @click="view='dashboard';selectedSession=null;loadStats();loadInsights()">
@@ -2145,7 +2193,7 @@
2145
2193
  <rect x="11" y="2" width="3" height="12" rx="0.5"/>
2146
2194
  </svg>
2147
2195
  </span>
2148
- Analytics
2196
+ <span class="nav-text">Analytics</span>
2149
2197
  </div>
2150
2198
  <div x-show="showNotes" class="nav-item nav-widget" :class="{ active: view === 'notes' }" @click="view='notes';selectedSession=null;loadPersonalNotes()">
2151
2199
  <span class="nav-icon">
@@ -2155,7 +2203,7 @@
2155
2203
  <line x1="5" y1="7" x2="11" y2="7"/><line x1="5" y1="10" x2="9" y2="10"/>
2156
2204
  </svg>
2157
2205
  </span>
2158
- Journal
2206
+ <span class="nav-text">Journal</span>
2159
2207
  <span class="nav-count" x-show="personalNotes.length>0" x-text="personalNotes.length"></span>
2160
2208
  </div>
2161
2209
  </div>
@@ -2793,15 +2841,6 @@
2793
2841
  </template>
2794
2842
  <template x-if="sessionSummaries[s.sessionId]">
2795
2843
  <div style="display:flex;flex-direction:column;gap:8px">
2796
- <!-- Tool counts -->
2797
- <template x-if="Object.keys(sessionSummaries[s.sessionId].toolCounts).length > 0">
2798
- <div style="display:flex;flex-wrap:wrap;gap:5px;align-items:center">
2799
- <span style="font-size:10.5px;color:var(--ink-3);margin-right:2px">Tools</span>
2800
- <template x-for="[tool, count] in Object.entries(sessionSummaries[s.sessionId].toolCounts).sort((a,b)=>b[1]-a[1]).slice(0,8)" :key="tool">
2801
- <span style="font-size:11px;padding:1px 6px;border-radius:4px;background:var(--bg-3,var(--rule));color:var(--ink-2)" x-text="tool + '×' + count"></span>
2802
- </template>
2803
- </div>
2804
- </template>
2805
2844
  <!-- Files touched -->
2806
2845
  <template x-if="sessionSummaries[s.sessionId].filesTouched.length > 0">
2807
2846
  <div>
@@ -3088,14 +3127,6 @@
3088
3127
  </template>
3089
3128
  </span>
3090
3129
  </template>
3091
- <!-- Tool count badges -->
3092
- <template x-if="sessionSummaries[selectedSession?.sessionId] && Object.keys(sessionSummaries[selectedSession.sessionId].toolCounts||{}).length">
3093
- <span style="display:flex;gap:4px;align-items:center;padding-left:8px;border-left:1px solid var(--rule)">
3094
- <template x-for="[tool,count] in Object.entries(sessionSummaries[selectedSession.sessionId].toolCounts).sort((a,b)=>b[1]-a[1]).slice(0,4)" :key="tool">
3095
- <span class="meta-tag" x-text="tool+'×'+count"></span>
3096
- </template>
3097
- </span>
3098
- </template>
3099
3130
  <!-- Bookmark navigation -->
3100
3131
  <template x-if="(sessionBookmarks[selectedSession?.sessionId]||[]).length > 0">
3101
3132
  <span style="display:flex;gap:3px;align-items:center;padding-left:8px;border-left:1px solid var(--rule)">
@@ -3174,14 +3205,6 @@
3174
3205
  </span>
3175
3206
  </template>
3176
3207
 
3177
- <!-- Tool feed -->
3178
- <template x-if="liveTools.length > 0">
3179
- <span style="display:flex;align-items:center;gap:4px;padding-left:12px;border-left:1px solid #F4D0D0">
3180
- <template x-for="(tool, ti) in liveTools" :key="ti">
3181
- <span class="live-tool-tag" x-text="tool"></span>
3182
- </template>
3183
- </span>
3184
- </template>
3185
3208
 
3186
3209
 
3187
3210
  </div>
@@ -3570,9 +3593,6 @@
3570
3593
  <template x-if="msg.message?.model">
3571
3594
  <span class="msg-meta" x-text="shortModelName(msg.message.model)"></span>
3572
3595
  </template>
3573
- <template x-if="msg.message?.usage">
3574
- <span class="msg-meta" x-text="'↑' + fmtNum(msg.message.usage.input_tokens) + ' ↓' + fmtNum(msg.message.usage.output_tokens)"></span>
3575
- </template>
3576
3596
  <span class="msg-meta" x-text="formatTime(msg.timestamp)"></span>
3577
3597
  <button class="bookmark-btn" :class="{bookmarked: isBookmarked(msg.uuid)}"
3578
3598
  @click.stop="toggleBookmark(msg)" title="Bookmark">
@@ -6360,6 +6380,8 @@
6360
6380
  noteTagRenaming: false,
6361
6381
  noteTagRenameDraft: '',
6362
6382
  sidebarW: parseInt(localStorage.getItem('cm:sidebarW') || '260'),
6383
+ navWidth: parseInt(localStorage.getItem('cs:navWidth') || '224'),
6384
+ _navDragging: false,
6363
6385
  historyEntries: [],
6364
6386
  historyLoading: false,
6365
6387
  historySearch: '',
@@ -6504,6 +6526,7 @@
6504
6526
  setupTab: 'commands',
6505
6527
  showToday: localStorage.getItem('cs:showToday') !== 'false',
6506
6528
  showInsights: localStorage.getItem('cs:showInsights') !== 'false',
6529
+ widgetsOpen: localStorage.getItem('cs:widgetsOpen') !== 'false',
6507
6530
  showNotes: localStorage.getItem('cs:showNotes') !== 'false',
6508
6531
  defaultView: localStorage.getItem('cs:defaultView') || '',
6509
6532
 
@@ -6926,14 +6949,16 @@
6926
6949
  const order = [];
6927
6950
  for (const d of diffs) {
6928
6951
  if (!fileMap.has(d.filePath)) {
6929
- fileMap.set(d.filePath, { filePath: d.filePath, tool: d.tool, totalAdded: 0, totalRemoved: 0, changes: [] });
6930
6952
  order.push(d.filePath);
6931
6953
  }
6932
- const e = fileMap.get(d.filePath);
6933
- e.totalAdded += d.added;
6934
- e.totalRemoved += d.removed;
6935
- if (d.tool === 'Write') e.tool = 'Write';
6936
- e.changes.push(d);
6954
+ // Siempre sobreescribir con el último cambio del fichero
6955
+ fileMap.set(d.filePath, {
6956
+ filePath: d.filePath,
6957
+ tool: d.tool,
6958
+ totalAdded: d.added,
6959
+ totalRemoved: d.removed,
6960
+ changes: [d],
6961
+ });
6937
6962
  }
6938
6963
  return order.map(k => fileMap.get(k));
6939
6964
  },
@@ -7420,6 +7445,46 @@
7420
7445
  document.addEventListener('mouseup', onUp);
7421
7446
  },
7422
7447
 
7448
+ toggleNav() {
7449
+ const el = document.querySelector('.sidebar');
7450
+ el.classList.add('nav-animating');
7451
+ if (this.navWidth <= 56) {
7452
+ this.navWidth = parseInt(localStorage.getItem('cs:navWidthFull') || '224');
7453
+ } else {
7454
+ localStorage.setItem('cs:navWidthFull', this.navWidth);
7455
+ this.navWidth = 44;
7456
+ }
7457
+ localStorage.setItem('cs:navWidth', this.navWidth);
7458
+ setTimeout(() => el.classList.remove('nav-animating'), 220);
7459
+ },
7460
+ startNavResize(e) {
7461
+ const startX = e.clientX;
7462
+ const startW = this.navWidth;
7463
+ this._navDragging = true;
7464
+ document.body.style.cursor = 'col-resize';
7465
+ document.body.style.userSelect = 'none';
7466
+ const ICON_W = 44, SNAP_IN = 80, MIN_FULL = 160, MAX_W = 400;
7467
+ const onMove = (ev) => {
7468
+ const w = startW + (ev.clientX - startX);
7469
+ if (w < SNAP_IN) {
7470
+ this.navWidth = ICON_W;
7471
+ } else {
7472
+ this.navWidth = Math.max(MIN_FULL, Math.min(MAX_W, w));
7473
+ }
7474
+ };
7475
+ const onUp = () => {
7476
+ this._navDragging = false;
7477
+ document.body.style.cursor = '';
7478
+ document.body.style.userSelect = '';
7479
+ if (this.navWidth > ICON_W) localStorage.setItem('cs:navWidthFull', this.navWidth);
7480
+ localStorage.setItem('cs:navWidth', this.navWidth);
7481
+ document.removeEventListener('mousemove', onMove);
7482
+ document.removeEventListener('mouseup', onUp);
7483
+ };
7484
+ document.addEventListener('mousemove', onMove);
7485
+ document.addEventListener('mouseup', onUp);
7486
+ },
7487
+
7423
7488
  async deleteSession() {
7424
7489
  if (!this.selectedSession) return;
7425
7490
  if (!confirm(`Delete session "${this.selectedSession.firstPrompt || this.selectedSession.sessionId}"? This cannot be undone.`)) return;
package/server.js CHANGED
@@ -2236,16 +2236,28 @@ app.post('/api/sessions/:project/:sessionId/snapshot', async (req, res) => {
2236
2236
 
2237
2237
  // Extract user prompts (skip <command-name> tool messages)
2238
2238
  const prompts = [];
2239
+ // Track last tool per file (Write wins over Edit)
2240
+ const fileToolMap = new Map(); // filePath → 'Write'|'Edit'
2239
2241
  for (const m of messages) {
2240
- if (m.type !== 'user') continue;
2241
- const c = m.message?.content;
2242
- let text = '';
2243
- if (typeof c === 'string') text = c;
2244
- else if (Array.isArray(c)) {
2245
- text = c.filter(b => b.type === 'text' && !b.text?.includes('<command-name>')).map(b => b.text).join('\n');
2242
+ if (m.type === 'user') {
2243
+ const c = m.message?.content;
2244
+ let text = '';
2245
+ if (typeof c === 'string') text = c;
2246
+ else if (Array.isArray(c)) {
2247
+ text = c.filter(b => b.type === 'text' && !b.text?.includes('<command-name>')).map(b => b.text).join('\n');
2248
+ }
2249
+ text = text.trim();
2250
+ if (text && text.length > 2) prompts.push(text);
2251
+ } else if (m.type === 'assistant') {
2252
+ const content = m.message?.content;
2253
+ if (!Array.isArray(content)) continue;
2254
+ for (const block of content) {
2255
+ if (block.type !== 'tool_use') continue;
2256
+ if ((block.name === 'Edit' || block.name === 'Write') && block.input?.file_path) {
2257
+ fileToolMap.set(block.input.file_path, block.name);
2258
+ }
2259
+ }
2246
2260
  }
2247
- text = text.trim();
2248
- if (text && text.length > 2) prompts.push(text);
2249
2261
  }
2250
2262
 
2251
2263
  const firstPrompt = prompts[0] || sessionId;
@@ -2256,7 +2268,12 @@ app.post('/api/sessions/:project/:sessionId/snapshot', async (req, res) => {
2256
2268
 
2257
2269
  const promptList = prompts.map((p, i) => `${i + 1}. ${p.replace(/\n+/g, ' ').slice(0, 200)}${p.length > 200 ? '…' : ''}`).join('\n');
2258
2270
 
2259
- const content = `${meta}\n\n## Prompts\n\n${promptList}\n\n## Notes\n\n`;
2271
+ const fileLines = [...fileToolMap.entries()]
2272
+ .map(([fp, tool]) => `- \`${fp}\`${tool === 'Write' ? ' *(created)*' : ''}`)
2273
+ .join('\n');
2274
+ const filesSection = fileToolMap.size > 0 ? `\n\n## Files changed\n\n${fileLines}` : '';
2275
+
2276
+ const content = `${meta}\n\n## Prompts\n\n${promptList}${filesSection}\n\n## Notes\n\n`;
2260
2277
 
2261
2278
  ensureNotesDir();
2262
2279
  const slug = firstPrompt.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 40) || 'snapshot';