claude-slim 2.6.1 → 2.7.1
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 +8 -0
- package/dist/cleaner.d.ts +2 -2
- package/dist/cleaner.js +69 -24
- package/dist/cli.js +70 -31
- package/dist/manifest.d.ts +7 -4
- package/dist/manifest.js +26 -1
- package/dist/plugin-runtime.d.ts +4 -0
- package/dist/plugin-runtime.js +33 -0
- package/dist/report.d.ts +8 -1
- package/dist/report.js +29 -1
- package/dist/scanner/constants.d.ts +3 -0
- package/dist/scanner/constants.js +8 -0
- package/dist/scanner/detectors.d.ts +11 -0
- package/dist/scanner/detectors.js +49 -3
- package/dist/scanner/disabled-plugins.d.ts +7 -0
- package/dist/scanner/disabled-plugins.js +38 -0
- package/dist/scanner/index.js +40 -1
- package/dist/scanner/plugin-breakdown.d.ts +16 -0
- package/dist/scanner/plugin-breakdown.js +129 -0
- package/dist/scanner/plugin-cost.d.ts +27 -0
- package/dist/scanner/plugin-cost.js +35 -0
- package/dist/scanner/plugin-surfaces.d.ts +14 -0
- package/dist/scanner/plugin-surfaces.js +129 -0
- package/dist/scanner/sessions.d.ts +5 -0
- package/dist/scanner/sessions.js +158 -7
- package/dist/types.d.ts +22 -2
- package/package.json +1 -1
package/dist/scanner/sessions.js
CHANGED
|
@@ -79,6 +79,117 @@ export function extractSkillsFromTranscript(content) {
|
|
|
79
79
|
}
|
|
80
80
|
return skills;
|
|
81
81
|
}
|
|
82
|
+
// Extract MCP server prefixes from tool_use events whose name matches
|
|
83
|
+
// `mcp__<prefix>__<tool>`. Returns the set of unique prefixes seen.
|
|
84
|
+
//
|
|
85
|
+
// Example: `mcp__plugin_oh-my-claudecode_t__lsp_diagnostics` → `plugin_oh-my-claudecode_t`
|
|
86
|
+
export function extractMcpPrefixesFromTranscript(content) {
|
|
87
|
+
const prefixes = new Set();
|
|
88
|
+
const lines = content.split('\n');
|
|
89
|
+
for (const line of lines) {
|
|
90
|
+
if (!line)
|
|
91
|
+
continue;
|
|
92
|
+
let obj;
|
|
93
|
+
try {
|
|
94
|
+
obj = JSON.parse(line);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (typeof obj !== 'object' || obj === null)
|
|
100
|
+
continue;
|
|
101
|
+
const message = obj.message;
|
|
102
|
+
if (typeof message !== 'object' || message === null)
|
|
103
|
+
continue;
|
|
104
|
+
const msgContent = message.content;
|
|
105
|
+
if (!Array.isArray(msgContent))
|
|
106
|
+
continue;
|
|
107
|
+
for (const c of msgContent) {
|
|
108
|
+
if (typeof c !== 'object' || c === null)
|
|
109
|
+
continue;
|
|
110
|
+
const rec = c;
|
|
111
|
+
if (rec.type !== 'tool_use')
|
|
112
|
+
continue;
|
|
113
|
+
const name = rec.name;
|
|
114
|
+
if (typeof name !== 'string' || !name.startsWith('mcp__'))
|
|
115
|
+
continue;
|
|
116
|
+
// Must have at least two __ separators: mcp__<prefix>__<tool>
|
|
117
|
+
const after = name.slice('mcp__'.length);
|
|
118
|
+
const sep = after.indexOf('__');
|
|
119
|
+
if (sep === -1)
|
|
120
|
+
continue;
|
|
121
|
+
prefixes.add(after.slice(0, sep));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return prefixes;
|
|
125
|
+
}
|
|
126
|
+
// Extract slash command names from user messages containing the
|
|
127
|
+
// `<command-name>/foo</command-name>` tag that Claude Code injects when a
|
|
128
|
+
// user runs a slash command. The leading slash is stripped so callers receive
|
|
129
|
+
// plain names like "clear" or "grill-me".
|
|
130
|
+
//
|
|
131
|
+
// Only `type === "user"` / `role === "user"` messages are examined to avoid
|
|
132
|
+
// false positives from assistant text that may reference command names.
|
|
133
|
+
// Regex pattern held as a plain string — each call site constructs a fresh
|
|
134
|
+
// RegExp so there is no shared `lastIndex` state to reset. `String.matchAll`
|
|
135
|
+
// then wraps that RegExp in its own iterator, further insulating the loop.
|
|
136
|
+
const COMMAND_TAG_PATTERN = /<command-name>([^<]+)<\/command-name>/g;
|
|
137
|
+
export function extractCommandsFromTranscript(content) {
|
|
138
|
+
const commands = new Set();
|
|
139
|
+
const lines = content.split('\n');
|
|
140
|
+
for (const line of lines) {
|
|
141
|
+
if (!line)
|
|
142
|
+
continue;
|
|
143
|
+
let obj;
|
|
144
|
+
try {
|
|
145
|
+
obj = JSON.parse(line);
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (typeof obj !== 'object' || obj === null)
|
|
151
|
+
continue;
|
|
152
|
+
const rec = obj;
|
|
153
|
+
// Only inspect user-role messages
|
|
154
|
+
const message = rec.message;
|
|
155
|
+
if (typeof message !== 'object' || message === null)
|
|
156
|
+
continue;
|
|
157
|
+
const msgRec = message;
|
|
158
|
+
if (msgRec.role !== 'user')
|
|
159
|
+
continue;
|
|
160
|
+
const msgContent = msgRec.content;
|
|
161
|
+
// User content may be a plain string or an array of content blocks.
|
|
162
|
+
const texts = [];
|
|
163
|
+
if (typeof msgContent === 'string') {
|
|
164
|
+
texts.push(msgContent);
|
|
165
|
+
}
|
|
166
|
+
else if (Array.isArray(msgContent)) {
|
|
167
|
+
for (const c of msgContent) {
|
|
168
|
+
if (typeof c !== 'object' || c === null)
|
|
169
|
+
continue;
|
|
170
|
+
const text = c.text;
|
|
171
|
+
if (typeof text === 'string')
|
|
172
|
+
texts.push(text);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
for (const text of texts) {
|
|
176
|
+
for (const match of text.matchAll(COMMAND_TAG_PATTERN)) {
|
|
177
|
+
// Strip leading slash from the command value (e.g. "/clear" → "clear")
|
|
178
|
+
const raw = match[1].trim();
|
|
179
|
+
commands.add(raw.startsWith('/') ? raw.slice(1) : raw);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return commands;
|
|
184
|
+
}
|
|
185
|
+
// Parse all signals from a single JSONL transcript.
|
|
186
|
+
function parseTranscript(content) {
|
|
187
|
+
const skills = extractSkillsFromTranscript(content);
|
|
188
|
+
const mcpPrefixes = Array.from(extractMcpPrefixesFromTranscript(content));
|
|
189
|
+
const commands = Array.from(extractCommandsFromTranscript(content));
|
|
190
|
+
const invocationCount = skills.length + mcpPrefixes.length + commands.length;
|
|
191
|
+
return { skills, mcpPrefixes, commands, invocationCount };
|
|
192
|
+
}
|
|
82
193
|
// Walk every `~/.claude/projects/<slug>/*.jsonl` whose mtime falls inside the
|
|
83
194
|
// lookback window. Per-file results are cached by mtime, so warm scans only
|
|
84
195
|
// re-read files that have changed.
|
|
@@ -90,6 +201,9 @@ export async function scanSessionUsage(lookbackDays) {
|
|
|
90
201
|
// across many scans even as old session logs get rotated/deleted.
|
|
91
202
|
const newEntries = {};
|
|
92
203
|
const invokedSkills = new Set();
|
|
204
|
+
const mcpPrefixesInvoked = new Set();
|
|
205
|
+
const commandsInvoked = new Set();
|
|
206
|
+
let totalUserCallableInvocations = 0;
|
|
93
207
|
let sessionsScanned = 0;
|
|
94
208
|
let sessionsInWindow = 0;
|
|
95
209
|
let projectDirs = [];
|
|
@@ -97,7 +211,15 @@ export async function scanSessionUsage(lookbackDays) {
|
|
|
97
211
|
projectDirs = await readdir(projectsDir);
|
|
98
212
|
}
|
|
99
213
|
catch {
|
|
100
|
-
return {
|
|
214
|
+
return {
|
|
215
|
+
invokedSkills,
|
|
216
|
+
dataAvailable: false,
|
|
217
|
+
sessionsScanned: 0,
|
|
218
|
+
sessionsInWindow: 0,
|
|
219
|
+
mcpPrefixesInvoked,
|
|
220
|
+
commandsInvoked,
|
|
221
|
+
totalUserCallableInvocations: 0,
|
|
222
|
+
};
|
|
101
223
|
}
|
|
102
224
|
for (const projectName of projectDirs) {
|
|
103
225
|
const projectPath = join(projectsDir, projectName);
|
|
@@ -123,11 +245,21 @@ export async function scanSessionUsage(lookbackDays) {
|
|
|
123
245
|
if (mtimeMs < cutoffMs)
|
|
124
246
|
continue;
|
|
125
247
|
sessionsInWindow++;
|
|
126
|
-
// Cache hit: reuse the parsed
|
|
248
|
+
// Cache hit: reuse the parsed result only if mtime matches AND the entry
|
|
249
|
+
// has the v2.6 extension fields (mcpPrefixes / commands). Entries from
|
|
250
|
+
// older cache files lack these fields → force a re-parse.
|
|
127
251
|
const cached = cache.entries[filePath];
|
|
128
|
-
if (cached &&
|
|
252
|
+
if (cached &&
|
|
253
|
+
cached.mtimeMs === mtimeMs &&
|
|
254
|
+
Array.isArray(cached.mcpPrefixes) &&
|
|
255
|
+
Array.isArray(cached.commands)) {
|
|
129
256
|
for (const s of cached.skills)
|
|
130
257
|
invokedSkills.add(s);
|
|
258
|
+
for (const p of cached.mcpPrefixes)
|
|
259
|
+
mcpPrefixesInvoked.add(p);
|
|
260
|
+
for (const cmd of cached.commands)
|
|
261
|
+
commandsInvoked.add(cmd);
|
|
262
|
+
totalUserCallableInvocations += cached.invocationCount ?? 0;
|
|
131
263
|
newEntries[filePath] = cached;
|
|
132
264
|
continue;
|
|
133
265
|
}
|
|
@@ -138,13 +270,32 @@ export async function scanSessionUsage(lookbackDays) {
|
|
|
138
270
|
catch {
|
|
139
271
|
continue;
|
|
140
272
|
}
|
|
141
|
-
const
|
|
142
|
-
for (const s of skills)
|
|
273
|
+
const parsed = parseTranscript(content);
|
|
274
|
+
for (const s of parsed.skills)
|
|
143
275
|
invokedSkills.add(s);
|
|
144
|
-
|
|
276
|
+
for (const p of parsed.mcpPrefixes)
|
|
277
|
+
mcpPrefixesInvoked.add(p);
|
|
278
|
+
for (const cmd of parsed.commands)
|
|
279
|
+
commandsInvoked.add(cmd);
|
|
280
|
+
totalUserCallableInvocations += parsed.invocationCount;
|
|
281
|
+
newEntries[filePath] = {
|
|
282
|
+
mtimeMs,
|
|
283
|
+
skills: Array.from(new Set(parsed.skills)),
|
|
284
|
+
mcpPrefixes: Array.from(new Set(parsed.mcpPrefixes)),
|
|
285
|
+
commands: Array.from(new Set(parsed.commands)),
|
|
286
|
+
invocationCount: parsed.invocationCount,
|
|
287
|
+
};
|
|
145
288
|
}
|
|
146
289
|
}
|
|
147
290
|
const dataAvailable = sessionsInWindow >= MIN_SESSIONS_FOR_DATA_AVAILABLE && invokedSkills.size > 0;
|
|
148
291
|
await saveCache({ version: CACHE_VERSION, entries: newEntries });
|
|
149
|
-
return {
|
|
292
|
+
return {
|
|
293
|
+
invokedSkills,
|
|
294
|
+
dataAvailable,
|
|
295
|
+
sessionsScanned,
|
|
296
|
+
sessionsInWindow,
|
|
297
|
+
mcpPrefixesInvoked,
|
|
298
|
+
commandsInvoked,
|
|
299
|
+
totalUserCallableInvocations,
|
|
300
|
+
};
|
|
150
301
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export interface PluginInfo {
|
|
|
25
25
|
status?: 'enabled' | 'disabled';
|
|
26
26
|
}
|
|
27
27
|
export type IssueTier = 1 | 2 | 3;
|
|
28
|
-
export type IssueType = 'broken_symlink' | 'template' | 'skill_dup' | 'duplicate' | 'oversized_memory' | 'oversized_skill' | 'unused_skill' | 'disabled_plugin' | 'stale_project' | 'temp_cache';
|
|
28
|
+
export type IssueType = 'broken_symlink' | 'template' | 'skill_dup' | 'duplicate' | 'oversized_memory' | 'oversized_skill' | 'unused_skill' | 'unused_plugin' | 'disabled_plugin' | 'stale_project' | 'temp_cache';
|
|
29
29
|
export interface Issue {
|
|
30
30
|
type: IssueType;
|
|
31
31
|
tier: IssueTier;
|
|
@@ -33,6 +33,17 @@ export interface Issue {
|
|
|
33
33
|
detail?: string;
|
|
34
34
|
tokens: number;
|
|
35
35
|
path: string;
|
|
36
|
+
marketplace?: string;
|
|
37
|
+
}
|
|
38
|
+
export interface PluginBreakdown {
|
|
39
|
+
name: string;
|
|
40
|
+
marketplace: string;
|
|
41
|
+
tokens: number;
|
|
42
|
+
skills: number;
|
|
43
|
+
mcp: number;
|
|
44
|
+
commands: number;
|
|
45
|
+
lastUsed: 'used' | 'never';
|
|
46
|
+
status: 'used' | 'unused' | 'agent-only' | 'insufficient data' | 'disabled';
|
|
36
47
|
}
|
|
37
48
|
export interface ScanResult {
|
|
38
49
|
localSkills: SkillInfo[];
|
|
@@ -51,6 +62,7 @@ export interface ScanResult {
|
|
|
51
62
|
mcpServerNames: string[];
|
|
52
63
|
issues: Issue[];
|
|
53
64
|
totalTokensBefore: number;
|
|
65
|
+
pluginBreakdown: PluginBreakdown[];
|
|
54
66
|
}
|
|
55
67
|
export interface ManifestEntry {
|
|
56
68
|
date: string;
|
|
@@ -60,9 +72,17 @@ export interface ManifestEntry {
|
|
|
60
72
|
tokenCount?: number;
|
|
61
73
|
tier?: IssueTier;
|
|
62
74
|
}
|
|
75
|
+
export type LegacyManifestEntry = ManifestEntry;
|
|
76
|
+
export interface DisabledPluginEntry {
|
|
77
|
+
type: 'disabled_plugin';
|
|
78
|
+
plugin: string;
|
|
79
|
+
marketplace: string;
|
|
80
|
+
disabledAt: string;
|
|
81
|
+
}
|
|
82
|
+
export type AnyManifestEntry = ManifestEntry | DisabledPluginEntry;
|
|
63
83
|
export interface Manifest {
|
|
64
84
|
version: 2;
|
|
65
|
-
entries:
|
|
85
|
+
entries: AnyManifestEntry[];
|
|
66
86
|
}
|
|
67
87
|
export interface TokenCache {
|
|
68
88
|
version: number;
|