@viren/claude-code-dashboard 0.0.8 → 0.0.10
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 +1 -1
- package/src/assembler.mjs +16 -14
- package/src/constants.mjs +1 -1
- package/src/helpers.mjs +98 -14
- package/src/pipeline.mjs +38 -1
- package/src/sections.mjs +35 -46
- package/template/dashboard.css +8 -7
- package/template/dashboard.html +8 -6
- package/template/dashboard.js +7 -7
package/package.json
CHANGED
package/src/assembler.mjs
CHANGED
|
@@ -2,12 +2,13 @@ import { readFileSync } from "fs";
|
|
|
2
2
|
import { fileURLToPath } from "url";
|
|
3
3
|
import { dirname, join } from "path";
|
|
4
4
|
|
|
5
|
-
import { esc,
|
|
5
|
+
import { esc, insightsToPrompt } from "./helpers.mjs";
|
|
6
6
|
import { VERSION, REPO_URL } from "./constants.mjs";
|
|
7
7
|
import { renderCmd, renderRule, renderRepoCard } from "./render.mjs";
|
|
8
8
|
import {
|
|
9
9
|
renderSkillsCard,
|
|
10
10
|
renderMcpCard,
|
|
11
|
+
renderMcpRecommendedCard,
|
|
11
12
|
renderToolsCard,
|
|
12
13
|
renderLangsCard,
|
|
13
14
|
renderErrorsCard,
|
|
@@ -71,12 +72,20 @@ export function generateDashboardHtml(data) {
|
|
|
71
72
|
<button id="refresh-btn" class="header-btn" title="Copy refresh command to clipboard" aria-label="Copy refresh command">↻ refresh</button>
|
|
72
73
|
<button id="theme-toggle" class="theme-toggle" title="Toggle light/dark mode" aria-label="Toggle theme"><span class="theme-icon"></span></button>
|
|
73
74
|
</div>
|
|
74
|
-
<p class="sub">generated ${timestamp} · <a href="${esc(REPO_URL)}" target="_blank" rel="noopener" style="color:var(--accent);text-decoration:
|
|
75
|
+
<p class="sub">generated ${timestamp} · <a href="${esc(REPO_URL)}" target="_blank" rel="noopener" style="color:var(--accent);text-decoration:underline;text-underline-offset:2px">v${esc(VERSION)}</a></p>`;
|
|
75
76
|
|
|
76
77
|
const statsBar = renderStatsBar(data);
|
|
77
78
|
|
|
78
|
-
//
|
|
79
|
-
const
|
|
79
|
+
// Home tab — actionable content first
|
|
80
|
+
const insightsPrompt = insightsToPrompt(insights);
|
|
81
|
+
const insightsHtml = renderInsightsCard(insights, insightsPrompt);
|
|
82
|
+
const mcpRecsHtml = renderMcpRecommendedCard(recommendedMcpServers);
|
|
83
|
+
const chainsHtml = renderChainsCard(chains);
|
|
84
|
+
const consolidationHtml = renderConsolidationCard(consolidationGroups);
|
|
85
|
+
const tabHome = `${insightsHtml}\n ${mcpRecsHtml}\n ${chainsHtml}\n ${consolidationHtml}`;
|
|
86
|
+
|
|
87
|
+
// Config tab — stable reference: commands, rules, skills, MCP servers
|
|
88
|
+
const commandsRulesHtml = `<div class="top-grid">
|
|
80
89
|
<div class="card" id="section-commands" style="margin-bottom:0">
|
|
81
90
|
<h2>Global Commands <span class="n">${globalCmds.length}</span></h2>
|
|
82
91
|
${globalCmds.map((c) => renderCmd(c)).join("\n ")}
|
|
@@ -86,14 +95,7 @@ export function generateDashboardHtml(data) {
|
|
|
86
95
|
${globalRules.map((r) => renderRule(r)).join("\n ")}
|
|
87
96
|
</div>
|
|
88
97
|
</div>`;
|
|
89
|
-
const
|
|
90
|
-
const insightsHtml = renderInsightsCard(insights, insightsMarkdown);
|
|
91
|
-
const chainsHtml = renderChainsCard(chains);
|
|
92
|
-
const consolidationHtml = renderConsolidationCard(consolidationGroups);
|
|
93
|
-
const tabOverview = `${overviewCommands}\n ${insightsHtml}\n ${chainsHtml}\n ${consolidationHtml}`;
|
|
94
|
-
|
|
95
|
-
// Skills & MCP tab
|
|
96
|
-
const tabSkillsMcp = `${renderSkillsCard(globalSkills)}\n ${renderMcpCard(mcpSummary, mcpPromotions, formerMcpServers, recommendedMcpServers, availableMcpServers, registryTotal)}`;
|
|
98
|
+
const tabConfig = `${commandsRulesHtml}\n ${renderSkillsCard(globalSkills)}\n ${renderMcpCard(mcpSummary, mcpPromotions, formerMcpServers, availableMcpServers, registryTotal)}`;
|
|
97
99
|
|
|
98
100
|
// Analytics tab
|
|
99
101
|
const insightsReportHtml = renderInsightsReportCard(insightsReport);
|
|
@@ -146,8 +148,8 @@ export function generateDashboardHtml(data) {
|
|
|
146
148
|
html = html.replace("/* {{JS}} */", _js);
|
|
147
149
|
html = html.replace("<!-- {{HEADER}} -->", header);
|
|
148
150
|
html = html.replace("<!-- {{STATS_BAR}} -->", statsBar);
|
|
149
|
-
html = html.replace("<!-- {{
|
|
150
|
-
html = html.replace("<!-- {{
|
|
151
|
+
html = html.replace("<!-- {{TAB_HOME}} -->", tabHome);
|
|
152
|
+
html = html.replace("<!-- {{TAB_CONFIG}} -->", tabConfig);
|
|
151
153
|
html = html.replace("<!-- {{TAB_ANALYTICS}} -->", tabAnalytics);
|
|
152
154
|
html = html.replace("<!-- {{TAB_REPOS}} -->", tabRepos);
|
|
153
155
|
html = html.replace("<!-- {{TAB_REFERENCE}} -->", tabReference);
|
package/src/constants.mjs
CHANGED
package/src/helpers.mjs
CHANGED
|
@@ -33,25 +33,109 @@ export function gitCmd(repoDir, ...args) {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
const PROMPT_GENERATORS = {
|
|
37
|
+
"config-drift"(meta) {
|
|
38
|
+
const list = meta.repos.map((r) => `- ${r.name} (${r.commitsSince} commits since last update)`);
|
|
39
|
+
return [
|
|
40
|
+
"These repos have stale CLAUDE.md files:",
|
|
41
|
+
...list,
|
|
42
|
+
"",
|
|
43
|
+
"For each repo:",
|
|
44
|
+
"1. Read the current CLAUDE.md",
|
|
45
|
+
"2. Run `git log --oneline` to see what changed since the last config update",
|
|
46
|
+
"3. Check if new patterns, tools, or conventions were introduced that should be documented",
|
|
47
|
+
"4. Propose targeted additions — don't rewrite from scratch, just fill gaps",
|
|
48
|
+
].join("\n");
|
|
49
|
+
},
|
|
50
|
+
"unconfigured-repos"(meta) {
|
|
51
|
+
if (!meta.repos.length) return "Several repos have no Claude Code configuration.";
|
|
52
|
+
const list = meta.repos.map((r) => `- ${r.name} (${r.techStack.join(", ")})`);
|
|
53
|
+
return [
|
|
54
|
+
"These repos have no CLAUDE.md yet:",
|
|
55
|
+
...list,
|
|
56
|
+
"",
|
|
57
|
+
"Pick the one you want to configure. For that repo:",
|
|
58
|
+
"1. Read the project structure and config files (package.json, pyproject.toml, etc.)",
|
|
59
|
+
"2. Identify the build system, test framework, and linter",
|
|
60
|
+
"3. Generate a concise CLAUDE.md (50-100 lines) with build/test/lint commands and key conventions",
|
|
61
|
+
].join("\n");
|
|
62
|
+
},
|
|
63
|
+
"mcp-promotion"(meta) {
|
|
64
|
+
const list = meta.servers.map((s) => `- ${s.name} (used in ${s.projectCount} projects)`);
|
|
65
|
+
return [
|
|
66
|
+
"These MCP servers are installed in multiple projects and should be promoted to global:",
|
|
67
|
+
...list,
|
|
68
|
+
"",
|
|
69
|
+
"To promote a server, run:",
|
|
70
|
+
" claude mcp add --scope user <server-name> <config>",
|
|
71
|
+
"Then remove the duplicate entries from each project's .mcp.json.",
|
|
72
|
+
].join("\n");
|
|
73
|
+
},
|
|
74
|
+
"mcp-redundant"(meta) {
|
|
75
|
+
const list = meta.servers.map((s) => `- ${s.name}: remove from ${s.projects.join(", ")}`);
|
|
76
|
+
return [
|
|
77
|
+
"These MCP servers are configured globally AND redundantly in project .mcp.json:",
|
|
78
|
+
...list,
|
|
79
|
+
"",
|
|
80
|
+
"The global config already covers all projects. Remove the listed project-level entries.",
|
|
81
|
+
].join("\n");
|
|
82
|
+
},
|
|
83
|
+
"mcp-recommendations"(meta) {
|
|
84
|
+
const list = meta.servers.map(
|
|
85
|
+
(s) =>
|
|
86
|
+
`- ${s.name} — ${s.reasons.join(", ")}` +
|
|
87
|
+
(s.installCommand?.trim() ? `\n ${s.installCommand.trim()}` : ""),
|
|
88
|
+
);
|
|
89
|
+
return [
|
|
90
|
+
"Based on your tech stacks, these MCP servers would be useful:",
|
|
91
|
+
...list,
|
|
92
|
+
"",
|
|
93
|
+
"Run any install command above to add a server. Which ones interest you?",
|
|
94
|
+
].join("\n");
|
|
95
|
+
},
|
|
96
|
+
"shared-skills"(meta) {
|
|
97
|
+
const list = meta.skills.map((s) => `- ${s.name} (relevant in ${s.repoCount} repos)`);
|
|
98
|
+
return [
|
|
99
|
+
"These skills are relevant across multiple repos and would benefit from being global:",
|
|
100
|
+
...list,
|
|
101
|
+
"",
|
|
102
|
+
"To add a skill globally, copy its definition from any project's .claude/commands/ to ~/.claude/commands/.",
|
|
103
|
+
].join("\n");
|
|
104
|
+
},
|
|
105
|
+
"health-quickwins"(meta) {
|
|
106
|
+
const list = meta.repos.map((r) => `- ${r.name} (${r.healthScore}/100): ${r.topReason}`);
|
|
107
|
+
return [
|
|
108
|
+
"These repos have easy config health improvements:",
|
|
109
|
+
...list,
|
|
110
|
+
"",
|
|
111
|
+
"Pick a repo and I'll help you make the specific improvement.",
|
|
112
|
+
].join("\n");
|
|
113
|
+
},
|
|
114
|
+
"insights-report"() {
|
|
115
|
+
return "Run `/insights` in Claude Code to generate a personalized usage report with patterns, friction points, and suggestions.";
|
|
116
|
+
},
|
|
41
117
|
};
|
|
42
118
|
|
|
43
|
-
/** Convert
|
|
44
|
-
export function
|
|
119
|
+
/** Convert insights to an actionable prompt for pasting into Claude Code. */
|
|
120
|
+
export function insightsToPrompt(insights) {
|
|
45
121
|
if (!insights || !insights.length) return "";
|
|
46
|
-
const
|
|
122
|
+
const sections = [];
|
|
47
123
|
for (const i of insights) {
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
124
|
+
const gen = i.meta?.kind && PROMPT_GENERATORS[i.meta.kind];
|
|
125
|
+
if (gen) {
|
|
126
|
+
sections.push(gen(i.meta));
|
|
127
|
+
} else {
|
|
128
|
+
const lines = [`${i.title}`];
|
|
129
|
+
if (i.detail) lines.push(i.detail);
|
|
130
|
+
if (i.action) lines.push(i.action);
|
|
131
|
+
sections.push(lines.join("\n"));
|
|
132
|
+
}
|
|
53
133
|
}
|
|
54
|
-
return
|
|
134
|
+
return [
|
|
135
|
+
"I ran the Claude Code Dashboard and found these items to address:",
|
|
136
|
+
...sections.map((s, idx) => `${idx + 1}. ${s}`),
|
|
137
|
+
"Which of these would you like to tackle first?",
|
|
138
|
+
].join("\n\n");
|
|
55
139
|
}
|
|
56
140
|
|
|
57
141
|
export function anonymizePath(p) {
|
package/src/pipeline.mjs
CHANGED
|
@@ -446,6 +446,10 @@ export function buildDashboardData(raw) {
|
|
|
446
446
|
.map((r) => `${r.name} (${r.drift.commitsSince} commits since config update)`)
|
|
447
447
|
.join(", "),
|
|
448
448
|
action: "Review and update CLAUDE.md in these repos",
|
|
449
|
+
meta: {
|
|
450
|
+
kind: "config-drift",
|
|
451
|
+
repos: highDriftRepos.map((r) => ({ name: r.name, commitsSince: r.drift.commitsSince })),
|
|
452
|
+
},
|
|
449
453
|
});
|
|
450
454
|
}
|
|
451
455
|
|
|
@@ -461,6 +465,10 @@ export function buildDashboardData(raw) {
|
|
|
461
465
|
? `Top candidates: ${withStack.map((r) => `${r.name} (${r.techStack.join(", ")})`).join(", ")}`
|
|
462
466
|
: "",
|
|
463
467
|
action: "Run claude-code-dashboard init --template <stack> in these repos",
|
|
468
|
+
meta: {
|
|
469
|
+
kind: "unconfigured-repos",
|
|
470
|
+
repos: withStack.map((r) => ({ name: r.name, techStack: r.techStack })),
|
|
471
|
+
},
|
|
464
472
|
});
|
|
465
473
|
}
|
|
466
474
|
}
|
|
@@ -472,6 +480,10 @@ export function buildDashboardData(raw) {
|
|
|
472
480
|
title: `${mcpPromotions.length} MCP server${mcpPromotions.length > 1 ? "s" : ""} could be promoted to global`,
|
|
473
481
|
detail: mcpPromotions.map((p) => `${p.name} (in ${p.projects.length} projects)`).join(", "),
|
|
474
482
|
action: "Add to ~/.claude/mcp_config.json for all projects",
|
|
483
|
+
meta: {
|
|
484
|
+
kind: "mcp-promotion",
|
|
485
|
+
servers: mcpPromotions.map((p) => ({ name: p.name, projectCount: p.projects.length })),
|
|
486
|
+
},
|
|
475
487
|
});
|
|
476
488
|
}
|
|
477
489
|
|
|
@@ -483,6 +495,10 @@ export function buildDashboardData(raw) {
|
|
|
483
495
|
title: `${redundantMcp.length} MCP server${redundantMcp.length > 1 ? "s are" : " is"} global but also in project .mcp.json`,
|
|
484
496
|
detail: redundantMcp.map((s) => `${s.name} (${s.projects.join(", ")})`).join("; "),
|
|
485
497
|
action: "Remove from project .mcp.json — global config already covers all projects",
|
|
498
|
+
meta: {
|
|
499
|
+
kind: "mcp-redundant",
|
|
500
|
+
servers: redundantMcp.map((s) => ({ name: s.name, projects: s.projects })),
|
|
501
|
+
},
|
|
486
502
|
});
|
|
487
503
|
}
|
|
488
504
|
|
|
@@ -495,7 +511,15 @@ export function buildDashboardData(raw) {
|
|
|
495
511
|
.slice(0, 3)
|
|
496
512
|
.map((s) => `${s.name} (${s.reasons.join(", ")})`)
|
|
497
513
|
.join(", "),
|
|
498
|
-
action: "Check the
|
|
514
|
+
action: "Check the Config tab for install commands",
|
|
515
|
+
meta: {
|
|
516
|
+
kind: "mcp-recommendations",
|
|
517
|
+
servers: recommendedMcpServers.slice(0, 5).map((s) => ({
|
|
518
|
+
name: s.name,
|
|
519
|
+
installCommand: s.installCommand,
|
|
520
|
+
reasons: s.reasons,
|
|
521
|
+
})),
|
|
522
|
+
},
|
|
499
523
|
});
|
|
500
524
|
}
|
|
501
525
|
|
|
@@ -518,6 +542,10 @@ export function buildDashboardData(raw) {
|
|
|
518
542
|
title: `${widelyRelevant.length} skill${widelyRelevant.length > 1 ? "s" : ""} relevant across 3+ repos`,
|
|
519
543
|
detail: top.map(([name, repos]) => `${name} (${repos.length} repos)`).join(", "),
|
|
520
544
|
action: "Consider adding these skills to your global config",
|
|
545
|
+
meta: {
|
|
546
|
+
kind: "shared-skills",
|
|
547
|
+
skills: top.map(([name, repos]) => ({ name, repoCount: repos.length })),
|
|
548
|
+
},
|
|
521
549
|
});
|
|
522
550
|
}
|
|
523
551
|
|
|
@@ -534,6 +562,14 @@ export function buildDashboardData(raw) {
|
|
|
534
562
|
.map((r) => `${r.name} (${r.healthScore}/100): ${r.healthReasons[0]}`)
|
|
535
563
|
.join("; "),
|
|
536
564
|
action: "Small changes for measurable improvement",
|
|
565
|
+
meta: {
|
|
566
|
+
kind: "health-quickwins",
|
|
567
|
+
repos: quickWinRepos.map((r) => ({
|
|
568
|
+
name: r.name,
|
|
569
|
+
healthScore: r.healthScore,
|
|
570
|
+
topReason: r.healthReasons[0],
|
|
571
|
+
})),
|
|
572
|
+
},
|
|
537
573
|
});
|
|
538
574
|
}
|
|
539
575
|
|
|
@@ -544,6 +580,7 @@ export function buildDashboardData(raw) {
|
|
|
544
580
|
title: "Generate your Claude Code Insights report",
|
|
545
581
|
detail: "Get personalized usage patterns, friction points, and feature suggestions",
|
|
546
582
|
action: "Run /insights in Claude Code",
|
|
583
|
+
meta: { kind: "insights-report" },
|
|
547
584
|
});
|
|
548
585
|
}
|
|
549
586
|
|
package/src/sections.mjs
CHANGED
|
@@ -21,15 +21,33 @@ export function renderSkillsCard(globalSkills) {
|
|
|
21
21
|
</div>`;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
export function renderMcpRecommendedCard(recommendedMcpServers) {
|
|
25
|
+
if (!recommendedMcpServers.length) return "";
|
|
26
|
+
return `<div class="card">
|
|
27
|
+
<h2>Recommended MCP Servers <span class="n">${recommendedMcpServers.length}</span></h2>
|
|
28
|
+
${recommendedMcpServers
|
|
29
|
+
.map(
|
|
30
|
+
(s) =>
|
|
31
|
+
`<div class="mcp-recommended"><span class="mcp-name">${esc(s.name)}</span> <span class="mcp-rec-badge">recommended</span>` +
|
|
32
|
+
(s.description ? `<div class="mcp-desc">${esc(s.description)}</div>` : "") +
|
|
33
|
+
(s.reasons && s.reasons.length
|
|
34
|
+
? `<div class="mcp-reason">${s.reasons.map((r) => esc(r)).join(", ")}</div>`
|
|
35
|
+
: "") +
|
|
36
|
+
(s.installCommand ? `<code class="mcp-install">${esc(s.installCommand)}</code>` : "") +
|
|
37
|
+
`</div>`,
|
|
38
|
+
)
|
|
39
|
+
.join("\n ")}
|
|
40
|
+
</div>`;
|
|
41
|
+
}
|
|
42
|
+
|
|
24
43
|
export function renderMcpCard(
|
|
25
44
|
mcpSummary,
|
|
26
45
|
mcpPromotions,
|
|
27
46
|
formerMcpServers,
|
|
28
|
-
recommendedMcpServers,
|
|
29
47
|
availableMcpServers,
|
|
30
48
|
registryTotal,
|
|
31
49
|
) {
|
|
32
|
-
if (!mcpSummary.length && !
|
|
50
|
+
if (!mcpSummary.length && !availableMcpServers.length) return "";
|
|
33
51
|
const rows = mcpSummary
|
|
34
52
|
.map((s) => {
|
|
35
53
|
const disabledClass = s.disabledIn > 0 ? " mcp-disabled" : "";
|
|
@@ -69,24 +87,6 @@ export function renderMcpCard(
|
|
|
69
87
|
})
|
|
70
88
|
.join("\n ")}`
|
|
71
89
|
: "";
|
|
72
|
-
const recommendedHtml = recommendedMcpServers.length
|
|
73
|
-
? `<details class="mcp-section"${recommendedMcpServers.length <= 5 ? " open" : ""}>
|
|
74
|
-
<summary class="label" style="cursor:pointer;margin-top:.75rem">Recommended <span class="cat-n">${recommendedMcpServers.length}</span></summary>
|
|
75
|
-
${recommendedMcpServers
|
|
76
|
-
.map(
|
|
77
|
-
(s) =>
|
|
78
|
-
`<div class="mcp-recommended"><span class="mcp-name">${esc(s.name)}</span> <span class="mcp-rec-badge">recommended</span>` +
|
|
79
|
-
(s.description ? `<div class="mcp-desc">${esc(s.description)}</div>` : "") +
|
|
80
|
-
(s.reasons && s.reasons.length
|
|
81
|
-
? `<div class="mcp-reason">${s.reasons.map((r) => esc(r)).join(", ")}</div>`
|
|
82
|
-
: "") +
|
|
83
|
-
(s.installCommand ? `<code class="mcp-install">${esc(s.installCommand)}</code>` : "") +
|
|
84
|
-
`</div>`,
|
|
85
|
-
)
|
|
86
|
-
.join("\n ")}
|
|
87
|
-
</details>`
|
|
88
|
-
: "";
|
|
89
|
-
|
|
90
90
|
const availableHtml = availableMcpServers.length
|
|
91
91
|
? `<details class="mcp-section">
|
|
92
92
|
<summary class="label" style="cursor:pointer;margin-top:.75rem">Available <span class="cat-n">${availableMcpServers.length}</span></summary>
|
|
@@ -112,7 +112,6 @@ export function renderMcpCard(
|
|
|
112
112
|
${rows}
|
|
113
113
|
${promoteHtml}
|
|
114
114
|
${formerHtml}
|
|
115
|
-
${recommendedHtml}
|
|
116
115
|
${availableHtml}
|
|
117
116
|
${registryNote}
|
|
118
117
|
</div>`;
|
|
@@ -378,13 +377,13 @@ export function renderReferenceCard() {
|
|
|
378
377
|
</div>`;
|
|
379
378
|
}
|
|
380
379
|
|
|
381
|
-
export function renderInsightsCard(insights,
|
|
380
|
+
export function renderInsightsCard(insights, prompt) {
|
|
382
381
|
if (!insights || !insights.length) return "";
|
|
383
|
-
const
|
|
384
|
-
return `<div class="card insight-card"${
|
|
382
|
+
const promptAttr = prompt ? ` data-prompt="${esc(prompt)}"` : "";
|
|
383
|
+
return `<div class="card insight-card"${promptAttr}>
|
|
385
384
|
<div class="card-header">
|
|
386
385
|
<h2>Insights <span class="n">${insights.length}</span></h2>
|
|
387
|
-
${
|
|
386
|
+
${prompt ? `<button class="copy-prompt-btn" title="Copy as a prompt for Claude Code">📋 copy as prompt</button>` : ""}
|
|
388
387
|
</div>
|
|
389
388
|
${insights
|
|
390
389
|
.map(
|
|
@@ -437,29 +436,19 @@ export function renderInsightsReportCard(insightsReport) {
|
|
|
437
436
|
}
|
|
438
437
|
|
|
439
438
|
export function renderStatsBar(data) {
|
|
440
|
-
const {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
mcpCount,
|
|
449
|
-
driftCount,
|
|
450
|
-
ccusageData,
|
|
451
|
-
usageAnalytics,
|
|
452
|
-
} = data;
|
|
439
|
+
const { coveragePct, configuredCount, totalRepos, avgHealth, driftCount, ccusageData } = data;
|
|
440
|
+
const driftStat =
|
|
441
|
+
driftCount > 0
|
|
442
|
+
? `<div class="stat" data-nav="repos" data-section="repo-grid" title="View drifting repos" style="border-color:#f8717133"><b style="color:var(--red)">${driftCount}</b><span>Drifting Repos</span></div>`
|
|
443
|
+
: "";
|
|
444
|
+
const spendStat = ccusageData
|
|
445
|
+
? `<div class="stat" data-nav="analytics" data-section="section-activity" title="View analytics" style="border-color:#4ade8033"><b style="color:var(--green)">$${Math.round(Number(ccusageData.totals.totalCost) || 0).toLocaleString()}</b><span>Total Spent</span></div>`
|
|
446
|
+
: "";
|
|
453
447
|
return `<div class="stats">
|
|
454
448
|
<div class="stat coverage" data-nav="repos" data-section="repo-grid" title="View repos"><b>${coveragePct}%</b><span>Coverage (${configuredCount}/${totalRepos})</span></div>
|
|
455
449
|
<div class="stat" data-nav="repos" data-section="repo-grid" title="View repos" style="${avgHealth >= 70 ? "border-color:#4ade8033" : avgHealth >= 40 ? "border-color:#fbbf2433" : "border-color:#f8717133"}"><b style="color:${healthScoreColor(avgHealth)}">${avgHealth}</b><span>Avg Health</span></div>
|
|
456
|
-
<div class="stat" data-nav="
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
${mcpCount > 0 ? `<div class="stat" data-nav="skills-mcp" data-section="section-mcp" title="View MCP servers"><b>${mcpCount}</b><span>MCP Servers</span></div>` : ""}
|
|
460
|
-
${driftCount > 0 ? `<div class="stat" data-nav="repos" data-section="repo-grid" title="View drifting repos" style="border-color:#f8717133"><b style="color:var(--red)">${driftCount}</b><span>Drifting Repos</span></div>` : ""}
|
|
461
|
-
${ccusageData ? `<div class="stat" data-nav="analytics" data-section="section-activity" title="View analytics" style="border-color:#4ade8033"><b style="color:var(--green)">$${Math.round(Number(ccusageData.totals.totalCost) || 0).toLocaleString()}</b><span>Total Spent</span></div>` : ""}
|
|
462
|
-
${ccusageData ? `<div class="stat" data-nav="analytics" data-section="section-activity" title="View analytics"><b>${formatTokens(ccusageData.totals.totalTokens).replace(" tokens", "")}</b><span>Total Tokens</span></div>` : ""}
|
|
463
|
-
${usageAnalytics.heavySessions > 0 ? `<div class="stat" data-nav="analytics" data-section="section-activity" title="View analytics"><b>${usageAnalytics.heavySessions}</b><span>Heavy Sessions</span></div>` : ""}
|
|
450
|
+
<div class="stat" data-nav="repos" data-section="repo-grid" title="View repos"><b>${totalRepos}</b><span>Repos</span></div>
|
|
451
|
+
${driftStat}
|
|
452
|
+
${spendStat}
|
|
464
453
|
</div>`;
|
|
465
454
|
}
|
package/template/dashboard.css
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
--surface2: #1a1a1a;
|
|
5
5
|
--border: #262626;
|
|
6
6
|
--text: #e5e5e5;
|
|
7
|
-
--text-dim: #
|
|
7
|
+
--text-dim: #999;
|
|
8
8
|
--accent: #c4956a;
|
|
9
|
-
--accent-dim: #
|
|
9
|
+
--accent-dim: #b08a60;
|
|
10
10
|
--green: #4ade80;
|
|
11
11
|
--blue: #60a5fa;
|
|
12
12
|
--purple: #a78bfa;
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
--surface2: #f0f0f0;
|
|
20
20
|
--border: #e0e0e0;
|
|
21
21
|
--text: #1a1a1a;
|
|
22
|
-
--text-dim: #
|
|
22
|
+
--text-dim: #555;
|
|
23
23
|
--accent: #9b6b47;
|
|
24
24
|
--accent-dim: #b8956e;
|
|
25
25
|
--green: #16a34a;
|
|
@@ -643,7 +643,7 @@ details.cmd-detail > summary::-webkit-details-marker {
|
|
|
643
643
|
.card-header h2 {
|
|
644
644
|
margin-bottom: 0;
|
|
645
645
|
}
|
|
646
|
-
.copy-
|
|
646
|
+
.copy-prompt-btn {
|
|
647
647
|
background: var(--surface2);
|
|
648
648
|
border: 1px solid var(--border);
|
|
649
649
|
border-radius: 5px;
|
|
@@ -656,7 +656,7 @@ details.cmd-detail > summary::-webkit-details-marker {
|
|
|
656
656
|
color 0.15s;
|
|
657
657
|
white-space: nowrap;
|
|
658
658
|
}
|
|
659
|
-
.copy-
|
|
659
|
+
.copy-prompt-btn:hover {
|
|
660
660
|
border-color: var(--accent-dim);
|
|
661
661
|
color: var(--text);
|
|
662
662
|
}
|
|
@@ -797,7 +797,8 @@ details.cmd-detail > summary::-webkit-details-marker {
|
|
|
797
797
|
margin-top: 0.5rem;
|
|
798
798
|
font-size: 0.72rem;
|
|
799
799
|
color: var(--accent);
|
|
800
|
-
text-decoration:
|
|
800
|
+
text-decoration: underline;
|
|
801
|
+
text-underline-offset: 2px;
|
|
801
802
|
}
|
|
802
803
|
.report-link:hover {
|
|
803
804
|
text-decoration: underline;
|
|
@@ -1253,7 +1254,7 @@ details.cmd-detail > summary::-webkit-details-marker {
|
|
|
1253
1254
|
}
|
|
1254
1255
|
.unconfigured-item .upath {
|
|
1255
1256
|
font-size: 0.6rem;
|
|
1256
|
-
color:
|
|
1257
|
+
color: var(--text-dim);
|
|
1257
1258
|
display: block;
|
|
1258
1259
|
overflow: hidden;
|
|
1259
1260
|
text-overflow: ellipsis;
|
package/template/dashboard.html
CHANGED
|
@@ -13,20 +13,21 @@
|
|
|
13
13
|
|
|
14
14
|
<!-- {{STATS_BAR}} -->
|
|
15
15
|
|
|
16
|
+
<main>
|
|
16
17
|
<nav class="tab-nav">
|
|
17
|
-
<button class="tab-btn active" data-tab="
|
|
18
|
-
<button class="tab-btn" data-tab="
|
|
18
|
+
<button class="tab-btn active" data-tab="home">Home</button>
|
|
19
|
+
<button class="tab-btn" data-tab="config">Config</button>
|
|
19
20
|
<button class="tab-btn" data-tab="analytics">Analytics</button>
|
|
20
21
|
<button class="tab-btn" data-tab="repos">Repos</button>
|
|
21
22
|
<button class="tab-btn" data-tab="reference">Reference</button>
|
|
22
23
|
</nav>
|
|
23
24
|
|
|
24
|
-
<div class="tab-content active" id="tab-
|
|
25
|
-
<!-- {{
|
|
25
|
+
<div class="tab-content active" id="tab-home">
|
|
26
|
+
<!-- {{TAB_HOME}} -->
|
|
26
27
|
</div>
|
|
27
28
|
|
|
28
|
-
<div class="tab-content" id="tab-
|
|
29
|
-
<!-- {{
|
|
29
|
+
<div class="tab-content" id="tab-config">
|
|
30
|
+
<!-- {{TAB_CONFIG}} -->
|
|
30
31
|
</div>
|
|
31
32
|
|
|
32
33
|
<div class="tab-content" id="tab-analytics">
|
|
@@ -42,6 +43,7 @@
|
|
|
42
43
|
</div>
|
|
43
44
|
|
|
44
45
|
<!-- {{FOOTER}} -->
|
|
46
|
+
</main>
|
|
45
47
|
|
|
46
48
|
<div class="chart-tooltip" id="chart-tooltip"></div>
|
|
47
49
|
<script>
|
package/template/dashboard.js
CHANGED
|
@@ -133,19 +133,19 @@ function showToast(msg) {
|
|
|
133
133
|
}, 2000);
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
// ── Copy
|
|
137
|
-
document.querySelectorAll(".copy-
|
|
136
|
+
// ── Copy as Prompt button ────────────────────────────────────
|
|
137
|
+
document.querySelectorAll(".copy-prompt-btn").forEach(function (btn) {
|
|
138
138
|
btn.addEventListener("click", function () {
|
|
139
|
-
var card = btn.closest("[data-
|
|
139
|
+
var card = btn.closest("[data-prompt]");
|
|
140
140
|
if (!card) return;
|
|
141
|
-
var
|
|
141
|
+
var prompt = card.dataset.prompt;
|
|
142
142
|
navigator.clipboard
|
|
143
|
-
.writeText(
|
|
143
|
+
.writeText(prompt)
|
|
144
144
|
.then(function () {
|
|
145
|
-
showToast("
|
|
145
|
+
showToast("Prompt copied \u2014 paste into Claude Code");
|
|
146
146
|
})
|
|
147
147
|
.catch(function () {
|
|
148
|
-
showToast("Copy failed \u2014
|
|
148
|
+
showToast("Copy failed \u2014 try selecting the text manually");
|
|
149
149
|
});
|
|
150
150
|
});
|
|
151
151
|
});
|