@viren/claude-code-dashboard 0.0.4 → 0.0.6
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 +12 -2
- package/generate-dashboard.mjs +1 -1
- package/package.json +4 -3
- package/src/assembler.mjs +154 -0
- package/src/constants.mjs +1 -1
- package/src/helpers.mjs +21 -0
- package/src/render.mjs +0 -4
- package/src/sections.mjs +417 -0
- package/template/dashboard.css +1320 -0
- package/template/dashboard.html +51 -0
- package/template/dashboard.js +198 -0
- package/src/html-template.mjs +0 -946
package/README.md
CHANGED
|
@@ -31,9 +31,9 @@ Scans your home directory for git repos, collects Claude Code configuration (com
|
|
|
31
31
|
|
|
32
32
|

|
|
33
33
|
|
|
34
|
-
###
|
|
34
|
+
### Light mode
|
|
35
35
|
|
|
36
|
-

|
|
37
37
|
|
|
38
38
|
> Screenshots generated with `claude-code-dashboard --demo`
|
|
39
39
|
|
|
@@ -212,6 +212,16 @@ If no directories are listed, the entire home directory is scanned (depth 5).
|
|
|
212
212
|
- Node.js 18+
|
|
213
213
|
- Git (for freshness timestamps and drift detection)
|
|
214
214
|
|
|
215
|
+
## Roadmap
|
|
216
|
+
|
|
217
|
+
Completed: v0.1 (foundation), v0.2 (intelligence layer), v0.3 (recommendations engine), v0.4 (config templates), v0.5 (control center).
|
|
218
|
+
|
|
219
|
+
**Up next:**
|
|
220
|
+
|
|
221
|
+
- [ ] Org-wide dashboard — scan multiple users' configs for team visibility
|
|
222
|
+
|
|
223
|
+
See [issues](https://github.com/VirenMohindra/claude-code-dashboard/issues) for feature requests.
|
|
224
|
+
|
|
215
225
|
## Privacy
|
|
216
226
|
|
|
217
227
|
The generated HTML file contains:
|
package/generate-dashboard.mjs
CHANGED
|
@@ -60,7 +60,7 @@ import {
|
|
|
60
60
|
import { aggregateSessionMeta } from "./src/usage.mjs";
|
|
61
61
|
import { handleInit } from "./src/templates.mjs";
|
|
62
62
|
import { generateCatalogHtml } from "./src/render.mjs";
|
|
63
|
-
import { generateDashboardHtml } from "./src/
|
|
63
|
+
import { generateDashboardHtml } from "./src/assembler.mjs";
|
|
64
64
|
import { startWatch } from "./src/watch.mjs";
|
|
65
65
|
|
|
66
66
|
// ── CLI ──────────────────────────────────────────────────────────────────────
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@viren/claude-code-dashboard",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "A visual dashboard for your Claude Code configuration across all repos",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"generate": "node generate-dashboard.mjs",
|
|
11
|
-
"test": "node --test test
|
|
11
|
+
"test": "node --test test/*.test.mjs",
|
|
12
12
|
"lint": "eslint .",
|
|
13
13
|
"lint:fix": "eslint . --fix",
|
|
14
14
|
"format": "prettier --write .",
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
},
|
|
34
34
|
"files": [
|
|
35
35
|
"generate-dashboard.mjs",
|
|
36
|
-
"src/"
|
|
36
|
+
"src/",
|
|
37
|
+
"template/"
|
|
37
38
|
],
|
|
38
39
|
"devDependencies": {
|
|
39
40
|
"eslint": "^9.0.0",
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
3
|
+
import { dirname, join } from "path";
|
|
4
|
+
|
|
5
|
+
import { esc, insightsToMarkdown } from "./helpers.mjs";
|
|
6
|
+
import { VERSION, REPO_URL } from "./constants.mjs";
|
|
7
|
+
import { renderCmd, renderRule, renderRepoCard } from "./render.mjs";
|
|
8
|
+
import {
|
|
9
|
+
renderSkillsCard,
|
|
10
|
+
renderMcpCard,
|
|
11
|
+
renderToolsCard,
|
|
12
|
+
renderLangsCard,
|
|
13
|
+
renderErrorsCard,
|
|
14
|
+
renderActivityCard,
|
|
15
|
+
renderChainsCard,
|
|
16
|
+
renderConsolidationCard,
|
|
17
|
+
renderUnconfiguredCard,
|
|
18
|
+
renderReferenceCard,
|
|
19
|
+
renderInsightsCard,
|
|
20
|
+
renderInsightsReportCard,
|
|
21
|
+
renderStatsBar,
|
|
22
|
+
} from "./sections.mjs";
|
|
23
|
+
|
|
24
|
+
// Resolve template directory relative to this module (works when installed via npm too)
|
|
25
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
const TEMPLATE_DIR = join(__dirname, "..", "template");
|
|
27
|
+
|
|
28
|
+
// Cache template files (read once per process).
|
|
29
|
+
// Assumes one-shot CLI usage; watch mode spawns fresh processes.
|
|
30
|
+
let _css, _js, _html;
|
|
31
|
+
function loadTemplates() {
|
|
32
|
+
if (!_css) _css = readFileSync(join(TEMPLATE_DIR, "dashboard.css"), "utf8");
|
|
33
|
+
if (!_js) _js = readFileSync(join(TEMPLATE_DIR, "dashboard.js"), "utf8");
|
|
34
|
+
if (!_html) _html = readFileSync(join(TEMPLATE_DIR, "dashboard.html"), "utf8");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function generateDashboardHtml(data) {
|
|
38
|
+
loadTemplates();
|
|
39
|
+
|
|
40
|
+
const {
|
|
41
|
+
configured,
|
|
42
|
+
unconfigured,
|
|
43
|
+
globalCmds,
|
|
44
|
+
globalRules,
|
|
45
|
+
globalSkills,
|
|
46
|
+
chains,
|
|
47
|
+
mcpSummary,
|
|
48
|
+
mcpPromotions,
|
|
49
|
+
formerMcpServers,
|
|
50
|
+
consolidationGroups,
|
|
51
|
+
usageAnalytics,
|
|
52
|
+
ccusageData,
|
|
53
|
+
statsCache,
|
|
54
|
+
timestamp,
|
|
55
|
+
coveragePct,
|
|
56
|
+
totalRepos,
|
|
57
|
+
configuredCount,
|
|
58
|
+
unconfiguredCount,
|
|
59
|
+
scanScope,
|
|
60
|
+
insights,
|
|
61
|
+
insightsReport,
|
|
62
|
+
} = data;
|
|
63
|
+
|
|
64
|
+
// ── Build section HTML fragments ──────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
const header = `<h1>claude code dashboard</h1>
|
|
67
|
+
<div class="header-actions">
|
|
68
|
+
<button id="refresh-btn" class="header-btn" title="Copy refresh command to clipboard" aria-label="Copy refresh command">↻ refresh</button>
|
|
69
|
+
<button id="theme-toggle" class="theme-toggle" title="Toggle light/dark mode" aria-label="Toggle theme"><span class="theme-icon"></span></button>
|
|
70
|
+
</div>
|
|
71
|
+
<p class="sub">generated ${timestamp} · <a href="${esc(REPO_URL)}" target="_blank" rel="noopener" style="color:var(--accent);text-decoration:none">v${esc(VERSION)}</a></p>`;
|
|
72
|
+
|
|
73
|
+
const statsBar = renderStatsBar(data);
|
|
74
|
+
|
|
75
|
+
// Overview tab
|
|
76
|
+
const overviewCommands = `<div class="top-grid">
|
|
77
|
+
<div class="card" id="section-commands" style="margin-bottom:0">
|
|
78
|
+
<h2>Global Commands <span class="n">${globalCmds.length}</span></h2>
|
|
79
|
+
${globalCmds.map((c) => renderCmd(c)).join("\n ")}
|
|
80
|
+
</div>
|
|
81
|
+
<div class="card" style="margin-bottom:0">
|
|
82
|
+
<h2>Global Rules <span class="n">${globalRules.length}</span></h2>
|
|
83
|
+
${globalRules.map((r) => renderRule(r)).join("\n ")}
|
|
84
|
+
</div>
|
|
85
|
+
</div>`;
|
|
86
|
+
const insightsMarkdown = insightsToMarkdown(insights);
|
|
87
|
+
const insightsHtml = renderInsightsCard(insights, insightsMarkdown);
|
|
88
|
+
const chainsHtml = renderChainsCard(chains);
|
|
89
|
+
const consolidationHtml = renderConsolidationCard(consolidationGroups);
|
|
90
|
+
const tabOverview = `${overviewCommands}\n ${insightsHtml}\n ${chainsHtml}\n ${consolidationHtml}`;
|
|
91
|
+
|
|
92
|
+
// Skills & MCP tab
|
|
93
|
+
const tabSkillsMcp = `${renderSkillsCard(globalSkills)}\n ${renderMcpCard(mcpSummary, mcpPromotions, formerMcpServers)}`;
|
|
94
|
+
|
|
95
|
+
// Analytics tab
|
|
96
|
+
const insightsReportHtml = renderInsightsReportCard(insightsReport);
|
|
97
|
+
const toolsHtml = renderToolsCard(usageAnalytics.topTools);
|
|
98
|
+
const langsHtml = renderLangsCard(usageAnalytics.topLanguages);
|
|
99
|
+
const errorsHtml = renderErrorsCard(usageAnalytics.errorCategories);
|
|
100
|
+
const activityHtml = renderActivityCard(statsCache, ccusageData);
|
|
101
|
+
const tabAnalytics = `${insightsReportHtml}
|
|
102
|
+
<div class="top-grid">
|
|
103
|
+
${toolsHtml || ""}
|
|
104
|
+
${langsHtml || ""}
|
|
105
|
+
</div>
|
|
106
|
+
${errorsHtml}
|
|
107
|
+
${activityHtml}`;
|
|
108
|
+
|
|
109
|
+
// Repos tab
|
|
110
|
+
const repoCards = configured.map((r) => renderRepoCard(r)).join("\n");
|
|
111
|
+
const unconfiguredHtml = renderUnconfiguredCard(unconfigured);
|
|
112
|
+
const tabRepos = `<div class="search-bar">
|
|
113
|
+
<input type="text" id="search" placeholder="search repos..." autocomplete="off">
|
|
114
|
+
<span class="search-hint"><kbd>/</kbd></span>
|
|
115
|
+
</div>
|
|
116
|
+
<div class="group-controls">
|
|
117
|
+
<label class="group-label">Group by:</label>
|
|
118
|
+
<select id="group-by" class="group-select">
|
|
119
|
+
<option value="none">None</option>
|
|
120
|
+
<option value="stack">Tech Stack</option>
|
|
121
|
+
<option value="parent">Parent Directory</option>
|
|
122
|
+
</select>
|
|
123
|
+
</div>
|
|
124
|
+
<div class="repo-grid" id="repo-grid">
|
|
125
|
+
${repoCards}
|
|
126
|
+
</div>
|
|
127
|
+
${unconfiguredHtml}`;
|
|
128
|
+
|
|
129
|
+
// Reference tab
|
|
130
|
+
const tabReference = renderReferenceCard();
|
|
131
|
+
|
|
132
|
+
// Footer
|
|
133
|
+
const footer = `<div class="ts">found ${totalRepos} repos · ${configuredCount} configured · ${unconfiguredCount} unconfigured · scanned ${scanScope} · ${timestamp}</div>`;
|
|
134
|
+
|
|
135
|
+
// ── Inject dynamic coverage color via CSS custom property ─────────────────
|
|
136
|
+
const coverageColor =
|
|
137
|
+
coveragePct >= 70 ? "var(--green)" : coveragePct >= 40 ? "var(--yellow)" : "var(--red)";
|
|
138
|
+
const css = `:root { --coverage-color: ${coverageColor}; }\n${_css}`;
|
|
139
|
+
|
|
140
|
+
// ── Assemble final HTML via placeholder replacement ───────────────────────
|
|
141
|
+
let html = _html;
|
|
142
|
+
html = html.replace("<!-- {{CSS}} -->", css);
|
|
143
|
+
html = html.replace("/* {{JS}} */", _js);
|
|
144
|
+
html = html.replace("<!-- {{HEADER}} -->", header);
|
|
145
|
+
html = html.replace("<!-- {{STATS_BAR}} -->", statsBar);
|
|
146
|
+
html = html.replace("<!-- {{TAB_OVERVIEW}} -->", tabOverview);
|
|
147
|
+
html = html.replace("<!-- {{TAB_SKILLS_MCP}} -->", tabSkillsMcp);
|
|
148
|
+
html = html.replace("<!-- {{TAB_ANALYTICS}} -->", tabAnalytics);
|
|
149
|
+
html = html.replace("<!-- {{TAB_REPOS}} -->", tabRepos);
|
|
150
|
+
html = html.replace("<!-- {{TAB_REFERENCE}} -->", tabReference);
|
|
151
|
+
html = html.replace("<!-- {{FOOTER}} -->", footer);
|
|
152
|
+
|
|
153
|
+
return html;
|
|
154
|
+
}
|
package/src/constants.mjs
CHANGED
package/src/helpers.mjs
CHANGED
|
@@ -33,6 +33,27 @@ export function gitCmd(repoDir, ...args) {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
const INSIGHT_ICONS = {
|
|
37
|
+
warning: "\u26A0\uFE0F",
|
|
38
|
+
tip: "\u2728",
|
|
39
|
+
promote: "\u2B06",
|
|
40
|
+
info: "\u2139\uFE0F",
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** Convert an insights array to a markdown string suitable for pasting into Claude Code. */
|
|
44
|
+
export function insightsToMarkdown(insights) {
|
|
45
|
+
if (!insights || !insights.length) return "";
|
|
46
|
+
const lines = ["# Dashboard Insights\n"];
|
|
47
|
+
for (const i of insights) {
|
|
48
|
+
const icon = INSIGHT_ICONS[i.type] || INSIGHT_ICONS.info;
|
|
49
|
+
lines.push(`## ${icon} ${i.title}`);
|
|
50
|
+
if (i.detail) lines.push(i.detail);
|
|
51
|
+
if (i.action) lines.push(`**Action:** ${i.action}`);
|
|
52
|
+
lines.push("");
|
|
53
|
+
}
|
|
54
|
+
return lines.join("\n");
|
|
55
|
+
}
|
|
56
|
+
|
|
36
57
|
export function anonymizePath(p) {
|
|
37
58
|
return p
|
|
38
59
|
.replace(/^\/Users\/[^/]+\//, "~/")
|
package/src/render.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { esc } from "./helpers.mjs";
|
|
2
2
|
import { extractSteps, extractSections } from "./markdown.mjs";
|
|
3
|
-
import { groupSkillsByCategory } from "./skills.mjs";
|
|
4
3
|
|
|
5
4
|
export function renderSections(sections) {
|
|
6
5
|
return sections
|
|
@@ -58,9 +57,6 @@ export function renderSkill(skill) {
|
|
|
58
57
|
return `<div class="cmd-row"><span class="cmd-name skill-name">${esc(skill.name)}</span>${badge}<span class="cmd-desc">${d}</span></div>`;
|
|
59
58
|
}
|
|
60
59
|
|
|
61
|
-
// Re-export from skills.mjs (single source of truth)
|
|
62
|
-
export { groupSkillsByCategory };
|
|
63
|
-
|
|
64
60
|
export function renderBadges(repo) {
|
|
65
61
|
const b = [];
|
|
66
62
|
if (repo.commands.length) b.push(`<span class="badge cmds">${repo.commands.length} cmd</span>`);
|