claude-slim 2.12.3 → 2.13.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 +21 -20
- package/dist/cli.js +3 -2
- package/dist/codex/report.js +4 -2
- package/dist/report.js +6 -0
- package/dist/scanner/index.js +34 -1
- package/dist/scanner/plugin-skills.js +8 -18
- package/dist/types.d.ts +21 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -94,21 +94,21 @@ That's slower responses. Hitting your usage cap faster. Paying for context you'r
|
|
|
94
94
|
**Report** — Shows exactly what changed:
|
|
95
95
|
|
|
96
96
|
```
|
|
97
|
-
|
|
98
|
-
│ claude-slim report
|
|
99
|
-
│
|
|
100
|
-
│ Before: 14,510 tokens at startup
|
|
101
|
-
│ After: 5,181 tokens at startup
|
|
102
|
-
│ Saved: 9,329 tokens (64.3%)
|
|
103
|
-
│
|
|
104
|
-
│ Top offenders removed:
|
|
105
|
-
│ • office-hours 23,008 tok
|
|
106
|
-
│ • harness 7,902 tok
|
|
107
|
-
│ • manpower 4,764 tok
|
|
108
|
-
│
|
|
109
|
-
│ Est. monthly savings: ~$1.68
|
|
110
|
-
│ (2 sessions/day × $0.003/1K tok)
|
|
111
|
-
|
|
97
|
+
╭────────────────────────────────────────╮
|
|
98
|
+
│ claude-slim report │
|
|
99
|
+
│ │
|
|
100
|
+
│ Before: 14,510 tokens at startup │
|
|
101
|
+
│ After: 5,181 tokens at startup │
|
|
102
|
+
│ Saved: 9,329 tokens (64.3%) │
|
|
103
|
+
│ │
|
|
104
|
+
│ Top offenders removed: │
|
|
105
|
+
│ • office-hours 23,008 tok│
|
|
106
|
+
│ • harness 7,902 tok│
|
|
107
|
+
│ • manpower 4,764 tok│
|
|
108
|
+
│ │
|
|
109
|
+
│ Est. monthly savings: ~$1.68 │
|
|
110
|
+
│ (2 sessions/day × $0.003/1K tok) │
|
|
111
|
+
╰────────────────────────────────────────╯
|
|
112
112
|
|
|
113
113
|
┌──────────────────┬──────────┬──────────┬────────────┐
|
|
114
114
|
│ │ Before │ After │ Saved │
|
|
@@ -249,13 +249,14 @@ Token counts come from [js-tiktoken](https://github.com/nicolo-ribaudo/js-tiktok
|
|
|
249
249
|
|
|
250
250
|
---
|
|
251
251
|
|
|
252
|
-
## v2.
|
|
252
|
+
## v2.13.1 — What's new
|
|
253
253
|
|
|
254
|
-
- **Fixed: the
|
|
255
|
-
-
|
|
256
|
-
- **Corrected: v2.12.2 reported this issue as "1,944 tokens, 14.5%". It is 931 tokens, 6.9%.** That estimate mistook two distinct plugins shipping identical skill names (`document-skills` and `example-skills` share all 16) for duplicates.
|
|
254
|
+
- **Fixed: `scan` promised the tool never touches `~/.codex/`, while `clean --auto` deletes from it permanently.** The Codex summary closed with `Reported only — claude-slim never modifies ~/.codex/.` That was true in v2.10 and false from **v2.11** on, when `clean` gained the Codex tiers. Measured here: `clean --dry-run` selects `[Auto] [codex] temp_cache: .tmp (146MB of install leftovers) (permanent)` — Tier 1, which `--auto` applies without prompting and no `restore` undoes.
|
|
255
|
+
- The summary now says what actually happens: `scan` only reads, `clean` acts under the same tiers as `~/.claude/`, moves land in `~/.codex/skills.disabled/` and reverse with `restore`, and Tier 1 install leftovers are deleted permanently.
|
|
257
256
|
|
|
258
|
-
|
|
257
|
+
A test was holding the false claim in place. It asserted the summary *must* say "never modifies", so the line could not be corrected without a test failing — and all 461 tests stayed green across four releases that contradicted it. The assertion is now inverted: the summary must not promise `~/.codex/` is left alone, and must name the permanent deletion and `--auto`.
|
|
258
|
+
|
|
259
|
+
Tests: 461 → 462.
|
|
259
260
|
|
|
260
261
|
For older release notes, see [CHANGELOG.md](CHANGELOG.md).
|
|
261
262
|
|
package/dist/cli.js
CHANGED
|
@@ -50,8 +50,9 @@ program
|
|
|
50
50
|
lookbackDays: parseNonNegativeInt(opts.lookbackDays, 60),
|
|
51
51
|
projectDir,
|
|
52
52
|
});
|
|
53
|
-
// Codex is scanned when present.
|
|
54
|
-
//
|
|
53
|
+
// Codex is scanned when present. `scan` only reads; `clean` has acted here
|
|
54
|
+
// since v2.11 under the same tiers. Unused-skill detection stays suppressed
|
|
55
|
+
// for lack of a usage signal.
|
|
55
56
|
// commander maps `--no-codex` to `opts.codex === false`, not `opts.noCodex`.
|
|
56
57
|
const codex = opts.codex === false ? null : await scanCodex();
|
|
57
58
|
await flushCache();
|
package/dist/codex/report.js
CHANGED
|
@@ -44,13 +44,15 @@ export function formatCodexSummary(result) {
|
|
|
44
44
|
lines.push(` ${b.name.length > 30 ? `${b.name.slice(0, 29)}…` : b.name.padEnd(30)} ` +
|
|
45
45
|
`${String(b.listingTokens).padStart(5)} tok \x1b[90m${b.backupArtifact}\x1b[0m`);
|
|
46
46
|
}
|
|
47
|
-
lines.push(` \x1b[
|
|
47
|
+
lines.push(` \x1b[90mOffered by clean as Tier 2 — moved to ~/.codex/skills.disabled/, reversible with restore.\x1b[0m`);
|
|
48
48
|
}
|
|
49
49
|
lines.push('');
|
|
50
50
|
lines.push(` \x1b[33m!\x1b[0m Unused-skill detection unavailable for Codex.`);
|
|
51
51
|
lines.push(` \x1b[90m${result.unusedDetectionReason}\x1b[0m`);
|
|
52
52
|
lines.push('');
|
|
53
|
-
lines.push(` \x1b[
|
|
53
|
+
lines.push(` \x1b[90mscan only reads. Since v2.11 clean also acts here, under the same tiers as ~/.claude/:\x1b[0m`);
|
|
54
|
+
lines.push(` \x1b[90mmoves go to ~/.codex/skills.disabled/ and reverse with restore, but Tier 1 install\x1b[0m`);
|
|
55
|
+
lines.push(` \x1b[90mleftovers (~/.codex/.tmp) are deleted permanently, and --auto applies Tier 1 unprompted.\x1b[0m`);
|
|
54
56
|
lines.push('');
|
|
55
57
|
return lines.join('\n');
|
|
56
58
|
}
|
package/dist/report.js
CHANGED
|
@@ -251,6 +251,12 @@ export function formatScanSummary(result) {
|
|
|
251
251
|
// --- SUMMARY ---
|
|
252
252
|
lines.push('');
|
|
253
253
|
lines.push(`\x1b[1m ESTIMATED OVERHEAD\x1b[0m: ~${result.totalTokensBefore.toLocaleString()} tokens at session start`);
|
|
254
|
+
if (result.disabledPluginSkillTokens > 0) {
|
|
255
|
+
// Stated rather than silently omitted: the figure moved out of the total in
|
|
256
|
+
// 2.13.0, and a total that drops with no explanation reads like a bug.
|
|
257
|
+
lines.push(` \x1b[90mexcludes ~${result.disabledPluginSkillTokens.toLocaleString()} tokens of disabled-plugin skills — ` +
|
|
258
|
+
`not loaded, so not a startup cost\x1b[0m`);
|
|
259
|
+
}
|
|
254
260
|
if (result.issues.length > 0) {
|
|
255
261
|
// Stated next to the total it is a fraction of, because the numbers on the
|
|
256
262
|
// issue rows below are body sizes, not startup cost, and adding them up
|
package/dist/scanner/index.js
CHANGED
|
@@ -90,7 +90,39 @@ export async function scan(opts = {}) {
|
|
|
90
90
|
// measured from each file's frontmatter description rather than assumed
|
|
91
91
|
// (see scanner/skill-listing.ts) — the real spread is 30–500+ tokens apiece.
|
|
92
92
|
const sumListing = (entries) => entries.reduce((sum, e) => sum + e.listingTokens, 0);
|
|
93
|
-
|
|
93
|
+
// A disabled plugin's skills are not in the session catalog, so they cost
|
|
94
|
+
// nothing at startup. Verified against a live session: skills from every
|
|
95
|
+
// disabled plugin here (document-skills, superpowers, telegram, …) were
|
|
96
|
+
// absent from the prompt, while enabled ones were present. Counting them put
|
|
97
|
+
// 3,397 tokens — 27% of the total — into a number labelled "at session start".
|
|
98
|
+
//
|
|
99
|
+
// Only names reported *explicitly disabled* are dropped. `claude plugin list`
|
|
100
|
+
// also emits `failed to load`, which the parser matches as neither enabled nor
|
|
101
|
+
// disabled: railway reports it (a hook clash) and its twelve skills still
|
|
102
|
+
// load. Treating anything unrecognised as disabled would have silently
|
|
103
|
+
// deleted those from the total, so anything not known-disabled still counts.
|
|
104
|
+
// Keyed on `<plugin>@<marketplace>`, not the bare name: the same plugin name
|
|
105
|
+
// can be installed from two marketplaces in different states, and a name-only
|
|
106
|
+
// set would drop the enabled copy along with the disabled one. `pluginName`
|
|
107
|
+
// is the cache directory, which is the marketplace.
|
|
108
|
+
//
|
|
109
|
+
// An identity enabled anywhere is treated as enabled. `claude plugin list`
|
|
110
|
+
// emits one row per scope, so the same identity legitimately appears twice —
|
|
111
|
+
// and counting a live plugin is the safe error to make, not dropping it.
|
|
112
|
+
const pluginIdentity = (plugin, marketplace) => `${plugin}@${marketplace}`;
|
|
113
|
+
const enabledIdentities = new Set(installed.filter((p) => p.enabled).map((p) => pluginIdentity(p.name, p.marketplace)));
|
|
114
|
+
const disabledIdentities = new Set(installed
|
|
115
|
+
.filter((p) => !p.enabled)
|
|
116
|
+
.map((p) => pluginIdentity(p.name, p.marketplace))
|
|
117
|
+
.filter((id) => !enabledIdentities.has(id)));
|
|
118
|
+
const isLoadedAtStartup = (s) => {
|
|
119
|
+
if (s.plugin === undefined || s.pluginName === undefined)
|
|
120
|
+
return true;
|
|
121
|
+
return !disabledIdentities.has(pluginIdentity(s.plugin, s.pluginName));
|
|
122
|
+
};
|
|
123
|
+
const activePluginSkills = pluginSkills.filter(isLoadedAtStartup);
|
|
124
|
+
const disabledPluginSkillTokens = sumListing(pluginSkills.filter((s) => !isLoadedAtStartup(s)));
|
|
125
|
+
const skillListingTokens = sumListing(localSkills) + sumListing(activePluginSkills);
|
|
94
126
|
const agentListingTokens = sumListing(userSurfaces.agents);
|
|
95
127
|
const commandListingTokens = sumListing(userSurfaces.commands);
|
|
96
128
|
// Memory is per-project: a session loads ~/.claude/projects/<slug>/memory/
|
|
@@ -138,6 +170,7 @@ export async function scan(opts = {}) {
|
|
|
138
170
|
currentProjectMemoryTokens,
|
|
139
171
|
allProjectsMemoryTokens,
|
|
140
172
|
recoverableStartupTokens,
|
|
173
|
+
disabledPluginSkillTokens,
|
|
141
174
|
};
|
|
142
175
|
}
|
|
143
176
|
async function pathExists(p) {
|
|
@@ -4,17 +4,6 @@ import { getPluginsDir } from '../paths.js';
|
|
|
4
4
|
import { safeReadFile, safeReaddir, isDirectory, getDirSize, safeStat } from './fs-walk.js';
|
|
5
5
|
import { pickActiveVersion } from './plugin-versions.js';
|
|
6
6
|
import { listingTokensFromContent } from './skill-listing.js';
|
|
7
|
-
/**
|
|
8
|
-
* The directories under a marketplace whose skills a session would load — one
|
|
9
|
-
* per plugin, never two versions of the same one.
|
|
10
|
-
*
|
|
11
|
-
* The cache is laid out `<marketplace>/<plugin>/<version>/`. A plugin whose
|
|
12
|
-
* content sits directly under `<plugin>/` is returned as-is; otherwise its
|
|
13
|
-
* children are versions and only the active one counts. That distinction is
|
|
14
|
-
* drawn by looking for a `skills` directory rather than by pattern-matching
|
|
15
|
-
* version names, so a plugin that ships no skills is simply walked as before
|
|
16
|
-
* and contributes nothing either way.
|
|
17
|
-
*/
|
|
18
7
|
async function resolveContentRoots(pluginBaseDir) {
|
|
19
8
|
const entries = await safeReaddir(pluginBaseDir);
|
|
20
9
|
// Flat layout: the cache entry holds content directly, with no plugin or
|
|
@@ -23,7 +12,7 @@ async function resolveContentRoots(pluginBaseDir) {
|
|
|
23
12
|
// a candidate version, then picks exactly one — which is not a miscount but a
|
|
24
13
|
// silent disappearance: the whole entry reported zero skills.
|
|
25
14
|
if (entries.includes('skills'))
|
|
26
|
-
return [pluginBaseDir];
|
|
15
|
+
return [{ dir: pluginBaseDir }];
|
|
27
16
|
const roots = [];
|
|
28
17
|
for (const entry of entries) {
|
|
29
18
|
const pluginDir = join(pluginBaseDir, entry);
|
|
@@ -32,7 +21,7 @@ async function resolveContentRoots(pluginBaseDir) {
|
|
|
32
21
|
const children = await safeReaddir(pluginDir);
|
|
33
22
|
if (children.includes('skills')) {
|
|
34
23
|
// Content root, not a version container.
|
|
35
|
-
roots.push(pluginDir);
|
|
24
|
+
roots.push({ dir: pluginDir, plugin: entry });
|
|
36
25
|
continue;
|
|
37
26
|
}
|
|
38
27
|
const versions = [];
|
|
@@ -46,11 +35,11 @@ async function resolveContentRoots(pluginBaseDir) {
|
|
|
46
35
|
const active = pickActiveVersion(versions);
|
|
47
36
|
// No subdirectories at all: hand back the plugin dir so the walk behaves
|
|
48
37
|
// exactly as it did before rather than silently dropping the plugin.
|
|
49
|
-
roots.push(active ? active.dir : pluginDir);
|
|
38
|
+
roots.push({ dir: active ? active.dir : pluginDir, plugin: entry });
|
|
50
39
|
}
|
|
51
40
|
// A marketplace with no plugin subdirectories still needs walking — some
|
|
52
41
|
// caches put content directly under the top level.
|
|
53
|
-
return roots.length > 0 ? roots : [pluginBaseDir];
|
|
42
|
+
return roots.length > 0 ? roots : [{ dir: pluginBaseDir }];
|
|
54
43
|
}
|
|
55
44
|
export async function scanPluginSkills() {
|
|
56
45
|
const skills = [];
|
|
@@ -69,7 +58,7 @@ export async function scanPluginSkills() {
|
|
|
69
58
|
return;
|
|
70
59
|
}
|
|
71
60
|
const pluginSkillNames = [];
|
|
72
|
-
const walkDir = async (dir) => {
|
|
61
|
+
const walkDir = async (dir, plugin) => {
|
|
73
62
|
// Kept generic: a plugin's content root is normally
|
|
74
63
|
// `<plugin>/<version>/`, but the walk also has to reach skills nested
|
|
75
64
|
// deeper. Version selection happens before we get here — see
|
|
@@ -97,17 +86,18 @@ export async function scanPluginSkills() {
|
|
|
97
86
|
listingTokens: listingTokensFromContent(skillDir, content),
|
|
98
87
|
source: 'plugin',
|
|
99
88
|
pluginName,
|
|
89
|
+
plugin,
|
|
100
90
|
});
|
|
101
91
|
}
|
|
102
92
|
}
|
|
103
93
|
}
|
|
104
94
|
else {
|
|
105
|
-
await walkDir(entryPath);
|
|
95
|
+
await walkDir(entryPath, plugin);
|
|
106
96
|
}
|
|
107
97
|
}
|
|
108
98
|
};
|
|
109
99
|
for (const root of await resolveContentRoots(pluginDir)) {
|
|
110
|
-
await walkDir(root);
|
|
100
|
+
await walkDir(root.dir, root.plugin);
|
|
111
101
|
}
|
|
112
102
|
if (pluginSkillNames.length > 0) {
|
|
113
103
|
plugins.push({
|
package/dist/types.d.ts
CHANGED
|
@@ -14,7 +14,20 @@ export interface SkillInfo {
|
|
|
14
14
|
*/
|
|
15
15
|
listingTokens: number;
|
|
16
16
|
source: 'local' | 'plugin';
|
|
17
|
+
/**
|
|
18
|
+
* Cache directory this skill was found under — the *marketplace*, not the
|
|
19
|
+
* plugin. Kept as-is because `plugins[]` and the `disabled_plugin` detector
|
|
20
|
+
* match cache directories by this name (see parseDisabledPlugins).
|
|
21
|
+
*/
|
|
17
22
|
pluginName?: string;
|
|
23
|
+
/**
|
|
24
|
+
* The actual plugin, e.g. `oh-my-claudecode` where `pluginName` is `omc`.
|
|
25
|
+
*
|
|
26
|
+
* Needed because enabled/disabled state is reported per plugin, not per
|
|
27
|
+
* marketplace, and one marketplace can host both. Absent when the cache entry
|
|
28
|
+
* has no plugin level to read it from.
|
|
29
|
+
*/
|
|
30
|
+
plugin?: string;
|
|
18
31
|
}
|
|
19
32
|
export interface BrokenSymlink {
|
|
20
33
|
name: string;
|
|
@@ -109,6 +122,14 @@ export interface ScanResult {
|
|
|
109
122
|
* against a 13,434-token startup total. This field is the honest number.
|
|
110
123
|
*/
|
|
111
124
|
recoverableStartupTokens: number;
|
|
125
|
+
/**
|
|
126
|
+
* Skill-listing tokens belonging to plugins reported as disabled.
|
|
127
|
+
*
|
|
128
|
+
* Excluded from `totalTokensBefore` because a disabled plugin's skills are
|
|
129
|
+
* not in the session catalog. Reported separately so the number does not just
|
|
130
|
+
* vanish: it is what re-enabling everything would cost.
|
|
131
|
+
*/
|
|
132
|
+
disabledPluginSkillTokens: number;
|
|
112
133
|
}
|
|
113
134
|
export interface ManifestEntry {
|
|
114
135
|
date: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-slim",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.1",
|
|
4
4
|
"description": "Audit and shrink your Claude Code startup context. Measures what every skill, plugin, agent, command, and memory file costs in the system prompt, then reversibly disables the dead weight. Non-destructive scan, tiered proposals, one-command restore — no proxy, no compression.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -65,6 +65,6 @@
|
|
|
65
65
|
"@types/node": "^22.20.1",
|
|
66
66
|
"tmp-promise": "^3.0.3",
|
|
67
67
|
"typescript": "^5.9.3",
|
|
68
|
-
"vitest": "^4.1.
|
|
68
|
+
"vitest": "^4.1.11"
|
|
69
69
|
}
|
|
70
70
|
}
|