@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 CHANGED
@@ -31,9 +31,9 @@ Scans your home directory for git repos, collects Claude Code configuration (com
31
31
 
32
32
  ![Repo Expanded](screenshots/05-repo-expanded.png)
33
33
 
34
- ### Dark mode
34
+ ### Light mode
35
35
 
36
- ![Dark Mode](screenshots/06-dark-mode.png)
36
+ ![Light Mode](screenshots/06-light-mode.png)
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:
@@ -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/html-template.mjs";
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.4",
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/helpers.test.mjs",
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">&#8635; 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
@@ -1,7 +1,7 @@
1
1
  import { join } from "path";
2
2
  import { homedir } from "os";
3
3
 
4
- export const VERSION = "0.0.4";
4
+ export const VERSION = "0.0.6";
5
5
  export const REPO_URL = "https://github.com/VirenMohindra/claude-code-dashboard";
6
6
 
7
7
  export const HOME = homedir();
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>`);