@viren/claude-code-dashboard 0.0.1 → 0.0.2

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
@@ -1,9 +1,42 @@
1
1
  # claude-code-dashboard
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/@viren/claude-code-dashboard)](https://www.npmjs.com/package/@viren/claude-code-dashboard)
4
+ [![CI](https://github.com/VirenMohindra/claude-code-dashboard/actions/workflows/ci.yml/badge.svg)](https://github.com/VirenMohindra/claude-code-dashboard/actions/workflows/ci.yml)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](https://nodejs.org)
7
+
3
8
  A visual dashboard for your [Claude Code](https://docs.anthropic.com/en/docs/claude-code) configuration across all repos.
4
9
 
5
10
  Scans your home directory for git repos, collects Claude Code configuration (commands, rules, skills, MCP servers, usage data), and generates a self-contained HTML dashboard.
6
11
 
12
+ ## Screenshots
13
+
14
+ ### Dashboard overview — stats, global commands, and rules
15
+
16
+ ![Overview](screenshots/01-overview.png)
17
+
18
+ ### Skills with auto-categorization and MCP server discovery
19
+
20
+ ![Skills and MCP](screenshots/02-skills-mcp.png)
21
+
22
+ ### Usage analytics — tool usage, languages, activity heatmap
23
+
24
+ ![Usage Analytics](screenshots/03-usage-analytics.png)
25
+
26
+ ### Repo cards with search, grouping, and consolidation hints
27
+
28
+ ![Repo Cards](screenshots/04-repo-cards.png)
29
+
30
+ ### Expanded repo — commands, rules, health score, matched skills
31
+
32
+ ![Repo Expanded](screenshots/05-repo-expanded.png)
33
+
34
+ ### Dark mode
35
+
36
+ ![Dark Mode](screenshots/06-light-mode.png)
37
+
38
+ > Screenshots generated with `claude-code-dashboard --demo`
39
+
7
40
  ## Features
8
41
 
9
42
  ### Core
@@ -22,7 +22,9 @@ import { join, basename, dirname } from "path";
22
22
 
23
23
  import { VERSION, HOME, CLAUDE_DIR, DEFAULT_OUTPUT, CONF, MAX_DEPTH } from "./src/constants.mjs";
24
24
  import { parseArgs, generateCompletions } from "./src/cli.mjs";
25
- import { shortPath, anonymizePath } from "./src/helpers.mjs";
25
+ import { shortPath } from "./src/helpers.mjs";
26
+ import { anonymizeAll } from "./src/anonymize.mjs";
27
+ import { generateDemoData } from "./src/demo.mjs";
26
28
  import { findGitRepos, getScanRoots } from "./src/discovery.mjs";
27
29
  import { extractProjectDesc, extractSections, scanMdDir } from "./src/markdown.mjs";
28
30
  import { scanSkillsDir, groupSkillsByCategory } from "./src/skills.mjs";
@@ -58,6 +60,25 @@ const cliArgs = parseArgs(process.argv);
58
60
  if (cliArgs.completions) generateCompletions();
59
61
  if (cliArgs.command === "init") handleInit(cliArgs);
60
62
 
63
+ // ── Demo Mode ────────────────────────────────────────────────────────────────
64
+
65
+ if (cliArgs.demo) {
66
+ const demoData = generateDemoData();
67
+ const html = generateDashboardHtml(demoData);
68
+
69
+ const outputPath = cliArgs.output;
70
+ mkdirSync(dirname(outputPath), { recursive: true });
71
+ writeFileSync(outputPath, html);
72
+ if (!cliArgs.quiet) console.log(outputPath);
73
+
74
+ if (cliArgs.open) {
75
+ const cmd =
76
+ process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
77
+ execFile(cmd, [outputPath]);
78
+ }
79
+ process.exit(0);
80
+ }
81
+
61
82
  // ── Collect Everything ───────────────────────────────────────────────────────
62
83
 
63
84
  const scanRoots = getScanRoots();
@@ -479,10 +500,18 @@ if (cliArgs.diff) {
479
500
  // ── Anonymize ────────────────────────────────────────────────────────────────
480
501
 
481
502
  if (cliArgs.anonymize) {
482
- for (const repo of [...configured, ...unconfigured]) {
483
- repo.shortPath = anonymizePath(repo.shortPath);
484
- repo.path = anonymizePath(repo.path);
485
- }
503
+ anonymizeAll({
504
+ configured,
505
+ unconfigured,
506
+ globalCmds,
507
+ globalRules,
508
+ globalSkills,
509
+ chains,
510
+ mcpSummary,
511
+ mcpPromotions,
512
+ formerMcpServers,
513
+ consolidationGroups,
514
+ });
486
515
  }
487
516
 
488
517
  // ── JSON Output ──────────────────────────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viren/claude-code-dashboard",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "A visual dashboard for your Claude Code configuration across all repos",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Deep anonymization for --anonymize flag.
3
+ *
4
+ * Builds a stable name map (real name -> generic label) and applies it
5
+ * across every data structure that ends up in the HTML output.
6
+ */
7
+
8
+ const REPO_LABELS = [
9
+ "web-app",
10
+ "api-service",
11
+ "mobile-app",
12
+ "data-pipeline",
13
+ "admin-panel",
14
+ "shared-lib",
15
+ "cli-tool",
16
+ "docs-site",
17
+ "auth-service",
18
+ "analytics-engine",
19
+ "worker-queue",
20
+ "config-store",
21
+ "gateway",
22
+ "scheduler",
23
+ "notification-svc",
24
+ "search-index",
25
+ "media-service",
26
+ "payment-gateway",
27
+ "user-service",
28
+ "reporting-tool",
29
+ "internal-dashboard",
30
+ "test-harness",
31
+ "deploy-scripts",
32
+ "design-system",
33
+ "content-api",
34
+ "ml-pipeline",
35
+ "event-bus",
36
+ "cache-layer",
37
+ "log-aggregator",
38
+ "infra-tools",
39
+ ];
40
+
41
+ const PERSON_RE = /\b[A-Z][a-z]+\s[A-Z][a-z]+\b/g;
42
+ const GITHUB_HANDLE_RE = /@[a-zA-Z0-9][-a-zA-Z0-9]{0,38}\b/g;
43
+
44
+ function anonymizePath(p) {
45
+ return p
46
+ .replace(/^\/Users\/[^/]+\//, "~/")
47
+ .replace(/^\/home\/[^/]+\//, "~/")
48
+ .replace(/^C:\\Users\\[^\\]+\\/, "~\\")
49
+ .replace(/^C:\/Users\/[^/]+\//, "~/");
50
+ }
51
+
52
+ function buildNameMap(configured, unconfigured) {
53
+ const map = new Map();
54
+ let idx = 0;
55
+ for (const repo of [...configured, ...unconfigured]) {
56
+ if (!map.has(repo.name)) {
57
+ const label = idx < REPO_LABELS.length ? REPO_LABELS[idx] : `project-${idx + 1}`;
58
+ map.set(repo.name, label);
59
+ idx++;
60
+ }
61
+ }
62
+
63
+ // Extract username from home dir paths to anonymize it too
64
+ for (const repo of [...configured, ...unconfigured]) {
65
+ const m = repo.path.match(/^\/(?:Users|home)\/([^/]+)\//);
66
+ if (m && m[1] && !map.has(m[1])) {
67
+ map.set(m[1], "user");
68
+ break;
69
+ }
70
+ }
71
+
72
+ return map;
73
+ }
74
+
75
+ function mapName(nameMap, name) {
76
+ return nameMap.get(name) || name;
77
+ }
78
+
79
+ /**
80
+ * Replace person names, GitHub handles, and all known repo names in text.
81
+ * Uses case-insensitive replacement to catch "Salsa", "salsa", "SALSA" etc.
82
+ */
83
+ function anonymizeText(text, nameMap) {
84
+ let result = text.replace(PERSON_RE, "[name]").replace(GITHUB_HANDLE_RE, "@[user]");
85
+ if (nameMap) {
86
+ const sorted = [...nameMap.entries()].sort((a, b) => b[0].length - a[0].length);
87
+ for (const [real, anon] of sorted) {
88
+ if (real.length < 3) continue;
89
+ const re = new RegExp(escapeRegex(real), "gi");
90
+ result = result.replace(re, anon);
91
+ }
92
+ }
93
+ return result;
94
+ }
95
+
96
+ function escapeRegex(s) {
97
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
98
+ }
99
+
100
+ function anonymizeMdItems(items) {
101
+ return items.map((item, i) => ({
102
+ ...item,
103
+ name: `item-${String(i + 1).padStart(2, "0")}`,
104
+ desc: "...",
105
+ filepath: "", // prevent render functions from re-reading file content
106
+ }));
107
+ }
108
+
109
+ /**
110
+ * Anonymize all data structures in-place before HTML generation.
111
+ */
112
+ export function anonymizeAll({
113
+ configured,
114
+ unconfigured,
115
+ globalCmds,
116
+ globalRules,
117
+ globalSkills,
118
+ chains,
119
+ mcpSummary,
120
+ mcpPromotions,
121
+ formerMcpServers,
122
+ consolidationGroups,
123
+ }) {
124
+ const nameMap = buildNameMap(configured, unconfigured);
125
+
126
+ // Repos
127
+ for (const repo of [...configured, ...unconfigured]) {
128
+ const anonName = mapName(nameMap, repo.name);
129
+ repo.name = anonName;
130
+ repo.key = anonName;
131
+ repo.path = anonymizePath(repo.path).replace(/[^/]+$/, anonName);
132
+ repo.shortPath = anonymizePath(repo.shortPath).replace(/[^/]+$/, anonName);
133
+
134
+ // Description — redact entirely (contains arbitrary project-specific text)
135
+ repo.desc = repo.desc?.length ? ["[project description redacted]"] : [];
136
+
137
+ // Sections — keep headings (anonymized), redact preview content
138
+ for (const section of repo.sections || []) {
139
+ section.name = anonymizeText(section.name, nameMap);
140
+ section.preview = ["..."];
141
+ }
142
+
143
+ // Commands & rules
144
+ repo.commands = anonymizeMdItems(repo.commands || []);
145
+ repo.rules = anonymizeMdItems(repo.rules || []);
146
+
147
+ // Similar repos
148
+ repo.similarRepos = (repo.similarRepos || []).map((r) => ({
149
+ ...r,
150
+ name: mapName(nameMap, r.name),
151
+ }));
152
+
153
+ // Exemplar name
154
+ if (repo.exemplarName) {
155
+ repo.exemplarName = mapName(nameMap, repo.exemplarName);
156
+ }
157
+
158
+ // Suggestions text — may reference exemplar name
159
+ if (repo.suggestions) {
160
+ repo.suggestions = repo.suggestions.map((s) => anonymizeText(s, nameMap));
161
+ }
162
+
163
+ // MCP servers per repo
164
+ for (const mcp of repo.mcpServers || []) {
165
+ if (mcp.source) {
166
+ const repoName = mcp.source.split("/").pop();
167
+ const anonPath = anonymizePath(mcp.source);
168
+ mcp.source = anonPath.replace(/[^/]+$/, mapName(nameMap, repoName));
169
+ }
170
+ }
171
+ }
172
+
173
+ // Global commands, rules
174
+ globalCmds.splice(0, globalCmds.length, ...anonymizeMdItems(globalCmds));
175
+ globalRules.splice(0, globalRules.length, ...anonymizeMdItems(globalRules));
176
+
177
+ // Global skills — anonymize name, redact description + filepath
178
+ for (let i = 0; i < globalSkills.length; i++) {
179
+ const skill = globalSkills[i];
180
+ skill.name = `skill-${String(i + 1).padStart(2, "0")}`;
181
+ skill.desc = "...";
182
+ skill.filepath = "";
183
+ }
184
+
185
+ // Chains — anonymize node names (may have extra text like "name (backend)")
186
+ for (const chain of chains) {
187
+ chain.nodes = chain.nodes.map((n) => anonymizeText(n.trim(), nameMap));
188
+ }
189
+
190
+ // MCP summary — anonymize project paths
191
+ for (const mcp of mcpSummary) {
192
+ mcp.projects = (mcp.projects || []).map((p) => {
193
+ const anonPath = anonymizePath(p);
194
+ const repoName = p.split("/").pop();
195
+ return anonPath.replace(/[^/]+$/, mapName(nameMap, repoName));
196
+ });
197
+ }
198
+
199
+ // MCP promotions — anonymize project paths
200
+ for (const promo of mcpPromotions) {
201
+ promo.projects = (promo.projects || []).map((p) => {
202
+ const anonPath = anonymizePath(p);
203
+ const repoName = p.split("/").pop();
204
+ return anonPath.replace(/[^/]+$/, mapName(nameMap, repoName));
205
+ });
206
+ }
207
+
208
+ // Former MCP servers — anonymize names
209
+ for (let i = 0; i < formerMcpServers.length; i++) {
210
+ formerMcpServers[i] = `former-server-${i + 1}`;
211
+ }
212
+
213
+ // Consolidation groups
214
+ for (const group of consolidationGroups) {
215
+ group.repos = (group.repos || []).map((n) => mapName(nameMap, n));
216
+ group.suggestion = `${group.repos.length} ${group.stack} repos with ${group.avgSimilarity}% avg similarity — consider shared global rules`;
217
+ }
218
+ }
package/src/cli.mjs CHANGED
@@ -13,6 +13,7 @@ export function parseArgs(argv) {
13
13
  watch: false,
14
14
  diff: false,
15
15
  anonymize: false,
16
+ demo: false,
16
17
  completions: false,
17
18
  };
18
19
  let i = 2; // skip node + script
@@ -43,7 +44,8 @@ Options:
43
44
  --quiet Suppress output, just write file
44
45
  --watch Regenerate on file changes
45
46
  --diff Show changes since last generation
46
- --anonymize Anonymize paths for shareable export
47
+ --anonymize Anonymize all data for shareable export
48
+ --demo Generate dashboard with sample data (no scanning)
47
49
  --completions Output shell completion script for bash/zsh
48
50
  --version, -v Show version
49
51
  --help, -h Show this help
@@ -107,6 +109,9 @@ Config file: ~/.claude/dashboard.conf
107
109
  case "--anonymize":
108
110
  args.anonymize = true;
109
111
  break;
112
+ case "--demo":
113
+ args.demo = true;
114
+ break;
110
115
  case "--completions":
111
116
  args.completions = true;
112
117
  break;
@@ -124,11 +129,11 @@ export function generateCompletions() {
124
129
  # eval "$(claude-code-dashboard --completions)"
125
130
  if [ -n "$ZSH_VERSION" ]; then
126
131
  _claude_code_dashboard() {
127
- local -a opts; opts=(init lint --output --open --json --catalog --quiet --watch --diff --anonymize --completions --help --version)
132
+ local -a opts; opts=(init lint --output --open --json --catalog --quiet --watch --diff --anonymize --demo --completions --help --version)
128
133
  if (( CURRENT == 2 )); then _describe 'option' opts; fi
129
134
  }; compdef _claude_code_dashboard claude-code-dashboard
130
135
  elif [ -n "$BASH_VERSION" ]; then
131
- _claude_code_dashboard() { COMPREPLY=( $(compgen -W "init lint --output --open --json --catalog --quiet --watch --diff --anonymize --completions --help --version" -- "\${COMP_WORDS[COMP_CWORD]}") ); }
136
+ _claude_code_dashboard() { COMPREPLY=( $(compgen -W "init lint --output --open --json --catalog --quiet --watch --diff --anonymize --demo --completions --help --version" -- "\${COMP_WORDS[COMP_CWORD]}") ); }
132
137
  complete -F _claude_code_dashboard claude-code-dashboard
133
138
  fi`);
134
139
  process.exit(0);
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.1";
4
+ export const VERSION = "0.0.2";
5
5
 
6
6
  export const HOME = homedir();
7
7
  export const CLAUDE_DIR = join(HOME, ".claude");
package/src/demo.mjs ADDED
@@ -0,0 +1,479 @@
1
+ /**
2
+ * Generate realistic fake data for --demo flag.
3
+ * Produces a complete data object ready for generateDashboardHtml().
4
+ */
5
+
6
+ function daysAgo(n) {
7
+ return Math.floor(Date.now() / 1000) - n * 86400;
8
+ }
9
+
10
+ const DEMO_CONFIGURED = [
11
+ {
12
+ key: "acme-web",
13
+ name: "acme-web",
14
+ path: "~/work/acme-web",
15
+ shortPath: "~/work/acme-web",
16
+ commands: [
17
+ { name: "dev", desc: "Start dev server with hot reload", filepath: "" },
18
+ { name: "test", desc: "Run vitest suite with coverage", filepath: "" },
19
+ { name: "deploy", desc: "Deploy to staging via Vercel", filepath: "" },
20
+ { name: "lint", desc: "Run ESLint + Prettier check", filepath: "" },
21
+ ],
22
+ rules: [
23
+ { name: "architecture", desc: "App router conventions and component patterns", filepath: "" },
24
+ {
25
+ name: "testing",
26
+ desc: "Test behavior not implementation, use MSW for mocks",
27
+ filepath: "",
28
+ },
29
+ { name: "styling", desc: "Tailwind utility-first, no inline styles", filepath: "" },
30
+ ],
31
+ desc: [
32
+ "Customer-facing web application built with Next.js 15 and React Server Components.",
33
+ "Uses Supabase for auth and database, deployed on Vercel.",
34
+ ],
35
+ sections: [
36
+ {
37
+ name: "Architecture",
38
+ preview: [
39
+ "App router with RSC",
40
+ "Shared components in /ui",
41
+ "Feature modules in /features",
42
+ ],
43
+ },
44
+ {
45
+ name: "Testing",
46
+ preview: ["Vitest for unit tests", "Playwright for E2E", "MSW for API mocking"],
47
+ },
48
+ {
49
+ name: "Deployment",
50
+ preview: ["Preview deploys on PRs", "Staging auto-deploy from main"],
51
+ },
52
+ ],
53
+ freshness: daysAgo(3),
54
+ freshnessText: "3 days ago",
55
+ freshnessClass: "fresh",
56
+ techStack: ["next", "react"],
57
+ healthScore: 95,
58
+ healthReasons: ["Has CLAUDE.md", "Has commands", "Has rules", "Recently updated"],
59
+ hasAgentsFile: true,
60
+ configPattern: "modular",
61
+ drift: { level: "synced", commitsSince: 0 },
62
+ similarRepos: [{ name: "marketing-site", similarity: 72 }],
63
+ matchedSkills: [{ name: "e2e-test" }, { name: "react-doctor" }],
64
+ mcpServers: [
65
+ { name: "playwright", type: "stdio", scope: "project", source: "~/work/acme-web" },
66
+ ],
67
+ },
68
+ {
69
+ key: "payments-api",
70
+ name: "payments-api",
71
+ path: "~/work/payments-api",
72
+ shortPath: "~/work/payments-api",
73
+ commands: [
74
+ { name: "test", desc: "Run pytest with coverage", filepath: "" },
75
+ { name: "migrate", desc: "Run alembic migrations", filepath: "" },
76
+ { name: "serve", desc: "Start FastAPI dev server", filepath: "" },
77
+ ],
78
+ rules: [
79
+ { name: "security", desc: "Input validation on all endpoints, no raw SQL", filepath: "" },
80
+ {
81
+ name: "error-handling",
82
+ desc: "Structured errors with codes, never expose internals",
83
+ filepath: "",
84
+ },
85
+ ],
86
+ desc: ["Payment processing API handling Stripe integration and subscription management."],
87
+ sections: [
88
+ {
89
+ name: "Security",
90
+ preview: ["Validate all inputs with Pydantic", "Rate limiting on sensitive endpoints"],
91
+ },
92
+ {
93
+ name: "Database",
94
+ preview: ["PostgreSQL via SQLAlchemy", "Alembic for migrations"],
95
+ },
96
+ ],
97
+ freshness: daysAgo(12),
98
+ freshnessText: "12 days ago",
99
+ freshnessClass: "fresh",
100
+ techStack: ["python"],
101
+ healthScore: 80,
102
+ healthReasons: ["Has CLAUDE.md", "Has commands", "Has rules"],
103
+ hasAgentsFile: true,
104
+ configPattern: "modular",
105
+ drift: { level: "low", commitsSince: 4 },
106
+ similarRepos: [],
107
+ matchedSkills: [{ name: "systematic-debugging" }],
108
+ mcpServers: [],
109
+ },
110
+ {
111
+ key: "mobile-app",
112
+ name: "mobile-app",
113
+ path: "~/work/mobile-app",
114
+ shortPath: "~/work/mobile-app",
115
+ commands: [
116
+ { name: "ios", desc: "Run on iOS simulator", filepath: "" },
117
+ { name: "android", desc: "Run on Android emulator", filepath: "" },
118
+ { name: "test", desc: "Run Jest test suite", filepath: "" },
119
+ ],
120
+ rules: [
121
+ { name: "navigation", desc: "React Navigation v7 patterns and deep linking", filepath: "" },
122
+ ],
123
+ desc: ["Cross-platform mobile app built with Expo and React Native."],
124
+ sections: [
125
+ {
126
+ name: "Navigation",
127
+ preview: ["File-based routing via expo-router", "Deep link config in app.json"],
128
+ },
129
+ ],
130
+ freshness: daysAgo(45),
131
+ freshnessText: "1 month ago",
132
+ freshnessClass: "aging",
133
+ techStack: ["expo", "react"],
134
+ healthScore: 60,
135
+ healthReasons: ["Has CLAUDE.md", "Has commands"],
136
+ hasAgentsFile: true,
137
+ configPattern: "monolithic",
138
+ drift: { level: "medium", commitsSince: 18 },
139
+ similarRepos: [{ name: "acme-web", similarity: 45 }],
140
+ matchedSkills: [{ name: "react-doctor" }],
141
+ mcpServers: [],
142
+ },
143
+ {
144
+ key: "infra-tools",
145
+ name: "infra-tools",
146
+ path: "~/work/infra-tools",
147
+ shortPath: "~/work/infra-tools",
148
+ commands: [
149
+ { name: "build", desc: "Build all Go binaries", filepath: "" },
150
+ { name: "test", desc: "Run go test ./...", filepath: "" },
151
+ ],
152
+ rules: [],
153
+ desc: ["Internal CLI tools for infrastructure automation."],
154
+ sections: [{ name: "Build", preview: ["Go 1.22", "Multi-binary workspace layout"] }],
155
+ freshness: daysAgo(90),
156
+ freshnessText: "3 months ago",
157
+ freshnessClass: "stale",
158
+ techStack: ["go"],
159
+ healthScore: 40,
160
+ healthReasons: ["Has CLAUDE.md", "Has commands"],
161
+ hasAgentsFile: true,
162
+ configPattern: "minimal",
163
+ drift: { level: "high", commitsSince: 34 },
164
+ similarRepos: [],
165
+ matchedSkills: [],
166
+ mcpServers: [],
167
+ },
168
+ {
169
+ key: "marketing-site",
170
+ name: "marketing-site",
171
+ path: "~/work/marketing-site",
172
+ shortPath: "~/work/marketing-site",
173
+ commands: [
174
+ { name: "dev", desc: "Start Next.js dev server", filepath: "" },
175
+ { name: "build", desc: "Build static export", filepath: "" },
176
+ ],
177
+ rules: [
178
+ { name: "content", desc: "All copy comes from CMS, never hardcode text", filepath: "" },
179
+ ],
180
+ desc: ["Public marketing website with blog and documentation."],
181
+ sections: [
182
+ { name: "Content", preview: ["MDX for blog posts", "Contentlayer for type-safe content"] },
183
+ ],
184
+ freshness: daysAgo(7),
185
+ freshnessText: "1 week ago",
186
+ freshnessClass: "fresh",
187
+ techStack: ["next"],
188
+ healthScore: 70,
189
+ healthReasons: ["Has CLAUDE.md", "Has commands", "Has rules"],
190
+ hasAgentsFile: true,
191
+ configPattern: "modular",
192
+ drift: { level: "low", commitsSince: 2 },
193
+ similarRepos: [{ name: "acme-web", similarity: 68 }],
194
+ matchedSkills: [{ name: "e2e-test" }],
195
+ mcpServers: [],
196
+ },
197
+ {
198
+ key: "shared-ui",
199
+ name: "shared-ui",
200
+ path: "~/work/shared-ui",
201
+ shortPath: "~/work/shared-ui",
202
+ commands: [
203
+ { name: "storybook", desc: "Start Storybook dev server", filepath: "" },
204
+ { name: "build", desc: "Build and publish to internal registry", filepath: "" },
205
+ ],
206
+ rules: [
207
+ { name: "components", desc: "All components must have stories and a11y tests", filepath: "" },
208
+ ],
209
+ desc: ["Shared component library used across web projects."],
210
+ sections: [
211
+ {
212
+ name: "Components",
213
+ preview: ["Radix primitives", "Tailwind variants", "Storybook for documentation"],
214
+ },
215
+ ],
216
+ freshness: daysAgo(14),
217
+ freshnessText: "2 weeks ago",
218
+ freshnessClass: "fresh",
219
+ techStack: ["react"],
220
+ healthScore: 75,
221
+ healthReasons: ["Has CLAUDE.md", "Has commands", "Has rules"],
222
+ hasAgentsFile: true,
223
+ configPattern: "modular",
224
+ drift: { level: "synced", commitsSince: 0 },
225
+ similarRepos: [{ name: "acme-web", similarity: 55 }],
226
+ matchedSkills: [],
227
+ mcpServers: [],
228
+ },
229
+ ];
230
+
231
+ const DEMO_UNCONFIGURED = [
232
+ {
233
+ key: "data-scripts",
234
+ name: "data-scripts",
235
+ path: "~/work/data-scripts",
236
+ shortPath: "~/work/data-scripts",
237
+ techStack: ["python"],
238
+ suggestions: ["Add CLAUDE.md with project overview", "Add commands for common tasks"],
239
+ exemplarName: "payments-api",
240
+ mcpServers: [],
241
+ },
242
+ {
243
+ key: "legacy-admin",
244
+ name: "legacy-admin",
245
+ path: "~/work/legacy-admin",
246
+ shortPath: "~/work/legacy-admin",
247
+ techStack: ["react"],
248
+ suggestions: ["Add CLAUDE.md", "Add architecture rules"],
249
+ exemplarName: "acme-web",
250
+ mcpServers: [],
251
+ },
252
+ {
253
+ key: "ops-runbooks",
254
+ name: "ops-runbooks",
255
+ path: "~/work/ops-runbooks",
256
+ shortPath: "~/work/ops-runbooks",
257
+ techStack: [],
258
+ suggestions: [],
259
+ exemplarName: "",
260
+ mcpServers: [],
261
+ },
262
+ ];
263
+
264
+ const DEMO_GLOBAL_CMDS = [
265
+ { name: "commit", desc: "Stage changes and create a conventional commit", filepath: "" },
266
+ { name: "review-pr", desc: "Fetch PR diff and perform code review", filepath: "" },
267
+ { name: "dashboard", desc: "Generate and open the config dashboard", filepath: "" },
268
+ { name: "test-all", desc: "Run full test suite with coverage report", filepath: "" },
269
+ ];
270
+
271
+ const DEMO_GLOBAL_RULES = [
272
+ { name: "git-workflow", desc: "Branch naming, commit conventions, PR process", filepath: "" },
273
+ {
274
+ name: "code-standards",
275
+ desc: "Architecture principles, error handling, quality gates",
276
+ filepath: "",
277
+ },
278
+ {
279
+ name: "communication",
280
+ desc: "Slack message formatting, draft-before-send policy",
281
+ filepath: "",
282
+ },
283
+ ];
284
+
285
+ const DEMO_GLOBAL_SKILLS = [
286
+ {
287
+ name: "e2e-test",
288
+ desc: "Run Playwright E2E tests against a branch",
289
+ filepath: "",
290
+ source: { type: "custom" },
291
+ category: "code-quality",
292
+ },
293
+ {
294
+ name: "react-doctor",
295
+ desc: "Catch common React bugs and performance issues",
296
+ filepath: "",
297
+ source: { type: "skills.sh", repo: "community/react-tools" },
298
+ category: "debugging",
299
+ },
300
+ {
301
+ name: "systematic-debugging",
302
+ desc: "Structured root cause analysis with diagnostic plans",
303
+ filepath: "",
304
+ source: { type: "superpowers", repo: "obra/superpowers-skills" },
305
+ category: "debugging",
306
+ },
307
+ {
308
+ name: "writing-plans",
309
+ desc: "Create implementation plans from specs before coding",
310
+ filepath: "",
311
+ source: { type: "superpowers", repo: "obra/superpowers-skills" },
312
+ category: "workflow",
313
+ },
314
+ {
315
+ name: "code-review",
316
+ desc: "Review PR diffs for correctness, style, and security",
317
+ filepath: "",
318
+ source: { type: "superpowers", repo: "obra/superpowers-skills" },
319
+ category: "code-quality",
320
+ },
321
+ {
322
+ name: "find-session",
323
+ desc: "Search past Claude Code sessions by keyword or date",
324
+ filepath: "",
325
+ source: { type: "custom" },
326
+ category: "research",
327
+ },
328
+ {
329
+ name: "slack-digest",
330
+ desc: "Extract action items and decisions from Slack threads",
331
+ filepath: "",
332
+ source: { type: "custom" },
333
+ category: "integrations",
334
+ },
335
+ {
336
+ name: "deploy-staging",
337
+ desc: "Deploy current branch to staging environment",
338
+ filepath: "",
339
+ source: { type: "custom" },
340
+ category: "workflow",
341
+ },
342
+ ];
343
+
344
+ function generateDemoHeatmap() {
345
+ const days = [];
346
+ const now = new Date();
347
+ for (let i = 364; i >= 0; i--) {
348
+ const d = new Date(now);
349
+ d.setDate(d.getDate() - i);
350
+ const date = d.toISOString().slice(0, 10);
351
+ // Weighted random: more activity on weekdays
352
+ const isWeekday = d.getDay() > 0 && d.getDay() < 6;
353
+ const base = isWeekday ? 8 : 2;
354
+ const messageCount = Math.floor(Math.random() * base * 3);
355
+ if (messageCount > 0) days.push({ date, messageCount });
356
+ }
357
+ return days;
358
+ }
359
+
360
+ function generateDemoHourCounts() {
361
+ const counts = {};
362
+ // Peak at 10am and 2pm
363
+ for (let h = 0; h < 24; h++) {
364
+ const peak = Math.exp(-((h - 10) ** 2) / 18) + Math.exp(-((h - 14) ** 2) / 12);
365
+ counts[String(h)] = Math.round(peak * 120 + Math.random() * 20);
366
+ }
367
+ return counts;
368
+ }
369
+
370
+ export function generateDemoData() {
371
+ const now = new Date();
372
+ const timestamp =
373
+ now
374
+ .toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })
375
+ .toLowerCase() +
376
+ " at " +
377
+ now.toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" }).toLowerCase();
378
+
379
+ const configured = DEMO_CONFIGURED;
380
+ const unconfigured = DEMO_UNCONFIGURED;
381
+ const totalRepos = configured.length + unconfigured.length;
382
+
383
+ return {
384
+ configured,
385
+ unconfigured,
386
+ globalCmds: DEMO_GLOBAL_CMDS,
387
+ globalRules: DEMO_GLOBAL_RULES,
388
+ globalSkills: DEMO_GLOBAL_SKILLS,
389
+ chains: [
390
+ { nodes: ["shared-ui", "acme-web", "marketing-site"], arrow: "&rarr;" },
391
+ { nodes: ["payments-api", "acme-web"], arrow: "&rarr;" },
392
+ ],
393
+ mcpSummary: [
394
+ { name: "playwright", type: "stdio", projects: [], userLevel: true, disabledIn: 0 },
395
+ {
396
+ name: "github",
397
+ type: "stdio",
398
+ projects: ["~/work/acme-web", "~/work/payments-api"],
399
+ userLevel: false,
400
+ disabledIn: 0,
401
+ },
402
+ {
403
+ name: "postgres",
404
+ type: "stdio",
405
+ projects: ["~/work/payments-api"],
406
+ userLevel: false,
407
+ disabledIn: 0,
408
+ },
409
+ { name: "sentry", type: "http", projects: [], userLevel: true, disabledIn: 0 },
410
+ ],
411
+ mcpPromotions: [{ name: "github", projects: ["~/work/acme-web", "~/work/payments-api"] }],
412
+ formerMcpServers: ["redis", "datadog"],
413
+ consolidationGroups: [
414
+ {
415
+ stack: "next",
416
+ repos: ["acme-web", "marketing-site"],
417
+ avgSimilarity: 68,
418
+ suggestion: "2 next repos with 68% avg similarity — consider shared global rules",
419
+ },
420
+ {
421
+ stack: "react",
422
+ repos: ["acme-web", "shared-ui", "mobile-app"],
423
+ avgSimilarity: 45,
424
+ suggestion: "3 react repos with 45% avg similarity — consider shared global rules",
425
+ },
426
+ ],
427
+ usageAnalytics: {
428
+ totalSessions: 247,
429
+ topTools: [
430
+ { name: "Read", count: 1842 },
431
+ { name: "Edit", count: 1356 },
432
+ { name: "Bash", count: 987 },
433
+ { name: "Grep", count: 654 },
434
+ { name: "Write", count: 432 },
435
+ { name: "Glob", count: 321 },
436
+ { name: "Agent", count: 198 },
437
+ { name: "WebSearch", count: 87 },
438
+ ],
439
+ topLanguages: [
440
+ { name: "TypeScript", count: 4521 },
441
+ { name: "Python", count: 2103 },
442
+ { name: "JavaScript", count: 1456 },
443
+ { name: "Go", count: 432 },
444
+ { name: "Markdown", count: 321 },
445
+ ],
446
+ errorCategories: [
447
+ { name: "lint_error", count: 45 },
448
+ { name: "type_error", count: 32 },
449
+ { name: "test_failure", count: 28 },
450
+ ],
451
+ heavySessions: 12,
452
+ },
453
+ ccusageData: {
454
+ totals: { totalCost: 47.82, totalTokens: 28_450_000 },
455
+ daily: [],
456
+ },
457
+ statsCache: {
458
+ dailyActivity: generateDemoHeatmap(),
459
+ hourCounts: generateDemoHourCounts(),
460
+ modelUsage: {
461
+ "claude-sonnet-4-6": { inputTokens: 18_200_000, outputTokens: 6_800_000 },
462
+ "claude-haiku-4-5": { inputTokens: 2_400_000, outputTokens: 1_050_000 },
463
+ },
464
+ },
465
+ timestamp,
466
+ coveragePct: Math.round((configured.length / totalRepos) * 100),
467
+ totalRepos,
468
+ configuredCount: configured.length,
469
+ unconfiguredCount: unconfigured.length,
470
+ totalRepoCmds: configured.reduce((sum, r) => sum + r.commands.length, 0),
471
+ avgHealth: Math.round(
472
+ configured.reduce((sum, r) => sum + r.healthScore, 0) / configured.length,
473
+ ),
474
+ driftCount: configured.filter((r) => r.drift.level === "medium" || r.drift.level === "high")
475
+ .length,
476
+ mcpCount: 4,
477
+ scanScope: "~/work (depth 5)",
478
+ };
479
+ }