claude-mission-control 1.21.0 → 1.22.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/lib/collector.js CHANGED
@@ -621,6 +621,7 @@ class Collector {
621
621
  const sessionDays = [];
622
622
  let sessionTotal = 0;
623
623
  let costTotal = 0;
624
+ const liveIds = new Set(this.raw.live.map((s) => s.sessionId));
624
625
  const dayCut = Date.now() - 24 * 3600000;
625
626
  const timeline = [];
626
627
  const weekDayKey = dayKey(weekCut);
@@ -634,12 +635,14 @@ class Collector {
634
635
  let weekCacheAll = 0;
635
636
  for (const g of this.raw.transcriptGroups.values()) {
636
637
  if (isIgnored(g.path)) continue;
637
- sessionTotal += g.sessions.length;
638
638
  const projModels = {};
639
639
  for (const m of g.sessions) {
640
640
  const mDays = combinedDays(m);
641
641
  sessionDays.push(mDays);
642
- if (m.lastActivityAt >= dayCut) {
642
+ // Automated and empty sessions count toward spend, not toward sessions.
643
+ const real = !sessionKind(m, liveIds.has(m.sessionId));
644
+ if (real) sessionTotal++;
645
+ if (real && m.lastActivityAt >= dayCut) {
643
646
  timeline.push({
644
647
  project: friendlyName(g.path),
645
648
  sessionId: m.sessionId,
@@ -649,7 +652,7 @@ class Collector {
649
652
  });
650
653
  }
651
654
  if (m.lastActivityAt >= weekCut) {
652
- weekSessions++;
655
+ if (real) weekSessions++;
653
656
  if (m.waits) for (let i = 0; i < 4; i++) weekWaits[i] += m.waits[i];
654
657
  let mWeekCost = 0;
655
658
  for (const [day, byM] of Object.entries(mDays)) {
@@ -675,7 +678,7 @@ class Collector {
675
678
  mCost += cost;
676
679
  projModels[model] = (projModels[model] || 0) + cost;
677
680
  }
678
- topCandidates.push({
681
+ if (real) topCandidates.push({
679
682
  sessionId: m.sessionId,
680
683
  title: this.titleOf(m).title,
681
684
  projectName: friendlyName(g.path),
@@ -726,9 +729,12 @@ class Collector {
726
729
  fameSessions() {
727
730
  const pricing = readPricing();
728
731
  const out = [];
732
+ const liveIds = new Set(this.raw.live.map((s) => s.sessionId));
729
733
  for (const g of this.raw.transcriptGroups.values()) {
730
734
  if (isIgnored(g.path)) continue;
731
735
  for (const m of g.sessions) {
736
+ // A one-shot claude -p run that starts as a session ends would claim its commits.
737
+ if (sessionKind(m, liveIds.has(m.sessionId))) continue;
732
738
  const ts = toolSummary(m.tools);
733
739
  out.push({
734
740
  sessionId: m.sessionId,
@@ -154,7 +154,7 @@ async function scanFile(abs, sessionIdFromName) {
154
154
  }
155
155
  if (meta.firstUserPrompt === null) {
156
156
  const text = extractUserText(rec);
157
- if (text) meta.firstUserPrompt = text.slice(0, 120);
157
+ if (text) meta.firstUserPrompt = text.slice(0, 300); // room for tidyPromptTitle to drop a preamble
158
158
  }
159
159
  if (meta.cwd && meta.startedAt && meta.firstUserPrompt) break;
160
160
  }
@@ -655,12 +655,48 @@ function dailyCostSeries(daysList, numDays, today = Date.now(), pricing = null)
655
655
  // Best display title for a session, with its provenance.
656
656
  // `custom` is a user-set name from config.json (sessionNames); it wins over
657
657
  // anything derived from the transcript.
658
+ // A prompt reads badly as a title when it opens with where the work is:
659
+ // "In /Users/x/site/deploy.sh, the rsync flag…" or "In the Foo monorepo at
660
+ // `path` (branch y), the…". The card already names the project, so drop that
661
+ // clause, keep a file name it pointed at, and shorten home paths to ~/.
662
+ const LOCATION_WORDS = /\b(repo|repository|monorepo|project|folder|directory|codebase|theme|plugin|package|workspace)\b/i;
663
+ const homePaths = (t) => t.replace(/\/(?:Users|home)\/[^/\s]+\//g, '~/');
664
+
665
+ function leadingClauseEnd(t) {
666
+ let depth = 0, quote = null;
667
+ for (let i = 3; i < t.length; i++) {
668
+ const c = t[i];
669
+ if (quote) { if (c === quote) quote = null; continue; }
670
+ if (c === '`' || c === '"') quote = c;
671
+ else if ('([{'.includes(c)) depth++;
672
+ else if (')]}'.includes(c)) depth = Math.max(0, depth - 1);
673
+ else if ((c === ',' || c === ':') && depth === 0 && (i + 1 === t.length || t[i + 1] === ' ')) return i;
674
+ }
675
+ return -1;
676
+ }
677
+
678
+ function tidyPromptTitle(text) {
679
+ const t = String(text || '');
680
+ if (/^In /.test(t)) {
681
+ const end = leadingClauseEnd(t);
682
+ const clause = end > 0 ? t.slice(3, end) : '';
683
+ if (clause && (clause.includes('/') || LOCATION_WORDS.test(clause))) {
684
+ const rest = t.slice(end + 1).trim();
685
+ if (rest.length >= 8) {
686
+ const file = /\/([\w.-]+\.[a-z][a-z0-9]{0,5})(?=[\s`"'),]|$)/i.exec(clause);
687
+ return homePaths(file ? `${file[1]}: ${rest}` : rest);
688
+ }
689
+ }
690
+ }
691
+ return homePaths(t);
692
+ }
693
+
658
694
  function sessionTitle(meta, customNames) {
659
695
  const custom = customNames && customNames[meta.sessionId];
660
696
  if (custom) return { title: custom, source: 'custom' };
661
697
  if (meta.aiTitle) return { title: meta.aiTitle, source: 'ai-title' };
662
- if (meta.firstUserPrompt) return { title: meta.firstUserPrompt, source: 'first-prompt' };
663
- if (meta.lastPrompt) return { title: meta.lastPrompt, source: 'last-prompt' };
698
+ if (meta.firstUserPrompt) return { title: tidyPromptTitle(meta.firstUserPrompt).slice(0, 120), source: 'first-prompt' };
699
+ if (meta.lastPrompt) return { title: tidyPromptTitle(meta.lastPrompt).slice(0, 120), source: 'last-prompt' };
664
700
  return { title: meta.sessionId.slice(0, 8), source: 'session-id' };
665
701
  }
666
702
 
@@ -685,7 +721,7 @@ function groupByProject(sessions) {
685
721
  }
686
722
 
687
723
  module.exports = {
688
- scanAllTranscripts, scanFile, groupByProject, sessionTitle, sessionKind, combinedUsage, mergeUsage,
724
+ scanAllTranscripts, scanFile, groupByProject, sessionTitle, tidyPromptTitle, sessionKind, combinedUsage, mergeUsage,
689
725
  scanLine, mergeDays, combinedDays, dailyCostSeries, subagentSummary, usageBreakdown, dayKey, contextSummary, toolSummary,
690
726
  waitingReason, reasonText, shouldReadMeta, TOOL_RING,
691
727
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-mission-control",
3
- "version": "1.21.0",
3
+ "version": "1.22.0",
4
4
  "description": "Local dashboard for Claude Code: live sessions, transcripts, costs, git status — all your projects in one place.",
5
5
  "main": "server.js",
6
6
  "bin": {