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.
- package/README.md +77 -0
- package/cli.mjs +304 -0
- package/index.html +160 -0
- 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 (30d usage)</span></div>
|
|
106
|
+
<div><span class="dim">Skill Tokens Use Bar</span></div>
|
|
107
|
+
<hr class="div">
|
|
108
|
+
<div><span class="yellow">writing-skills </span> 21.7K <span class="red"> 0</span> <span class="bar-danger">██████████████</span></div>
|
|
109
|
+
<div><span class="yellow">pptx </span> 14.3K <span class="red"> 0</span> <span class="bar-danger">█████████</span><span class="bar-empty">░░░░░</span></div>
|
|
110
|
+
<div><span class="yellow">docx </span> 12.9K <span class="red"> 0</span> <span class="bar-danger">████████</span><span class="bar-empty">░░░░░░</span></div>
|
|
111
|
+
<div>nano-banana-pro 9.2K <span class="green"> 4</span> <span class="bar-warn">██████</span><span class="bar-empty">░░░░░░░░</span></div>
|
|
112
|
+
<div>playwright 7.6K <span class="green"> 2</span> <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">(>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 <span class="dim">21.7K tokens</span></div>
|
|
118
|
+
<div><span class="red">✗</span> pptx <span class="dim">14.3K tokens</span></div>
|
|
119
|
+
<div><span class="red">✗</span> docx <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 <N></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 <N></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
|
+
}
|