claude-slim 2.7.1 → 2.7.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 +11 -52
- package/dist/cleaner.d.ts +6 -0
- package/dist/cleaner.js +24 -2
- package/dist/cli.js +6 -0
- package/dist/plugin-runtime.d.ts +15 -0
- package/dist/plugin-runtime.js +41 -0
- package/dist/scanner/index.js +5 -0
- package/dist/scanner/local-skills.js +33 -39
- package/package.json +12 -4
package/README.md
CHANGED
|
@@ -211,58 +211,17 @@ From a real cleanup session:
|
|
|
211
211
|
|
|
212
212
|
---
|
|
213
213
|
|
|
214
|
-
## v2.7 — What's new
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
- **
|
|
219
|
-
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
- **Pinned development Node version** — `.nvmrc` and `.node-version` pin contributors to Node 22.12.0, matching the current Vitest/Vite/Rolldown patch-floor requirements.
|
|
226
|
-
|
|
227
|
-
## v2.4 — What's new
|
|
228
|
-
|
|
229
|
-
- **Unused-skill detection** — claude-slim now reads your `~/.claude/projects/*/*.jsonl` session transcripts, finds every `Skill` tool invocation in the last 60 days, and flags local skills you've installed but never actually used. Tier 3 (Optional, never auto-selected) so you decide. Configurable lookback via `--lookback-days <n>`. Falls back silently if there's not enough session history (≥3 sessions required) — no false-flagging when the data source is unreliable.
|
|
230
|
-
- **Plugin skills are intentionally out of scope** for this detector. They live inside `~/.claude/plugins/cache/` and are managed by the Claude Code plugin runtime; moving them would partially uninstall the plugin. Use `claude plugin disable <name>` for plugin-level cleanup.
|
|
231
|
-
- **Per-file session-usage cache** at `~/.claude/.skill-usage-cache.json` keyed by mtime. Warm rescans only re-parse session logs that have changed.
|
|
232
|
-
- **Node 20+** is now the engine floor (previously `>=18`, but Node 18 was already dropped from CI in v2.3.0).
|
|
233
|
-
|
|
234
|
-
## v2.3 — What's new
|
|
235
|
-
|
|
236
|
-
- **Detector registry refactor (v2.3.0)** — Scanner split from a 588-line module into focused detectors under `src/scanner/`. Adding a new heuristic is a one-function addition; see CONTRIBUTING.md. Public API unchanged.
|
|
237
|
-
- **Path-containment guard (v2.2.3)** — Every destructive op refuses any target path that escapes `~/.claude/`. `runCommand` no longer goes through a shell. `temp_cache` cleanup is symlink-safe.
|
|
238
|
-
- **Report sign fix (v2.2.3)** — The breakdown table's Saved column was inverted in earlier 2.2.x; cleanup now shows correct savings per row.
|
|
239
|
-
- **85 tests (was 73)** — New round-trip coverage for path containment, restore guards, breakdown sign, restore-selection dedup, atomic tokenizer flush, and custom detector injection.
|
|
240
|
-
|
|
241
|
-
## v2.2 — What's new
|
|
242
|
-
|
|
243
|
-
- **Atomic `stale_project` clean/restore** — Single directory `rename()` instead of per-file loop. No more partial-failure state if the operation is interrupted.
|
|
244
|
-
- **Clear collision errors** — Cleaning a project whose backup already exists (or restoring onto an existing directory) now fails with an actionable message instead of a cryptic OS error.
|
|
245
|
-
- **Manifest schema v2** — Single JSON file (`manifest.json`) containing only currently-disabled entries. Restore removes the entry entirely, so the manifest stays bounded across many clean/restore cycles.
|
|
246
|
-
- **Automatic migration from v1** — Existing legacy manifests (`.claude-slim-manifest.jsonl`) are auto-migrated on first read; the original is preserved as `.jsonl.bak` for safety.
|
|
247
|
-
- **Crash-safe manifest writes** — Write-to-tmp-then-rename pattern prevents partial-write corruption on power loss or SIGKILL.
|
|
248
|
-
- **Expanded test coverage** — 66 tests (was 35). New round-trip tests per issue type: `broken_symlink`, `template`, `duplicate`, `skill_dup`, `oversized_skill`, `temp_cache`, `stale_project`. Plus manifest migration + bounded-growth cycle tests.
|
|
249
|
-
|
|
250
|
-
## v2.0 — What's new
|
|
251
|
-
|
|
252
|
-
- **TypeScript CLI** — Rewritten from bash. Faster, more accurate, extensible.
|
|
253
|
-
- **Accurate token counting** — [js-tiktoken](https://github.com/nicolo-ribaudo/js-tiktoken) with `cl100k_base` encoding.
|
|
254
|
-
- **Savings report box** — Visual before/after with breakdown table and monthly savings estimate.
|
|
255
|
-
- **`--dry-run`** — Preview changes without making them.
|
|
256
|
-
- **`--json`** — Machine-readable output for automation.
|
|
257
|
-
- **Token cache** — Instant repeat scans.
|
|
258
|
-
- **Standalone CLI** — `npx claude-slim` works outside Claude Code.
|
|
259
|
-
- **`--auto`** — Non-interactive cleanup for CI/scripts (Tier 1 only).
|
|
260
|
-
- **Disabled plugin detection** — Finds plugins you disabled but didn't uninstall.
|
|
261
|
-
- **Stale project detection** — Flags project memory untouched for 90+ days.
|
|
262
|
-
- **CLAUDE.md section breakdown** — See which plugin instructions cost the most tokens.
|
|
263
|
-
- **Plugin status** — Shows enabled/disabled status for each plugin.
|
|
264
|
-
- **Non-TTY support** — Auto-selects Tier 1 when stdin is piped.
|
|
265
|
-
- **Unit tests** — Vitest-based.
|
|
214
|
+
## v2.7.2 — What's new
|
|
215
|
+
|
|
216
|
+
Correctness patch fixing three HIGH-severity issues from a post-2.7.1 review. No new features; no breaking changes.
|
|
217
|
+
|
|
218
|
+
- **Friendlier `unused_plugin` cleanup when the `claude` CLI is missing.** Users running claude-slim outside a Claude Code install used to see raw `spawn claude ENOENT` — one row per selected plugin. The cleaner now probes the CLI once up front via `isClaudeCliAvailable()`; failing items become `skipped` (not `errored`), and a single grouped warning points at the manual `claude plugin disable <name>` workaround.
|
|
219
|
+
- **Nested-skill scanner now covers 3-deep layouts.** The prior 2-level walk silently missed `skills/<org>/<group>/<skill>/SKILL.md` — token totals under-reported and `unused_skill` could never flag them. Replaced with a bounded recursive walk (`MAX_SKILL_DEPTH = 3`) that stops descending once a `SKILL.md` is found, so nested docs under a declared skill don't become phantom duplicates.
|
|
220
|
+
- **`scan --json` output contract locked in.** New test spies on `console.log` / `console.info` / `console.warn` during the full scan pipeline (both minimal and rich fixtures — session logs, plugin cache, memory, CLAUDE.md) and fails if any fires. No stray writes exist today; the invariant is now enforced so a future refactor can't silently break `claude-slim scan --json | jq`.
|
|
221
|
+
|
|
222
|
+
Tests: 190 → 206 (+16).
|
|
223
|
+
|
|
224
|
+
For older release notes, see [CHANGELOG.md](CHANGELOG.md).
|
|
266
225
|
|
|
267
226
|
---
|
|
268
227
|
|
package/dist/cleaner.d.ts
CHANGED
|
@@ -6,6 +6,12 @@ export interface CleanResult {
|
|
|
6
6
|
name: string;
|
|
7
7
|
error: string;
|
|
8
8
|
}>;
|
|
9
|
+
/**
|
|
10
|
+
* Populated when one or more `unused_plugin` items were requested but the
|
|
11
|
+
* `claude` CLI is missing from PATH. The CLI surfaces this once at the top of
|
|
12
|
+
* the error block instead of N repeat rows.
|
|
13
|
+
*/
|
|
14
|
+
claudeCliMissing?: boolean;
|
|
9
15
|
}
|
|
10
16
|
export declare function cleanIssues(issues: Issue[]): Promise<CleanResult>;
|
|
11
17
|
export declare function restoreItem(entry: ManifestEntry | DisabledPluginEntry): Promise<void>;
|
package/dist/cleaner.js
CHANGED
|
@@ -2,7 +2,7 @@ import { rename, readdir, rmdir, rm, unlink, lstat, mkdir } from 'node:fs/promis
|
|
|
2
2
|
import { join, dirname, resolve, sep } from 'node:path';
|
|
3
3
|
import { appendManifest, ensureDisabledDir, getDisabledDir, removeEntry, recordDisabledPlugin, removeDisabledPlugin } from './manifest.js';
|
|
4
4
|
import { assertInsideClaudeDir, getSkillsDir, getProjectsDir } from './paths.js';
|
|
5
|
-
import { disablePlugin, enablePlugin } from './plugin-runtime.js';
|
|
5
|
+
import { disablePlugin, enablePlugin, isClaudeCliAvailable, ClaudeCliMissingError } from './plugin-runtime.js';
|
|
6
6
|
// Restrict a restore target to a specific subtree of ~/.claude/. Complements
|
|
7
7
|
// assertInsideClaudeDir: a tampered manifest could still name a legal
|
|
8
8
|
// ~/.claude/ path that belongs to a different type of asset (e.g. redirect a
|
|
@@ -45,6 +45,14 @@ export async function cleanIssues(issues) {
|
|
|
45
45
|
const moved = [];
|
|
46
46
|
const skipped = [];
|
|
47
47
|
const errors = [];
|
|
48
|
+
// Pre-check: if any unused_plugin items are selected, verify `claude` CLI is
|
|
49
|
+
// reachable before we start. Failing fast with a single friendly message
|
|
50
|
+
// beats N raw ENOENTs mid-run. Skip the probe if no plugin items were picked.
|
|
51
|
+
const wantsPluginOps = issues.some((i) => i.type === 'unused_plugin');
|
|
52
|
+
let claudeCliMissing = false;
|
|
53
|
+
if (wantsPluginOps && !(await isClaudeCliAvailable())) {
|
|
54
|
+
claudeCliMissing = true;
|
|
55
|
+
}
|
|
48
56
|
for (const issue of issues) {
|
|
49
57
|
try {
|
|
50
58
|
// unused_plugin and report-only types don't touch filesystem paths directly
|
|
@@ -132,6 +140,12 @@ export async function cleanIssues(issues) {
|
|
|
132
140
|
moved.push(entry);
|
|
133
141
|
}
|
|
134
142
|
else if (issue.type === 'unused_plugin') {
|
|
143
|
+
// Pre-check flagged `claude` missing — skip silently; the CLI prints
|
|
144
|
+
// one grouped message after cleanIssues returns.
|
|
145
|
+
if (claudeCliMissing) {
|
|
146
|
+
skipped.push(issue.name);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
135
149
|
await disablePlugin(issue.name);
|
|
136
150
|
// Record in manifest; if that fails, roll back the disable
|
|
137
151
|
try {
|
|
@@ -150,13 +164,21 @@ export async function cleanIssues(issues) {
|
|
|
150
164
|
}
|
|
151
165
|
}
|
|
152
166
|
catch (err) {
|
|
167
|
+
// Race window: `claude` was on PATH at pre-check but gone by the time we
|
|
168
|
+
// shelled out. Convert to the same grouped skip path instead of a raw
|
|
169
|
+
// ENOENT row.
|
|
170
|
+
if (err instanceof ClaudeCliMissingError) {
|
|
171
|
+
claudeCliMissing = true;
|
|
172
|
+
skipped.push(issue.name);
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
153
175
|
const message = err instanceof Error ? err.message : String(err);
|
|
154
176
|
errors.push({ name: issue.name, error: message });
|
|
155
177
|
}
|
|
156
178
|
}
|
|
157
179
|
// Clean empty directories in skills/
|
|
158
180
|
await cleanEmptyDirs(getSkillsDir());
|
|
159
|
-
return { moved, skipped, errors };
|
|
181
|
+
return { moved, skipped, errors, claudeCliMissing };
|
|
160
182
|
}
|
|
161
183
|
async function cleanEmptyDirs(dir) {
|
|
162
184
|
try {
|
package/dist/cli.js
CHANGED
|
@@ -275,6 +275,12 @@ async function runCleanPipeline(opts) {
|
|
|
275
275
|
console.log('');
|
|
276
276
|
console.log(formatReportBox(reportData));
|
|
277
277
|
console.log('');
|
|
278
|
+
if (cleanResult.claudeCliMissing) {
|
|
279
|
+
const skippedPluginCount = selectedIssues.filter((i) => i.type === 'unused_plugin').length;
|
|
280
|
+
console.log(` \x1b[33m⚠ \`claude\` CLI not found on PATH — skipped ${skippedPluginCount} plugin(s).\x1b[0m`);
|
|
281
|
+
console.log(' Install Claude Code and re-run to disable unused plugins,');
|
|
282
|
+
console.log(' or disable them manually via `claude plugin disable <name>`.\n');
|
|
283
|
+
}
|
|
278
284
|
if (cleanResult.errors.length > 0) {
|
|
279
285
|
console.log(' \x1b[31mErrors:\x1b[0m');
|
|
280
286
|
for (const err of cleanResult.errors) {
|
package/dist/plugin-runtime.d.ts
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sentinel error class raised when the `claude` CLI is not on PATH.
|
|
3
|
+
* Callers (cleaner.ts) treat this as a "skip, don't error" condition so users
|
|
4
|
+
* running claude-slim outside a Claude Code install don't see a raw ENOENT
|
|
5
|
+
* mid-cleanup.
|
|
6
|
+
*/
|
|
7
|
+
export declare class ClaudeCliMissingError extends Error {
|
|
8
|
+
constructor();
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Best-effort probe: is `claude` on PATH and answering `--version`?
|
|
12
|
+
* Never throws — a false result short-circuits `unused_plugin` cleanup with a
|
|
13
|
+
* friendly message rather than surfacing spawn ENOENT to end users.
|
|
14
|
+
*/
|
|
15
|
+
export declare function isClaudeCliAvailable(): Promise<boolean>;
|
|
1
16
|
/** Validates plugin name then shells out to `claude plugin disable <name>`. */
|
|
2
17
|
export declare function disablePlugin(name: string): Promise<void>;
|
|
3
18
|
/** Validates plugin name then shells out to `claude plugin enable <name>`. */
|
package/dist/plugin-runtime.js
CHANGED
|
@@ -1,14 +1,43 @@
|
|
|
1
1
|
import { execFile } from 'node:child_process';
|
|
2
2
|
const PLUGIN_NAME_RE = /^[a-zA-Z0-9_-]+$/;
|
|
3
|
+
/**
|
|
4
|
+
* Sentinel error class raised when the `claude` CLI is not on PATH.
|
|
5
|
+
* Callers (cleaner.ts) treat this as a "skip, don't error" condition so users
|
|
6
|
+
* running claude-slim outside a Claude Code install don't see a raw ENOENT
|
|
7
|
+
* mid-cleanup.
|
|
8
|
+
*/
|
|
9
|
+
export class ClaudeCliMissingError extends Error {
|
|
10
|
+
constructor() {
|
|
11
|
+
super("`claude` CLI not found on PATH. Install Claude Code (https://claude.com/product/claude-code) " +
|
|
12
|
+
'to enable/disable plugins, or run `claude-slim clean` without the unused-plugin items selected.');
|
|
13
|
+
this.name = 'ClaudeCliMissingError';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
3
16
|
function validateName(name) {
|
|
4
17
|
if (!PLUGIN_NAME_RE.test(name)) {
|
|
5
18
|
throw new Error(`Refusing to operate on suspicious plugin name: ${name}`);
|
|
6
19
|
}
|
|
7
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Detects the classic "binary missing from PATH" shape of Node's execFile error.
|
|
23
|
+
* spawn ENOENT surfaces as an Error with { code: 'ENOENT', syscall: 'spawn claude' }.
|
|
24
|
+
* The exact-match on `spawn claude` avoids false positives for hypothetical
|
|
25
|
+
* sibling binaries (e.g. `claude-code`) that might be spawned by future code.
|
|
26
|
+
*/
|
|
27
|
+
function isClaudeMissing(err) {
|
|
28
|
+
if (!(err instanceof Error))
|
|
29
|
+
return false;
|
|
30
|
+
const e = err;
|
|
31
|
+
return e.code === 'ENOENT' && e.syscall === 'spawn claude';
|
|
32
|
+
}
|
|
8
33
|
function runPluginCommand(subcommand, name) {
|
|
9
34
|
return new Promise((resolve, reject) => {
|
|
10
35
|
execFile('claude', ['plugin', subcommand, name], { timeout: 30000 }, (err, _stdout, stderr) => {
|
|
11
36
|
if (err) {
|
|
37
|
+
if (isClaudeMissing(err)) {
|
|
38
|
+
reject(new ClaudeCliMissingError());
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
12
41
|
const detail = stderr?.trim() ? `: ${stderr.trim()}` : '';
|
|
13
42
|
reject(new Error(`${err.message}${detail}`));
|
|
14
43
|
return;
|
|
@@ -21,6 +50,18 @@ function runPluginCommand(subcommand, name) {
|
|
|
21
50
|
});
|
|
22
51
|
});
|
|
23
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Best-effort probe: is `claude` on PATH and answering `--version`?
|
|
55
|
+
* Never throws — a false result short-circuits `unused_plugin` cleanup with a
|
|
56
|
+
* friendly message rather than surfacing spawn ENOENT to end users.
|
|
57
|
+
*/
|
|
58
|
+
export function isClaudeCliAvailable() {
|
|
59
|
+
return new Promise((resolve) => {
|
|
60
|
+
execFile('claude', ['--version'], { timeout: 5000 }, (err) => {
|
|
61
|
+
resolve(!err);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
}
|
|
24
65
|
/** Validates plugin name then shells out to `claude plugin disable <name>`. */
|
|
25
66
|
export async function disablePlugin(name) {
|
|
26
67
|
validateName(name);
|
package/dist/scanner/index.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
// INVARIANT: nothing in the scanner (this file or anything under scanner/**)
|
|
2
|
+
// may write to stdout. The CLI pipes stdout of `scan --json` to jq/other
|
|
3
|
+
// tools; a stray console.log would silently corrupt machine-readable output.
|
|
4
|
+
// Route diagnostics through console.error. Enforced by
|
|
5
|
+
// src/__tests__/scan-stdout-invariant.test.ts.
|
|
1
6
|
import { join } from 'node:path';
|
|
2
7
|
import { countTokensCached } from '../tokenizer.js';
|
|
3
8
|
import { getClaudeDir } from '../paths.js';
|
|
@@ -20,24 +20,30 @@ export function dedupeBySymlink(candidates) {
|
|
|
20
20
|
}
|
|
21
21
|
return Array.from(seen.values());
|
|
22
22
|
}
|
|
23
|
+
// Max depth for the nested-skill walk. Depth 1 = ~/.claude/skills/<a>/SKILL.md,
|
|
24
|
+
// depth 2 = ~/.claude/skills/<a>/<b>/SKILL.md, etc. Depth 3 covers the deepest
|
|
25
|
+
// layouts seen in the wild (e.g. plugin-namespaced groups like
|
|
26
|
+
// skills/<org>/<group>/<skill>/SKILL.md) while keeping the walk finite.
|
|
27
|
+
// If a directory contains a SKILL.md we stop descending — nested SKILL.md
|
|
28
|
+
// files under an already-declared skill would just create phantom duplicates.
|
|
29
|
+
const MAX_SKILL_DEPTH = 3;
|
|
23
30
|
export async function scanLocalSkills() {
|
|
24
31
|
const skillsDir = getSkillsDir();
|
|
25
32
|
const candidates = [];
|
|
26
33
|
const brokenSymlinks = [];
|
|
27
34
|
const contents = new Map();
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const dirPath = join(skillsDir, entry);
|
|
31
|
-
if (!(await isDirectory(dirPath)))
|
|
35
|
+
async function visit(dirPath, nameParts, depth) {
|
|
36
|
+
if (depth > MAX_SKILL_DEPTH)
|
|
32
37
|
return;
|
|
33
38
|
const skillMd = join(dirPath, 'SKILL.md');
|
|
39
|
+
const displayName = nameParts.join('/');
|
|
34
40
|
if (await isBrokenSymlink(skillMd)) {
|
|
35
41
|
let target = 'unknown';
|
|
36
42
|
try {
|
|
37
43
|
target = await readlink(skillMd);
|
|
38
44
|
}
|
|
39
45
|
catch { /* */ }
|
|
40
|
-
brokenSymlinks.push({ name:
|
|
46
|
+
brokenSymlinks.push({ name: displayName, path: skillMd, target });
|
|
41
47
|
return;
|
|
42
48
|
}
|
|
43
49
|
const content = await safeReadFile(skillMd);
|
|
@@ -47,7 +53,7 @@ export async function scanLocalSkills() {
|
|
|
47
53
|
const realMdPath = await resolveRealPath(skillMd);
|
|
48
54
|
candidates.push({
|
|
49
55
|
skill: {
|
|
50
|
-
name:
|
|
56
|
+
name: displayName,
|
|
51
57
|
path: dirPath,
|
|
52
58
|
sizeBytes: Buffer.byteLength(content),
|
|
53
59
|
tokens,
|
|
@@ -55,43 +61,31 @@ export async function scanLocalSkills() {
|
|
|
55
61
|
},
|
|
56
62
|
realMdPath,
|
|
57
63
|
});
|
|
64
|
+
// Stop descending: nested SKILL.md files under a declared skill are
|
|
65
|
+
// documentation/examples, not addressable skills.
|
|
66
|
+
return;
|
|
58
67
|
}
|
|
59
|
-
|
|
68
|
+
if (depth === MAX_SKILL_DEPTH)
|
|
69
|
+
return;
|
|
60
70
|
const subEntries = await safeReaddir(dirPath);
|
|
61
|
-
|
|
71
|
+
await Promise.all(subEntries.map(async (sub) => {
|
|
62
72
|
const subDir = join(dirPath, sub);
|
|
73
|
+
// isDirectory() uses stat(), which follows symlinks — intentional so
|
|
74
|
+
// users can symlink shared skills into ~/.claude/skills/. A cycle
|
|
75
|
+
// through symlinks would be bounded by MAX_SKILL_DEPTH, not by us
|
|
76
|
+
// detecting the loop directly.
|
|
63
77
|
if (!(await isDirectory(subDir)))
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const subContent = await safeReadFile(subSkillMd);
|
|
76
|
-
if (subContent !== null) {
|
|
77
|
-
const name = `${entry}/${sub}`;
|
|
78
|
-
contents.set(subSkillMd, subContent);
|
|
79
|
-
const tokens = countTokensCached(subContent, subSkillMd);
|
|
80
|
-
const realMdPath = await resolveRealPath(subSkillMd);
|
|
81
|
-
candidates.push({
|
|
82
|
-
skill: {
|
|
83
|
-
name,
|
|
84
|
-
path: subDir,
|
|
85
|
-
sizeBytes: Buffer.byteLength(subContent),
|
|
86
|
-
tokens,
|
|
87
|
-
source: 'local',
|
|
88
|
-
},
|
|
89
|
-
realMdPath,
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
await Promise.all(scanPromises);
|
|
78
|
+
return;
|
|
79
|
+
await visit(subDir, [...nameParts, sub], depth + 1);
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
const topEntries = await safeReaddir(skillsDir);
|
|
83
|
+
await Promise.all(topEntries.map(async (entry) => {
|
|
84
|
+
const dirPath = join(skillsDir, entry);
|
|
85
|
+
if (!(await isDirectory(dirPath)))
|
|
86
|
+
return;
|
|
87
|
+
await visit(dirPath, [entry], 1);
|
|
88
|
+
}));
|
|
95
89
|
const skills = dedupeBySymlink(candidates);
|
|
96
90
|
return { skills, brokenSymlinks, contents };
|
|
97
91
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-slim",
|
|
3
|
-
"version": "2.7.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.7.3",
|
|
4
|
+
"description": "Cut Claude Code startup token overhead — non-destructive scan, tiered proposals, one-command restore. Finds unused skills, duplicate registrations, stale memory, and heavyweight plugins in ~/.claude/.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"claude-slim": "./dist/cli.js"
|
|
@@ -23,11 +23,19 @@
|
|
|
23
23
|
"keywords": [
|
|
24
24
|
"claude",
|
|
25
25
|
"claude-code",
|
|
26
|
+
"anthropic",
|
|
26
27
|
"token",
|
|
27
|
-
"optimization",
|
|
28
|
+
"token-optimization",
|
|
29
|
+
"context-bloat",
|
|
30
|
+
"prompt-optimization",
|
|
31
|
+
"system-prompt",
|
|
28
32
|
"cleanup",
|
|
29
33
|
"skills",
|
|
30
|
-
"
|
|
34
|
+
"skill-management",
|
|
35
|
+
"plugin",
|
|
36
|
+
"plugin-cleanup",
|
|
37
|
+
"cli",
|
|
38
|
+
"developer-experience"
|
|
31
39
|
],
|
|
32
40
|
"author": "iops-leo",
|
|
33
41
|
"license": "MIT",
|