cc-skill-audit 1.0.0

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.
Files changed (4) hide show
  1. package/README.md +77 -0
  2. package/cli.mjs +304 -0
  3. package/index.html +160 -0
  4. package/package.json +19 -0
package/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # cc-skill-audit
2
+
3
+ Audit your [Claude Code](https://docs.anthropic.com/en/docs/claude-code) skills: token overhead, usage frequency, and prune candidates.
4
+
5
+ ```
6
+ npx cc-skill-audit
7
+ ```
8
+
9
+ ## What it shows
10
+
11
+ ```
12
+ cc-skill-audit Skill token overhead analyzer
13
+ ────────────────────────────────────────────────────────────
14
+
15
+ Summary
16
+ Skills installed 109
17
+ Index overhead 2.3K tokens (loaded every session)
18
+ Total content 348.6K tokens (loaded on invocation)
19
+ Usage lookback 30 days
20
+
21
+ Top 20 skills by size (30d usage)
22
+ Skill Tokens Use Bar
23
+ ────────────────────────────────────────────────────────────
24
+ writing-skills 21.7K 0 ██████████████
25
+ pptx 14.3K 0 █████████░░░░░
26
+ docx 12.9K 0 ████████░░░░░░
27
+ ...
28
+
29
+ Prune candidates (>5K tokens, 0 uses in 30d)
30
+ Removing these would save ~82.2K tokens when invoked
31
+ ✗ writing-skills 21.7K tokens
32
+ ✗ pptx 14.3K tokens
33
+ ...
34
+
35
+ Active (used in 30d): 45 / 109
36
+ Never used in 30d: 64 skills
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ ```bash
42
+ npx cc-skill-audit # full report (top 20 skills)
43
+ npx cc-skill-audit --top 30 # show top 30 skills by size
44
+ npx cc-skill-audit --days 14 # look back 14 days for usage
45
+ npx cc-skill-audit --json # machine-readable JSON output
46
+ ```
47
+
48
+ ## Why it matters
49
+
50
+ Each skill in `~/.claude/skills/` contributes to:
51
+
52
+ 1. **Index overhead** — The skill list (name + description) loads into every session. More skills = more tokens per session.
53
+ 2. **Invocation overhead** — When you call `/skill-name`, its full content loads into the context window.
54
+
55
+ This tool helps you identify skills that are large but rarely used, so you can archive or remove them to keep your context window clean.
56
+
57
+ ## How it works
58
+
59
+ - Reads skill files from `~/.claude/skills/`
60
+ - Scans session transcripts in `~/.claude/projects/` for skill invocations over the lookback period
61
+ - Ranks skills by size and usage frequency
62
+ - Highlights prune candidates (>5K tokens, 0 uses)
63
+
64
+ Zero dependencies. Works entirely offline.
65
+
66
+ ## Requirements
67
+
68
+ - Node.js 18+
69
+ - Claude Code installed with skills in `~/.claude/skills/`
70
+
71
+ ## Part of cc-toolkit
72
+
73
+ `cc-skill-audit` is part of [cc-toolkit](https://yurukusa.github.io/cc-toolkit/) — a collection of CLI tools for Claude Code power users.
74
+
75
+ ## License
76
+
77
+ MIT
package/cli.mjs ADDED
@@ -0,0 +1,304 @@
1
+ #!/usr/bin/env node
2
+
3
+ // cc-skill-audit — Analyze your Claude Code skills usage and token overhead
4
+ // Zero dependencies. Reads ~/.claude/skills/ + ~/.claude/projects/ transcripts.
5
+ //
6
+ // Shows: skill sizes, usage frequency, prune candidates
7
+ //
8
+ // Usage:
9
+ // npx cc-skill-audit # full report
10
+ // npx cc-skill-audit --top 20 # show top N skills by size
11
+ // npx cc-skill-audit --days 14 # look back N days for usage (default 30)
12
+ // npx cc-skill-audit --json # machine-readable output
13
+
14
+ import { readdir, stat, readFile } from 'node:fs/promises';
15
+ import { createReadStream, existsSync } from 'node:fs';
16
+ import { join, resolve } from 'node:path';
17
+ import { homedir } from 'node:os';
18
+ import { createInterface } from 'node:readline';
19
+
20
+ const SKILLS_DIR = join(homedir(), '.claude', 'skills');
21
+ const PROJECTS_DIR = join(homedir(), '.claude', 'projects');
22
+ const CHARS_PER_TOKEN = 4; // rough approximation
23
+
24
+ const C = {
25
+ reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
26
+ red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m',
27
+ blue: '\x1b[34m', cyan: '\x1b[36m', magenta: '\x1b[35m', white: '\x1b[37m',
28
+ };
29
+
30
+ function pad(s, len, right = false) {
31
+ const t = String(s);
32
+ const p = ' '.repeat(Math.max(0, len - t.length));
33
+ return right ? p + t : t + p;
34
+ }
35
+
36
+ function fmtTokens(n) {
37
+ if (n >= 1000) return (n / 1000).toFixed(1) + 'K';
38
+ return String(n);
39
+ }
40
+
41
+ function tokenBar(tokens, maxTokens, width = 16) {
42
+ const pct = Math.min(1, tokens / maxTokens);
43
+ const fill = Math.round(pct * width);
44
+ const col = pct > 0.5 ? C.red : pct > 0.25 ? C.yellow : C.dim;
45
+ return col + '█'.repeat(fill) + C.reset + C.dim + '░'.repeat(width - fill) + C.reset;
46
+ }
47
+
48
+ // ── Skill discovery ──────────────────────────────────────────────────────────
49
+ async function getSkillSize(skillDir) {
50
+ let totalChars = 0;
51
+ let files = [];
52
+ try {
53
+ files = await readdir(skillDir);
54
+ } catch {
55
+ return 0;
56
+ }
57
+ for (const f of files) {
58
+ if (!f.endsWith('.md') && !f.endsWith('.txt')) continue;
59
+ try {
60
+ const fp = join(skillDir, f);
61
+ const s = await stat(fp);
62
+ totalChars += s.size;
63
+ } catch {}
64
+ }
65
+ return totalChars;
66
+ }
67
+
68
+ async function discoverSkills() {
69
+ const skills = [];
70
+ let entries;
71
+ try {
72
+ entries = await readdir(SKILLS_DIR, { withFileTypes: true });
73
+ } catch {
74
+ return skills;
75
+ }
76
+
77
+ for (const e of entries) {
78
+ if (e.name.startsWith('.')) continue;
79
+ let skillDir;
80
+ if (e.isSymbolicLink()) {
81
+ // resolve symlink
82
+ try {
83
+ const full = join(SKILLS_DIR, e.name);
84
+ const s = await stat(full); // follows symlink
85
+ if (s.isDirectory()) {
86
+ skillDir = full;
87
+ }
88
+ } catch { continue; }
89
+ } else if (e.isDirectory()) {
90
+ skillDir = join(SKILLS_DIR, e.name);
91
+ } else {
92
+ continue;
93
+ }
94
+ const chars = await getSkillSize(skillDir);
95
+ skills.push({
96
+ name: e.name,
97
+ chars,
98
+ tokens: Math.round(chars / CHARS_PER_TOKEN),
99
+ });
100
+ }
101
+ return skills;
102
+ }
103
+
104
+ // ── Index file size (always loaded as skill list) ────────────────────────────
105
+ async function getIndexSize() {
106
+ const indexPath = join(SKILLS_DIR, '.index');
107
+ try {
108
+ const s = await stat(indexPath);
109
+ return s.size;
110
+ } catch {
111
+ return 0;
112
+ }
113
+ }
114
+
115
+ // ── Usage from transcripts ───────────────────────────────────────────────────
116
+ // Look for skill invocations in .jsonl files: patterns like "/skill-name" or
117
+ // "Skill tool" calls with skill names in the content
118
+ async function parseTranscriptForSkills(filePath, skillNames, cutoffMs) {
119
+ const counts = {};
120
+ const rl = createInterface({ input: createReadStream(filePath), crlfDelay: Infinity });
121
+ for await (const line of rl) {
122
+ if (!line.trim()) continue;
123
+ let obj;
124
+ try { obj = JSON.parse(line); } catch { continue; }
125
+
126
+ // Check timestamp cutoff
127
+ const ts = obj.timestamp || obj.created_at;
128
+ if (ts && new Date(ts).getTime() < cutoffMs) continue;
129
+
130
+ // Look for skill invocations in content
131
+ // Pattern 1: Skill tool calls - {"type":"tool_use","name":"Skill","input":{"skill":"skill-name"}}
132
+ // Pattern 2: user messages containing "/skill-name"
133
+ const lineStr = line;
134
+
135
+ for (const sk of skillNames) {
136
+ // Tool call pattern
137
+ if (lineStr.includes('"Skill"') && lineStr.includes(`"${sk}"`)) {
138
+ counts[sk] = (counts[sk] || 0) + 1;
139
+ continue;
140
+ }
141
+ // User command pattern "/${sk}"
142
+ if (lineStr.includes(`"/${sk}"`) || lineStr.includes(`/${sk} `)) {
143
+ counts[sk] = (counts[sk] || 0) + 1;
144
+ }
145
+ }
146
+ }
147
+ return counts;
148
+ }
149
+
150
+ async function collectUsage(skillNames, lookbackDays) {
151
+ const cutoff = Date.now() - lookbackDays * 24 * 60 * 60 * 1000;
152
+ const totalCounts = {};
153
+ for (const sk of skillNames) totalCounts[sk] = 0;
154
+
155
+ try {
156
+ const projects = await readdir(PROJECTS_DIR);
157
+ for (const proj of projects) {
158
+ const dir = join(PROJECTS_DIR, proj);
159
+ let files;
160
+ try { files = await readdir(dir); } catch { continue; }
161
+ for (const f of files) {
162
+ if (!f.endsWith('.jsonl')) continue;
163
+ const fp = join(dir, f);
164
+ // Quick size check — skip tiny files
165
+ try {
166
+ const s = await stat(fp);
167
+ if (s.size < 100) continue;
168
+ // Also check mtime for efficiency
169
+ if (s.mtimeMs < cutoff) continue;
170
+ } catch { continue; }
171
+ const counts = await parseTranscriptForSkills(fp, skillNames, cutoff);
172
+ for (const [sk, c] of Object.entries(counts)) {
173
+ totalCounts[sk] = (totalCounts[sk] || 0) + c;
174
+ }
175
+ }
176
+ }
177
+ } catch {}
178
+ return totalCounts;
179
+ }
180
+
181
+ // ── Render ────────────────────────────────────────────────────────────────────
182
+ function renderReport(skills, usageCounts, indexChars, opts) {
183
+ const { topN, lookbackDays, jsonMode } = opts;
184
+ const indexTokens = Math.round(indexChars / CHARS_PER_TOKEN);
185
+ const totalSkillTokens = skills.reduce((s, sk) => s + sk.tokens, 0);
186
+
187
+ // Enrich with usage
188
+ const enriched = skills.map(sk => ({
189
+ ...sk,
190
+ usage: usageCounts[sk.name] || 0,
191
+ }));
192
+
193
+ if (jsonMode) {
194
+ const out = {
195
+ generated_at: new Date().toISOString(),
196
+ summary: {
197
+ skill_count: skills.length,
198
+ index_tokens: indexTokens,
199
+ total_content_tokens: totalSkillTokens,
200
+ lookback_days: lookbackDays,
201
+ },
202
+ skills: enriched.sort((a, b) => b.tokens - a.tokens),
203
+ };
204
+ console.log(JSON.stringify(out, null, 2));
205
+ return;
206
+ }
207
+
208
+ const W = 60;
209
+ const div = C.dim + '─'.repeat(W) + C.reset;
210
+ const lines = [];
211
+
212
+ lines.push('');
213
+ lines.push(` ${C.bold}cc-skill-audit${C.reset} ${C.dim}Skill token overhead analyzer${C.reset}`);
214
+ lines.push(' ' + div);
215
+ lines.push('');
216
+
217
+ // Summary
218
+ lines.push(` ${C.dim}Summary${C.reset}`);
219
+ lines.push(` Skills installed ${pad(String(skills.length), 6, true)}`);
220
+ lines.push(` Index overhead ${pad(fmtTokens(indexTokens) + ' tokens', 12, true)} ${C.dim}(loaded every session)${C.reset}`);
221
+ lines.push(` Total content ${pad(fmtTokens(totalSkillTokens) + ' tokens', 12, true)} ${C.dim}(loaded on invocation)${C.reset}`);
222
+ lines.push(` Usage lookback ${pad(String(lookbackDays) + ' days', 12, true)}`);
223
+ lines.push('');
224
+
225
+ // Sort by size descending
226
+ const bySize = [...enriched].sort((a, b) => b.tokens - a.tokens);
227
+ const shown = bySize.slice(0, topN);
228
+ const maxTok = shown[0]?.tokens || 1;
229
+
230
+ lines.push(` ${C.dim}Top ${shown.length} skills by size${C.reset} ${C.dim}(${lookbackDays}d usage)${C.reset}`);
231
+ lines.push(` ${C.dim}${'Skill'.padEnd(28)} ${'Tokens'.padStart(7)} ${'Use'.padStart(4)} Bar${C.reset}`);
232
+ lines.push(' ' + div);
233
+
234
+ for (const sk of shown) {
235
+ const usageStr = sk.usage === 0
236
+ ? C.red + pad('0', 4, true) + C.reset
237
+ : C.green + pad(String(sk.usage), 4, true) + C.reset;
238
+ const b = tokenBar(sk.tokens, maxTok, 14);
239
+ const nameCol = sk.tokens > 10000
240
+ ? C.yellow + pad(sk.name, 28) + C.reset
241
+ : pad(sk.name, 28);
242
+ lines.push(` ${nameCol} ${pad(fmtTokens(sk.tokens), 7, true)} ${usageStr} ${b}`);
243
+ }
244
+ lines.push('');
245
+
246
+ // Prune candidates: tokens > 5K AND usage == 0
247
+ const pruneCandidates = enriched
248
+ .filter(sk => sk.tokens > 5000 && sk.usage === 0)
249
+ .sort((a, b) => b.tokens - a.tokens);
250
+
251
+ if (pruneCandidates.length > 0) {
252
+ const savedTokens = pruneCandidates.reduce((s, sk) => s + sk.tokens, 0);
253
+ lines.push(` ${C.yellow}${C.bold}Prune candidates${C.reset} ${C.dim}(>5K tokens, 0 uses in ${lookbackDays}d)${C.reset}`);
254
+ lines.push(` ${C.dim}Removing these would save ~${fmtTokens(savedTokens)} tokens when invoked${C.reset}`);
255
+ for (const sk of pruneCandidates.slice(0, 10)) {
256
+ lines.push(` ${C.red}✗${C.reset} ${pad(sk.name, 30)} ${C.dim}${fmtTokens(sk.tokens)} tokens${C.reset}`);
257
+ }
258
+ lines.push('');
259
+ }
260
+
261
+ // Zero-usage count
262
+ const zeroUsage = enriched.filter(sk => sk.usage === 0);
263
+ const usedCount = enriched.filter(sk => sk.usage > 0).length;
264
+ lines.push(` ${C.dim}Active (used in ${lookbackDays}d):${C.reset} ${C.green}${usedCount}${C.reset} / ${skills.length}`);
265
+ if (zeroUsage.length > 0) {
266
+ lines.push(` ${C.dim}Never used in ${lookbackDays}d:${C.reset} ${C.yellow}${zeroUsage.length}${C.reset} skills`);
267
+ }
268
+ lines.push('');
269
+ lines.push(` ${C.dim}Note: Content tokens only load when a skill is invoked.${C.reset}`);
270
+ lines.push(` ${C.dim}Index tokens load every session (name + description list).${C.reset}`);
271
+ lines.push('');
272
+
273
+ console.log(lines.join('\n'));
274
+ }
275
+
276
+ // ── Main ──────────────────────────────────────────────────────────────────────
277
+ const args = process.argv.slice(2);
278
+ const topIdx = args.indexOf('--top');
279
+ const topN = topIdx >= 0 ? parseInt(args[topIdx + 1]) || 20 : 20;
280
+ const daysIdx = args.indexOf('--days');
281
+ const lookbackDays = daysIdx >= 0 ? parseInt(args[daysIdx + 1]) || 30 : 30;
282
+ const jsonMode = args.includes('--json');
283
+
284
+ (async () => {
285
+ if (!jsonMode) {
286
+ process.stdout.write(C.dim + ' Scanning skills...\r' + C.reset);
287
+ }
288
+ const [skills, indexChars] = await Promise.all([
289
+ discoverSkills(),
290
+ getIndexSize(),
291
+ ]);
292
+
293
+ if (!jsonMode) {
294
+ process.stdout.write(C.dim + ` Analyzing usage (last ${lookbackDays} days)...\r` + C.reset);
295
+ }
296
+ const skillNames = skills.map(s => s.name);
297
+ const usageCounts = await collectUsage(skillNames, lookbackDays);
298
+
299
+ if (!jsonMode) {
300
+ process.stdout.write(' '.repeat(50) + '\r'); // clear progress line
301
+ }
302
+
303
+ renderReport(skills, usageCounts, indexChars, { topN, lookbackDays, jsonMode });
304
+ })();
package/index.html ADDED
@@ -0,0 +1,160 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>cc-skill-audit — Claude Code Skill Analyzer</title>
7
+ <meta name="description" content="Audit your Claude Code skills: token overhead, usage frequency, prune candidates. Zero dependencies.">
8
+ <style>
9
+ * { box-sizing: border-box; margin: 0; padding: 0; }
10
+ body {
11
+ background: #0d1117;
12
+ color: #e6edf3;
13
+ font-family: 'SF Mono', 'Fira Code', monospace;
14
+ min-height: 100vh;
15
+ display: flex;
16
+ flex-direction: column;
17
+ align-items: center;
18
+ padding: 40px 16px;
19
+ }
20
+ .container { max-width: 680px; width: 100%; }
21
+ h1 { font-size: 1.5rem; color: #58a6ff; margin-bottom: 4px; }
22
+ .tagline { color: #8b949e; font-size: 0.85rem; margin-bottom: 32px; }
23
+ .terminal {
24
+ background: #161b22;
25
+ border: 1px solid #30363d;
26
+ border-radius: 8px;
27
+ padding: 24px;
28
+ font-size: 0.82rem;
29
+ line-height: 1.7;
30
+ }
31
+ .prompt { color: #58a6ff; }
32
+ .dim { color: #6e7681; }
33
+ .green { color: #3fb950; }
34
+ .yellow { color: #d29922; }
35
+ .red { color: #f85149; }
36
+ .cyan { color: #39c5cf; }
37
+ .bold { font-weight: 700; }
38
+ .bar-fill { color: #3fb950; }
39
+ .bar-warn { color: #d29922; }
40
+ .bar-danger { color: #f85149; }
41
+ .bar-empty { color: #30363d; }
42
+ hr.div { border: none; border-top: 1px solid #30363d; margin: 12px 0; }
43
+ .install {
44
+ background: #161b22;
45
+ border: 1px solid #30363d;
46
+ border-radius: 8px;
47
+ padding: 16px 24px;
48
+ margin: 24px 0;
49
+ display: flex;
50
+ align-items: center;
51
+ gap: 12px;
52
+ }
53
+ .install code { color: #79c0ff; font-size: 1rem; flex: 1; }
54
+ .copy-btn {
55
+ background: #21262d;
56
+ border: 1px solid #30363d;
57
+ color: #c9d1d9;
58
+ padding: 6px 14px;
59
+ border-radius: 6px;
60
+ cursor: pointer;
61
+ font-size: 0.8rem;
62
+ font-family: inherit;
63
+ transition: background 0.15s;
64
+ }
65
+ .copy-btn:hover { background: #30363d; }
66
+ .flags { margin-top: 24px; }
67
+ .flag { display: flex; gap: 12px; padding: 4px 0; }
68
+ .flag-name { color: #79c0ff; min-width: 200px; }
69
+ .flag-desc { color: #8b949e; }
70
+ .links { margin-top: 32px; display: flex; gap: 16px; flex-wrap: wrap; }
71
+ .links a {
72
+ color: #58a6ff;
73
+ text-decoration: none;
74
+ font-size: 0.85rem;
75
+ padding: 6px 14px;
76
+ border: 1px solid #30363d;
77
+ border-radius: 6px;
78
+ transition: border-color 0.15s;
79
+ }
80
+ .links a:hover { border-color: #58a6ff; }
81
+ footer { margin-top: 40px; color: #6e7681; font-size: 0.75rem; text-align: center; }
82
+ </style>
83
+ </head>
84
+ <body>
85
+ <div class="container">
86
+ <h1>cc-skill-audit</h1>
87
+ <p class="tagline">Audit your Claude Code skills — token overhead, usage frequency, prune candidates</p>
88
+
89
+ <div class="install">
90
+ <code id="cmd">npx cc-skill-audit</code>
91
+ <button class="copy-btn" onclick="copyCmd()">Copy</button>
92
+ </div>
93
+
94
+ <div class="terminal">
95
+ <div><span class="prompt">❯</span> <span class="dim">npx cc-skill-audit</span></div>
96
+ <br>
97
+ <div><span class="bold cyan">cc-skill-audit</span> <span class="dim">Skill token overhead analyzer</span></div>
98
+ <hr class="div">
99
+ <div><span class="dim">Summary</span></div>
100
+ <div>Skills installed <span class="bold">109</span></div>
101
+ <div>Index overhead <span class="bold">2.3K tokens</span> <span class="dim">(loaded every session)</span></div>
102
+ <div>Total content <span class="bold">348.6K tokens</span> <span class="dim">(loaded on invocation)</span></div>
103
+ <div>Usage lookback <span class="bold">30 days</span></div>
104
+ <br>
105
+ <div><span class="dim">Top 20 skills by size &nbsp;(30d usage)</span></div>
106
+ <div><span class="dim">Skill&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Tokens &nbsp;&nbsp;Use &nbsp;Bar</span></div>
107
+ <hr class="div">
108
+ <div><span class="yellow">writing-skills&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span> &nbsp;21.7K &nbsp;<span class="red">&nbsp;&nbsp; 0</span> &nbsp;<span class="bar-danger">██████████████</span></div>
109
+ <div><span class="yellow">pptx&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span> &nbsp;14.3K &nbsp;<span class="red">&nbsp;&nbsp; 0</span> &nbsp;<span class="bar-danger">█████████</span><span class="bar-empty">░░░░░</span></div>
110
+ <div><span class="yellow">docx&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span> &nbsp;12.9K &nbsp;<span class="red">&nbsp;&nbsp; 0</span> &nbsp;<span class="bar-danger">████████</span><span class="bar-empty">░░░░░░</span></div>
111
+ <div>nano-banana-pro&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;9.2K &nbsp;<span class="green">&nbsp;&nbsp; 4</span> &nbsp;<span class="bar-warn">██████</span><span class="bar-empty">░░░░░░░░</span></div>
112
+ <div>playwright&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;7.6K &nbsp;<span class="green">&nbsp;&nbsp; 2</span> &nbsp;<span class="bar-warn">█████</span><span class="bar-empty">░░░░░░░░░</span></div>
113
+ <div><span class="dim">...</span></div>
114
+ <br>
115
+ <div><span class="yellow bold">Prune candidates</span> <span class="dim">(&gt;5K tokens, 0 uses in 30d)</span></div>
116
+ <div><span class="dim">Removing these would save ~82.2K tokens when invoked</span></div>
117
+ <div><span class="red">✗</span> writing-skills &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="dim">21.7K tokens</span></div>
118
+ <div><span class="red">✗</span> pptx &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="dim">14.3K tokens</span></div>
119
+ <div><span class="red">✗</span> docx &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="dim">12.9K tokens</span></div>
120
+ <br>
121
+ <div><span class="dim">Active (used in 30d):</span> <span class="green">45</span> / 109</div>
122
+ <div><span class="dim">Never used in 30d:</span> <span class="yellow">64</span> skills</div>
123
+ </div>
124
+
125
+ <div class="flags">
126
+ <p class="dim" style="margin-bottom: 12px;">Options</p>
127
+ <div class="flag">
128
+ <span class="flag-name">--top &lt;N&gt;</span>
129
+ <span class="flag-desc">Show top N skills by size (default: 20)</span>
130
+ </div>
131
+ <div class="flag">
132
+ <span class="flag-name">--days &lt;N&gt;</span>
133
+ <span class="flag-desc">Lookback window for usage (default: 30)</span>
134
+ </div>
135
+ <div class="flag">
136
+ <span class="flag-name">--json</span>
137
+ <span class="flag-desc">Machine-readable JSON output</span>
138
+ </div>
139
+ </div>
140
+
141
+ <div class="links">
142
+ <a href="https://www.npmjs.com/package/cc-skill-audit" target="_blank">npm</a>
143
+ <a href="https://github.com/yurukusa/cc-skill-audit" target="_blank">GitHub</a>
144
+ <a href="https://yurukusa.github.io/cc-toolkit/" target="_blank">cc-toolkit</a>
145
+ </div>
146
+
147
+ <footer>Part of <a href="https://yurukusa.github.io/cc-toolkit/" style="color:#58a6ff;text-decoration:none">cc-toolkit</a> — Claude Code utilities by <a href="https://zenn.dev/yurukusa" style="color:#58a6ff;text-decoration:none">yurukusa</a></footer>
148
+ </div>
149
+
150
+ <script>
151
+ function copyCmd() {
152
+ navigator.clipboard.writeText('npx cc-skill-audit').then(() => {
153
+ const btn = document.querySelector('.copy-btn');
154
+ btn.textContent = 'Copied!';
155
+ setTimeout(() => btn.textContent = 'Copy', 2000);
156
+ });
157
+ }
158
+ </script>
159
+ </body>
160
+ </html>
package/package.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "cc-skill-audit",
3
+ "version": "1.0.0",
4
+ "description": "Audit your Claude Code skills: token overhead, usage frequency, prune candidates",
5
+ "type": "module",
6
+ "bin": {
7
+ "cc-skill-audit": "cli.mjs"
8
+ },
9
+ "scripts": {
10
+ "start": "node cli.mjs"
11
+ },
12
+ "keywords": ["claude", "claude-code", "ai", "skills", "audit", "token", "optimization"],
13
+ "author": "yurukusa",
14
+ "license": "MIT",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/yurukusa/cc-skill-audit"
18
+ }
19
+ }