@viren/claude-code-dashboard 0.0.1 → 0.0.3
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 +33 -0
- package/generate-dashboard.mjs +96 -13
- package/package.json +1 -1
- package/src/anonymize.mjs +222 -0
- package/src/cli.mjs +14 -5
- package/src/constants.mjs +4 -1
- package/src/demo.mjs +490 -0
- package/src/html-template.mjs +425 -355
- package/src/mcp.mjs +105 -14
- package/src/render.mjs +9 -0
package/README.md
CHANGED
|
@@ -1,9 +1,42 @@
|
|
|
1
1
|
# claude-code-dashboard
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@viren/claude-code-dashboard)
|
|
4
|
+
[](https://github.com/VirenMohindra/claude-code-dashboard/actions/workflows/ci.yml)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](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
|
+

|
|
17
|
+
|
|
18
|
+
### Skills with auto-categorization and MCP server discovery
|
|
19
|
+
|
|
20
|
+

|
|
21
|
+
|
|
22
|
+
### Usage analytics — tool usage, languages, activity heatmap
|
|
23
|
+
|
|
24
|
+

|
|
25
|
+
|
|
26
|
+
### Repo cards with search, grouping, and consolidation hints
|
|
27
|
+
|
|
28
|
+

|
|
29
|
+
|
|
30
|
+
### Expanded repo — commands, rules, health score, matched skills
|
|
31
|
+
|
|
32
|
+

|
|
33
|
+
|
|
34
|
+
### Dark mode
|
|
35
|
+
|
|
36
|
+

|
|
37
|
+
|
|
38
|
+
> Screenshots generated with `claude-code-dashboard --demo`
|
|
39
|
+
|
|
7
40
|
## Features
|
|
8
41
|
|
|
9
42
|
### Core
|
package/generate-dashboard.mjs
CHANGED
|
@@ -20,9 +20,20 @@ import { execFileSync, execFile } from "child_process";
|
|
|
20
20
|
import { readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync } from "fs";
|
|
21
21
|
import { join, basename, dirname } from "path";
|
|
22
22
|
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
VERSION,
|
|
25
|
+
HOME,
|
|
26
|
+
CLAUDE_DIR,
|
|
27
|
+
DEFAULT_OUTPUT,
|
|
28
|
+
CONF,
|
|
29
|
+
MAX_DEPTH,
|
|
30
|
+
REPO_URL,
|
|
31
|
+
SIMILARITY_THRESHOLD,
|
|
32
|
+
} from "./src/constants.mjs";
|
|
24
33
|
import { parseArgs, generateCompletions } from "./src/cli.mjs";
|
|
25
|
-
import { shortPath
|
|
34
|
+
import { shortPath } from "./src/helpers.mjs";
|
|
35
|
+
import { anonymizeAll } from "./src/anonymize.mjs";
|
|
36
|
+
import { generateDemoData } from "./src/demo.mjs";
|
|
26
37
|
import { findGitRepos, getScanRoots } from "./src/discovery.mjs";
|
|
27
38
|
import { extractProjectDesc, extractSections, scanMdDir } from "./src/markdown.mjs";
|
|
28
39
|
import { scanSkillsDir, groupSkillsByCategory } from "./src/skills.mjs";
|
|
@@ -44,6 +55,7 @@ import {
|
|
|
44
55
|
parseProjectMcpConfig,
|
|
45
56
|
findPromotionCandidates,
|
|
46
57
|
scanHistoricalMcpServers,
|
|
58
|
+
classifyHistoricalServers,
|
|
47
59
|
} from "./src/mcp.mjs";
|
|
48
60
|
import { aggregateSessionMeta } from "./src/usage.mjs";
|
|
49
61
|
import { handleInit } from "./src/templates.mjs";
|
|
@@ -58,6 +70,33 @@ const cliArgs = parseArgs(process.argv);
|
|
|
58
70
|
if (cliArgs.completions) generateCompletions();
|
|
59
71
|
if (cliArgs.command === "init") handleInit(cliArgs);
|
|
60
72
|
|
|
73
|
+
// ── Demo Mode ────────────────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
if (cliArgs.demo) {
|
|
76
|
+
const demoData = generateDemoData();
|
|
77
|
+
const html = generateDashboardHtml(demoData);
|
|
78
|
+
|
|
79
|
+
const outputPath = cliArgs.output;
|
|
80
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
81
|
+
writeFileSync(outputPath, html);
|
|
82
|
+
|
|
83
|
+
if (!cliArgs.quiet) {
|
|
84
|
+
const sp = shortPath(outputPath);
|
|
85
|
+
console.log(`\n claude-code-dashboard v${VERSION} (demo mode)\n`);
|
|
86
|
+
console.log(` ✓ ${sp}`);
|
|
87
|
+
if (cliArgs.open) console.log(` ✓ opening in browser`);
|
|
88
|
+
console.log(`\n ${REPO_URL}`);
|
|
89
|
+
console.log();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (cliArgs.open) {
|
|
93
|
+
const cmd =
|
|
94
|
+
process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
95
|
+
execFile(cmd, [outputPath]);
|
|
96
|
+
}
|
|
97
|
+
process.exit(0);
|
|
98
|
+
}
|
|
99
|
+
|
|
61
100
|
// ── Collect Everything ───────────────────────────────────────────────────────
|
|
62
101
|
|
|
63
102
|
const scanRoots = getScanRoots();
|
|
@@ -164,7 +203,7 @@ for (const repo of configured) {
|
|
|
164
203
|
const similar = configured
|
|
165
204
|
.filter((r) => r !== repo)
|
|
166
205
|
.map((r) => ({ name: r.name, similarity: computeConfigSimilarity(repo, r) }))
|
|
167
|
-
.filter((r) => r.similarity >=
|
|
206
|
+
.filter((r) => r.similarity >= SIMILARITY_THRESHOLD)
|
|
168
207
|
.sort((a, b) => b.similarity - a.similarity)
|
|
169
208
|
.slice(0, 2);
|
|
170
209
|
repo.similarRepos = similar;
|
|
@@ -296,19 +335,41 @@ for (const s of allMcpServers) {
|
|
|
296
335
|
for (const entry of Object.values(mcpByName)) {
|
|
297
336
|
entry.disabledIn = disabledByServer[entry.name] || 0;
|
|
298
337
|
}
|
|
338
|
+
|
|
339
|
+
const historicalMcpMap = scanHistoricalMcpServers(CLAUDE_DIR);
|
|
340
|
+
const currentMcpNames = new Set(allMcpServers.map((s) => s.name));
|
|
341
|
+
const { recent: recentMcpServers, former: formerMcpServers } = classifyHistoricalServers(
|
|
342
|
+
historicalMcpMap,
|
|
343
|
+
currentMcpNames,
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
// Normalize all historical project paths
|
|
347
|
+
for (const server of [...recentMcpServers, ...formerMcpServers]) {
|
|
348
|
+
server.projects = server.projects.map((p) => shortPath(p));
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Merge recently-seen servers into allMcpServers so they show up as current
|
|
352
|
+
for (const server of recentMcpServers) {
|
|
353
|
+
if (!mcpByName[server.name]) {
|
|
354
|
+
mcpByName[server.name] = {
|
|
355
|
+
name: server.name,
|
|
356
|
+
type: "unknown",
|
|
357
|
+
projects: server.projects,
|
|
358
|
+
userLevel: false,
|
|
359
|
+
disabledIn: disabledByServer[server.name] || 0,
|
|
360
|
+
recentlyActive: true,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
}
|
|
299
364
|
const mcpSummary = Object.values(mcpByName).sort((a, b) => {
|
|
300
365
|
if (a.userLevel !== b.userLevel) return a.userLevel ? -1 : 1;
|
|
301
366
|
return a.name.localeCompare(b.name);
|
|
302
367
|
});
|
|
303
368
|
const mcpCount = mcpSummary.length;
|
|
304
369
|
|
|
305
|
-
const historicalMcpNames = scanHistoricalMcpServers(CLAUDE_DIR);
|
|
306
|
-
const currentMcpNames = new Set(allMcpServers.map((s) => s.name));
|
|
307
|
-
const formerMcpServers = historicalMcpNames.filter((name) => !currentMcpNames.has(name)).sort();
|
|
308
|
-
|
|
309
370
|
// ── Usage Analytics ──────────────────────────────────────────────────────────
|
|
310
371
|
|
|
311
|
-
const SESSION_META_LIMIT =
|
|
372
|
+
const SESSION_META_LIMIT = 1000;
|
|
312
373
|
const sessionMetaDir = join(CLAUDE_DIR, "usage-data", "session-meta");
|
|
313
374
|
const sessionMetaFiles = [];
|
|
314
375
|
if (existsSync(sessionMetaDir)) {
|
|
@@ -479,10 +540,18 @@ if (cliArgs.diff) {
|
|
|
479
540
|
// ── Anonymize ────────────────────────────────────────────────────────────────
|
|
480
541
|
|
|
481
542
|
if (cliArgs.anonymize) {
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
543
|
+
anonymizeAll({
|
|
544
|
+
configured,
|
|
545
|
+
unconfigured,
|
|
546
|
+
globalCmds,
|
|
547
|
+
globalRules,
|
|
548
|
+
globalSkills,
|
|
549
|
+
chains,
|
|
550
|
+
mcpSummary,
|
|
551
|
+
mcpPromotions,
|
|
552
|
+
formerMcpServers,
|
|
553
|
+
consolidationGroups,
|
|
554
|
+
});
|
|
486
555
|
}
|
|
487
556
|
|
|
488
557
|
// ── JSON Output ──────────────────────────────────────────────────────────────
|
|
@@ -622,7 +691,21 @@ const html = generateDashboardHtml({
|
|
|
622
691
|
const outputPath = cliArgs.output;
|
|
623
692
|
mkdirSync(dirname(outputPath), { recursive: true });
|
|
624
693
|
writeFileSync(outputPath, html);
|
|
625
|
-
|
|
694
|
+
|
|
695
|
+
if (!cliArgs.quiet) {
|
|
696
|
+
const sp = shortPath(outputPath);
|
|
697
|
+
console.log(`\n claude-code-dashboard v${VERSION}\n`);
|
|
698
|
+
console.log(
|
|
699
|
+
` ${configuredCount} configured · ${unconfiguredCount} unconfigured · ${totalRepos} repos`,
|
|
700
|
+
);
|
|
701
|
+
console.log(
|
|
702
|
+
` ${globalCmds.length} global commands · ${globalSkills.length} skills · ${mcpCount} MCP servers`,
|
|
703
|
+
);
|
|
704
|
+
console.log(`\n ✓ ${sp}`);
|
|
705
|
+
if (cliArgs.open) console.log(` ✓ opening in browser`);
|
|
706
|
+
console.log(`\n ${REPO_URL}`);
|
|
707
|
+
console.log();
|
|
708
|
+
}
|
|
626
709
|
|
|
627
710
|
if (cliArgs.open) {
|
|
628
711
|
const cmd =
|
package/package.json
CHANGED
|
@@ -0,0 +1,222 @@
|
|
|
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 and projects
|
|
209
|
+
for (let i = 0; i < formerMcpServers.length; i++) {
|
|
210
|
+
formerMcpServers[i] = {
|
|
211
|
+
name: `former-server-${i + 1}`,
|
|
212
|
+
projects: (formerMcpServers[i].projects || []).map(() => `~/project-${i + 1}`),
|
|
213
|
+
lastSeen: formerMcpServers[i].lastSeen,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Consolidation groups
|
|
218
|
+
for (const group of consolidationGroups) {
|
|
219
|
+
group.repos = (group.repos || []).map((n) => mapName(nameMap, n));
|
|
220
|
+
group.suggestion = `${group.repos.length} ${group.stack} repos with ${group.avgSimilarity}% avg similarity — consider shared global rules`;
|
|
221
|
+
}
|
|
222
|
+
}
|
package/src/cli.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { VERSION, DEFAULT_OUTPUT, HOME } from "./constants.mjs";
|
|
|
3
3
|
export function parseArgs(argv) {
|
|
4
4
|
const args = {
|
|
5
5
|
output: DEFAULT_OUTPUT,
|
|
6
|
-
open: false,
|
|
6
|
+
open: process.stdout.isTTY !== false,
|
|
7
7
|
json: false,
|
|
8
8
|
catalog: false,
|
|
9
9
|
command: null,
|
|
@@ -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
|
|
@@ -39,11 +40,13 @@ Options:
|
|
|
39
40
|
--output, -o <path> Output path (default: ~/.claude/dashboard.html)
|
|
40
41
|
--json Output full data model as JSON instead of HTML
|
|
41
42
|
--catalog Generate a shareable skill catalog HTML page
|
|
42
|
-
--open Open
|
|
43
|
+
--open Open in browser after generating (default: true)
|
|
44
|
+
--no-open Skip opening in browser
|
|
43
45
|
--quiet Suppress output, just write file
|
|
44
46
|
--watch Regenerate on file changes
|
|
45
47
|
--diff Show changes since last generation
|
|
46
|
-
--anonymize Anonymize
|
|
48
|
+
--anonymize Anonymize all data for shareable export
|
|
49
|
+
--demo Generate dashboard with sample data (no scanning)
|
|
47
50
|
--completions Output shell completion script for bash/zsh
|
|
48
51
|
--version, -v Show version
|
|
49
52
|
--help, -h Show this help
|
|
@@ -84,6 +87,9 @@ Config file: ~/.claude/dashboard.conf
|
|
|
84
87
|
case "--open":
|
|
85
88
|
args.open = true;
|
|
86
89
|
break;
|
|
90
|
+
case "--no-open":
|
|
91
|
+
args.open = false;
|
|
92
|
+
break;
|
|
87
93
|
case "--template":
|
|
88
94
|
case "-t":
|
|
89
95
|
args.template = argv[++i];
|
|
@@ -107,6 +113,9 @@ Config file: ~/.claude/dashboard.conf
|
|
|
107
113
|
case "--anonymize":
|
|
108
114
|
args.anonymize = true;
|
|
109
115
|
break;
|
|
116
|
+
case "--demo":
|
|
117
|
+
args.demo = true;
|
|
118
|
+
break;
|
|
110
119
|
case "--completions":
|
|
111
120
|
args.completions = true;
|
|
112
121
|
break;
|
|
@@ -124,11 +133,11 @@ export function generateCompletions() {
|
|
|
124
133
|
# eval "$(claude-code-dashboard --completions)"
|
|
125
134
|
if [ -n "$ZSH_VERSION" ]; then
|
|
126
135
|
_claude_code_dashboard() {
|
|
127
|
-
local -a opts; opts=(init lint --output --open --json --catalog --quiet --watch --diff --anonymize --completions --help --version)
|
|
136
|
+
local -a opts; opts=(init lint --output --open --no-open --json --catalog --quiet --watch --diff --anonymize --demo --completions --help --version)
|
|
128
137
|
if (( CURRENT == 2 )); then _describe 'option' opts; fi
|
|
129
138
|
}; compdef _claude_code_dashboard claude-code-dashboard
|
|
130
139
|
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]}") ); }
|
|
140
|
+
_claude_code_dashboard() { COMPREPLY=( $(compgen -W "init lint --output --open --no-open --json --catalog --quiet --watch --diff --anonymize --demo --completions --help --version" -- "\${COMP_WORDS[COMP_CWORD]}") ); }
|
|
132
141
|
complete -F _claude_code_dashboard claude-code-dashboard
|
|
133
142
|
fi`);
|
|
134
143
|
process.exit(0);
|
package/src/constants.mjs
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { join } from "path";
|
|
2
2
|
import { homedir } from "os";
|
|
3
3
|
|
|
4
|
-
export const VERSION = "0.0.
|
|
4
|
+
export const VERSION = "0.0.3";
|
|
5
|
+
export const REPO_URL = "https://github.com/VirenMohindra/claude-code-dashboard";
|
|
5
6
|
|
|
6
7
|
export const HOME = homedir();
|
|
7
8
|
export const CLAUDE_DIR = join(HOME, ".claude");
|
|
8
9
|
export const DEFAULT_OUTPUT = join(CLAUDE_DIR, "dashboard.html");
|
|
9
10
|
export const CONF = join(CLAUDE_DIR, "dashboard.conf");
|
|
10
11
|
export const MAX_DEPTH = 5;
|
|
12
|
+
export const MAX_SESSION_SCAN = 1000;
|
|
13
|
+
export const SIMILARITY_THRESHOLD = 25;
|
|
11
14
|
|
|
12
15
|
// Freshness thresholds (seconds)
|
|
13
16
|
export const ONE_DAY = 86_400;
|