claude-home 1.2.2 → 1.4.0

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/bin/cli.js CHANGED
@@ -66,10 +66,8 @@ if (args.includes('--help') || args.includes('-h')) {
66
66
  if (subcommand === 'stop') {
67
67
  const { execSync } = require('child_process');
68
68
  try {
69
- const pid = execSync(`lsof -ti:${port}`, { encoding: 'utf8' }).trim();
70
- if (!pid) { console.log(`No claude-home process found on port ${port}`); process.exit(0); }
71
- execSync(`kill ${pid}`);
72
- console.log(`✓ claude-home stopped (pid ${pid})`);
69
+ execSync(`lsof -ti:${port} | xargs kill -9`, { stdio: 'ignore' });
70
+ console.log(`✓ claude-home stopped`);
73
71
  } catch { console.log(`No claude-home process found on port ${port}`); }
74
72
  process.exit(0);
75
73
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-home",
3
- "version": "1.2.2",
3
+ "version": "1.4.0",
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
@@ -397,6 +397,10 @@
397
397
 
398
398
  .message { display: flex; flex-direction: column; margin-bottom: 24px; }
399
399
 
400
+ .message.assistant { flex-direction: row; align-items: flex-start; gap: 10px; }
401
+ .message.assistant .message-role { margin-bottom: 0; flex-shrink: 0; padding-top: 3px; }
402
+ .message.assistant .message-bubble .md-content > *:first-child { margin-top: 0; }
403
+
400
404
  .message-role {
401
405
  font-size: 10px;
402
406
  font-weight: 700;
@@ -431,7 +435,10 @@
431
435
  align-items: center;
432
436
  gap: 8px;
433
437
  margin-top: 6px;
438
+ opacity: 0;
439
+ transition: opacity 0.15s;
434
440
  }
441
+ .message:hover .message-footer { opacity: 1; }
435
442
 
436
443
  .msg-meta { font-size: 10.5px; color: var(--ink-3); }
437
444
 
@@ -443,13 +450,16 @@
443
450
  .md-content p { margin: 6px 0; }
444
451
  .md-content ul, .md-content ol { padding-left: 20px; margin: 6px 0; }
445
452
  .md-content li { margin: 3px 0; }
446
- .md-content code {
453
+ .md-content code, .inline-code {
447
454
  background: var(--canvas);
448
455
  border: 1px solid var(--rule);
449
456
  padding: 1px 5px;
457
+ border-radius: 3px;
450
458
  font-family: 'SF Mono', 'Fira Code', monospace;
451
459
  font-size: 12px;
452
460
  }
461
+ /* keep inline hljs within the bubble background, don't override with theme's white */
462
+ code.inline-code.hljs { background: var(--canvas); }
453
463
  .md-content pre {
454
464
  background: var(--white);
455
465
  border: 1px solid var(--rule);
@@ -1959,7 +1969,7 @@
1959
1969
  </div>
1960
1970
  </div>
1961
1971
  <div style="display:flex;gap:6px;align-items:center;flex-shrink:0">
1962
- <button class="btn btn-sm" :class="cleanMode ? 'btn-primary' : ''" style="background:var(--canvas-2);color:var(--ink)" :style="cleanMode ? 'background:var(--blue);color:#fff' : ''" @click="cleanMode=!cleanMode" title="Toggle clean view">Clean</button>
1972
+ <button class="btn btn-sm" :class="cleanMode ? 'btn-primary' : ''" style="background:var(--canvas-2);color:var(--ink)" :style="cleanMode ? 'background:var(--blue);color:#fff' : ''" @click="cleanMode=!cleanMode" title="Toggle focus view">Focus</button>
1963
1973
  <div style="position:relative">
1964
1974
  <button class="btn btn-sm" style="background:var(--canvas-2);color:var(--ink);display:flex;align-items:center;gap:4px" @click="exportDropOpen=!exportDropOpen" @click.outside="exportDropOpen=false">
1965
1975
  Export <svg width="10" height="10" viewBox="0 0 10 10" fill="currentColor"><path d="M2 3.5L5 6.5L8 3.5"/></svg>
@@ -1992,8 +2002,11 @@
1992
2002
  <div class="message-bubble">
1993
2003
  <template x-for="(block, bi) in getUserBlocks(msg)" :key="bi">
1994
2004
  <div>
2005
+ <template x-if="block.type === 'slash-command'">
2006
+ <span style="display:inline-flex;align-items:center;gap:4px;font-size:11px;padding:2px 8px;border-radius:4px;background:var(--canvas-2);border:1px solid var(--rule-2);color:var(--ink-2);font-family:monospace" x-text="block.command"></span>
2007
+ </template>
1995
2008
  <template x-if="block.type === 'text'">
1996
- <div x-text="block.text" style="white-space:pre-wrap"></div>
2009
+ <div x-html="renderInlineCode(block.text)" style="white-space:pre-wrap"></div>
1997
2010
  </template>
1998
2011
  <template x-if="block.type === 'tool_result' && !cleanMode">
1999
2012
  <div class="collapsible" style="margin-top:6px">
@@ -2022,7 +2035,9 @@
2022
2035
  <!-- Assistant message -->
2023
2036
  <template x-if="msg.type === 'assistant'">
2024
2037
  <div class="message assistant" x-show="!cleanMode || msgHasText(msg)">
2025
- <div class="message-role">Claude</div>
2038
+ <div class="message-role">
2039
+ <svg fill="currentColor" fill-rule="evenodd" width="16" height="16" viewBox="0 0 24 24" style="color:var(--ink-3)" xmlns="http://www.w3.org/2000/svg"><path d="M4.709 15.955l4.72-2.647.08-.23-.08-.128H9.2l-.79-.048-2.698-.073-2.339-.097-2.266-.122-.571-.121L0 11.784l.055-.352.48-.321.686.06 1.52.103 2.278.158 1.652.097 2.449.255h.389l.055-.157-.134-.098-.103-.097-2.358-1.596-2.552-1.688-1.336-.972-.724-.491-.364-.462-.158-1.008.656-.722.881.06.225.061.893.686 1.908 1.476 2.491 1.833.365.304.145-.103.019-.073-.164-.274-1.355-2.446-1.446-2.49-.644-1.032-.17-.619a2.97 2.97 0 01-.104-.729L6.283.134 6.696 0l.996.134.42.364.62 1.414 1.002 2.229 1.555 3.03.456.898.243.832.091.255h.158V9.01l.128-1.706.237-2.095.23-2.695.08-.76.376-.91.747-.492.584.28.48.685-.067.444-.286 1.851-.559 2.903-.364 1.942h.212l.243-.242.985-1.306 1.652-2.064.73-.82.85-.904.547-.431h1.033l.76 1.129-.34 1.166-1.064 1.347-.881 1.142-1.264 1.7-.79 1.36.073.11.188-.02 2.856-.606 1.543-.28 1.841-.315.833.388.091.395-.328.807-1.969.486-2.309.462-3.439.813-.042.03.049.061 1.549.146.662.036h1.622l3.02.225.79.522.474.638-.079.485-1.215.62-1.64-.389-3.829-.91-1.312-.329h-.182v.11l1.093 1.068 2.006 1.81 2.509 2.33.127.578-.322.455-.34-.049-2.205-1.657-.851-.747-1.926-1.62h-.128v.17l.444.649 2.345 3.521.122 1.08-.17.353-.608.213-.668-.122-1.374-1.925-1.415-2.167-1.143-1.943-.14.08-.674 7.254-.316.37-.729.28-.607-.461-.322-.747.322-1.476.389-1.924.315-1.53.286-1.9.17-.632-.012-.042-.14.018-1.434 1.967-2.18 2.945-1.726 1.845-.414.164-.717-.37.067-.662.401-.589 2.388-3.036 1.44-1.882.93-1.086-.006-.158h-.055L4.132 18.56l-1.13.146-.487-.456.061-.746.231-.243 1.908-1.312-.006.006z"></path></svg>
2040
+ </div>
2026
2041
  <div class="message-bubble">
2027
2042
  <template x-for="(block, bi) in getAssistantBlocks(msg)" :key="bi">
2028
2043
  <div>
@@ -3811,14 +3826,26 @@
3811
3826
 
3812
3827
  <script>
3813
3828
  marked.setOptions({ gfm: true, breaks: true });
3814
- marked.use({ renderer: { code(token) {
3815
- const lang = token.lang || '';
3816
- const valid = hljs.getLanguage(lang) ? lang : 'plaintext';
3817
- try {
3818
- const highlighted = hljs.highlight(token.text, { language: valid }).value;
3819
- return `<pre><code class="hljs language-${valid}">${highlighted}</code></pre>`;
3820
- } catch { return `<pre><code>${token.text}</code></pre>`; }
3821
- }}});
3829
+ marked.use({ renderer: {
3830
+ code(token) {
3831
+ const lang = token.lang || '';
3832
+ const valid = hljs.getLanguage(lang) ? lang : 'plaintext';
3833
+ try {
3834
+ const highlighted = hljs.highlight(token.text, { language: valid }).value;
3835
+ return `<pre><code class="hljs language-${valid}">${highlighted}</code></pre>`;
3836
+ } catch { return `<pre><code>${token.text}</code></pre>`; }
3837
+ },
3838
+ codespan(token) {
3839
+ try {
3840
+ const highlighted = hljs.highlightAuto(token.text).value;
3841
+ return `<code class="inline-code hljs">${highlighted}</code>`;
3842
+ } catch { return `<code class="inline-code">${token.text}</code>`; }
3843
+ }
3844
+ }});
3845
+
3846
+ function hljsInline(text) {
3847
+ try { return hljs.highlightAuto(text).value; } catch { return text; }
3848
+ }
3822
3849
 
3823
3850
  function app() {
3824
3851
  return {
@@ -3854,7 +3881,7 @@
3854
3881
  deletingSession: false,
3855
3882
  exportDropOpen: false,
3856
3883
  exportMsg: '',
3857
- cleanMode: false,
3884
+ cleanMode: true,
3858
3885
  planExportMsg: '',
3859
3886
  planExportOpen: false,
3860
3887
  sidebarW: parseInt(localStorage.getItem('cm:sidebarW') || '260'),
@@ -4253,9 +4280,13 @@
4253
4280
 
4254
4281
  getUserBlocks(msg) {
4255
4282
  const content = msg.message?.content;
4256
- if (typeof content === 'string') return [{ type: 'text', text: content }];
4257
- if (Array.isArray(content)) return content;
4258
- return [];
4283
+ const blocks = typeof content === 'string' ? [{ type: 'text', text: content }] : Array.isArray(content) ? content : [];
4284
+ return blocks.map(b => {
4285
+ if (b.type !== 'text') return b;
4286
+ const m = b.text?.match(/<command-name>([^<]+)<\/command-name>/);
4287
+ if (m) return { type: 'slash-command', command: m[1].startsWith('/') ? m[1] : '/' + m[1] };
4288
+ return b;
4289
+ });
4259
4290
  },
4260
4291
 
4261
4292
  getAssistantBlocks(msg) {
@@ -4295,6 +4326,16 @@
4295
4326
 
4296
4327
  renderMd(text) { try { return marked.parse(text); } catch { return text; } },
4297
4328
 
4329
+ renderInlineCode(text) {
4330
+ const esc = text
4331
+ .replace(/&/g, '&amp;').replace(/</g, '&lt;')
4332
+ .replace(/>/g, '&gt;').replace(/"/g, '&quot;');
4333
+ return esc.replace(/`([^`\n]+)`/g, (_, code) => {
4334
+ const decoded = code.replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&amp;/g,'&').replace(/&quot;/g,'"');
4335
+ return `<code class="inline-code hljs">${hljsInline(decoded)}</code>`;
4336
+ });
4337
+ },
4338
+
4298
4339
  shortProjectName(p) {
4299
4340
  if (!p) return 'unknown';
4300
4341
  const parts = p.replace(/\\/g, '/').split('/');
package/server.js CHANGED
@@ -200,10 +200,15 @@ async function readFirstMessage(filePath) {
200
200
  if (!timestamp && obj.timestamp) timestamp = obj.timestamp;
201
201
  if (!firstPrompt && obj.type === 'user') {
202
202
  const content = obj.message?.content;
203
- if (typeof content === 'string') firstPrompt = content.slice(0, 300);
204
- else if (Array.isArray(content)) {
205
- const txt = content.find(c => c.type === 'text');
206
- if (txt) firstPrompt = txt.text.slice(0, 300);
203
+ const isHook = typeof content === 'string'
204
+ ? content.includes('<local-command-caveat>')
205
+ : Array.isArray(content) && content.every(c => c.type === 'text' && c.text?.includes('<local-command-caveat>'));
206
+ if (!isHook) {
207
+ if (typeof content === 'string' && !content.includes('<command-name>')) firstPrompt = content.slice(0, 300);
208
+ else if (Array.isArray(content)) {
209
+ const txt = content.find(c => c.type === 'text' && !c.text?.includes('<command-name>') && !c.text?.includes('<local-command-caveat>'));
210
+ if (txt) firstPrompt = txt.text.slice(0, 300);
211
+ }
207
212
  }
208
213
  }
209
214
  if (firstPrompt && gitBranch && count > 5) break;
@@ -291,6 +296,15 @@ async function parseJsonl(filePath, { includeNoise = false, searchText = null }
291
296
  try { obj = JSON.parse(line); } catch { continue; }
292
297
  if (!includeNoise && NOISE_TYPES.has(obj.type)) continue;
293
298
 
299
+ // Filter out hook output messages (local-command-caveat)
300
+ if (obj.type === 'user') {
301
+ const c = obj.message?.content;
302
+ const isHookMsg = typeof c === 'string'
303
+ ? c.includes('<local-command-caveat>')
304
+ : Array.isArray(c) && c.every(b => b.type === 'text' && b.text?.includes('<local-command-caveat>'));
305
+ if (isHookMsg) continue;
306
+ }
307
+
294
308
  if (searchText) {
295
309
  const text = extractText(obj);
296
310
  if (!text.toLowerCase().includes(searchText.toLowerCase())) continue;