claude-slim 2.11.0 → 2.12.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 +5 -8
- package/dist/cli.js +76 -0
- package/dist/update-run.d.ts +40 -0
- package/dist/update-run.js +101 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -165,6 +165,7 @@ npx claude-slim scan --no-codex # Skip the ~/.codex scan
|
|
|
165
165
|
npx claude-slim doctor # Diagnose Node/Claude/session-log readiness
|
|
166
166
|
npx claude-slim doctor --offline # Same, without the version check (no network)
|
|
167
167
|
npx claude-slim check-update # Report-only version check
|
|
168
|
+
npx claude-slim update # Run the upgrade for this install method
|
|
168
169
|
npx claude-slim restore # Undo
|
|
169
170
|
npx claude-slim report # Show savings from last clean
|
|
170
171
|
```
|
|
@@ -247,16 +248,12 @@ Token counts come from [js-tiktoken](https://github.com/nicolo-ribaudo/js-tiktok
|
|
|
247
248
|
|
|
248
249
|
---
|
|
249
250
|
|
|
250
|
-
## v2.
|
|
251
|
+
## v2.12.0 — What's new
|
|
251
252
|
|
|
252
|
-
|
|
253
|
+
- **`claude-slim update`** — runs the upgrade command for however this copy was installed, after showing it and asking. `--dry-run` to preview, `--yes` to skip the prompt. Plugin installs get the marketplace refresh first, then the qualified `claude-slim@claude-slim` id, and are reminded that a restart is needed. npx and source checkouts run nothing and say why — npx already resolves the latest every invocation, and pulling your own repository is not claude-slim's to do.
|
|
254
|
+
- **This corrects v2.9.0's reasoning.** That release stopped at detection, arguing updating belonged to the package manager. Half held: claude-slim must not write into a directory `claude plugin` owns. The other half did not — invoking the package manager is something this tool already does during cleanup (`claude plugin disable`), so refusing to here was inconsistent. It still writes nothing itself.
|
|
253
255
|
|
|
254
|
-
|
|
255
|
-
- **`~/.codex/skills.disabled/`** — moves are reversible through the same `restore`, and manifest entries record which agent they came from.
|
|
256
|
-
- **The path guard is now per-agent.** Not "inside any known root" — a Codex issue must not resolve into `~/.claude/` and vice versa, so a tampered manifest cannot cross between agents.
|
|
257
|
-
- `unused_skill` and `oversized_memory` remain unavailable for Codex: no invocation record, and no `~/.codex/projects/*/memory/`.
|
|
258
|
-
|
|
259
|
-
Tests: 347 → 374 (+27), the guard suite being the point — cross-agent isolation both directions, traversal escapes, and a `~/.claude-backup` sibling a naive `startsWith` would have allowed.
|
|
256
|
+
Tests: 374 → 392 (+18), pinning the safety properties — every argv a fixed literal, no shell metacharacters, executable only ever `claude` or `npm`, and a refusal to run unattended with no TTY and no `--yes`.
|
|
260
257
|
|
|
261
258
|
For older release notes, see [CHANGELOG.md](CHANGELOG.md).
|
|
262
259
|
|
package/dist/cli.js
CHANGED
|
@@ -11,6 +11,7 @@ import { readManifest } from './manifest.js';
|
|
|
11
11
|
import { formatScanSummary, formatReportBox, calculateReport, } from './report.js';
|
|
12
12
|
import { collectDoctorReport, formatDoctorReport } from './doctor.js';
|
|
13
13
|
import { checkForUpdate, formatUpdateNotice } from './update-check.js';
|
|
14
|
+
import { confirmDecision, planUpdate, renderStep, runUpdate } from './update-run.js';
|
|
14
15
|
import { scanCodex } from './codex/index.js';
|
|
15
16
|
import { formatCodexSummary } from './codex/report.js';
|
|
16
17
|
import { classifyCodexIssues } from './codex/detectors.js';
|
|
@@ -101,6 +102,81 @@ program
|
|
|
101
102
|
console.log(`\n \x1b[32m✓\x1b[0m claude-slim ${result.installed} is up to date.\n`);
|
|
102
103
|
}
|
|
103
104
|
});
|
|
105
|
+
// --- update ---
|
|
106
|
+
// Detection lives in `check-update`; this runs the command that detection
|
|
107
|
+
// identified. claude-slim still never writes into a package manager's
|
|
108
|
+
// directories itself — it invokes the manager, the same way cleanup already
|
|
109
|
+
// invokes `claude plugin disable`.
|
|
110
|
+
program
|
|
111
|
+
.command('update')
|
|
112
|
+
.description('Run the upgrade command for however this copy was installed')
|
|
113
|
+
.option('--yes', 'Skip the confirmation prompt')
|
|
114
|
+
.option('--dry-run', 'Show the commands without running them')
|
|
115
|
+
.action(async (opts) => {
|
|
116
|
+
const result = await checkForUpdate({ force: true });
|
|
117
|
+
const plan = planUpdate(result.installMethod);
|
|
118
|
+
console.log('');
|
|
119
|
+
console.log(` Installed: ${result.installed} Latest: ${result.latest ?? 'unknown'}`);
|
|
120
|
+
console.log(` Install method: ${result.installMethod}`);
|
|
121
|
+
console.log('');
|
|
122
|
+
if (result.latest === null) {
|
|
123
|
+
console.log(' Could not reach the npm registry, so there is nothing to compare against.');
|
|
124
|
+
console.log('');
|
|
125
|
+
process.exitCode = 1;
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (!result.outdated) {
|
|
129
|
+
console.log(' \x1b[32m✓\x1b[0m Already up to date.\n');
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (!plan.runnable) {
|
|
133
|
+
console.log(` ${plan.guidance}\n`);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
console.log(' Will run:');
|
|
137
|
+
for (const step of plan.steps)
|
|
138
|
+
console.log(` ${renderStep(step)}`);
|
|
139
|
+
console.log('');
|
|
140
|
+
if (opts.dryRun) {
|
|
141
|
+
console.log(' \x1b[90mDry run — nothing was executed.\x1b[0m\n');
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const gate = confirmDecision(opts, Boolean(process.stdin.isTTY));
|
|
145
|
+
if (gate === 'refuse') {
|
|
146
|
+
console.error(' Refusing to run unattended. Re-run with --yes to confirm.\n');
|
|
147
|
+
process.exitCode = 1;
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (gate === 'prompt') {
|
|
151
|
+
const answer = await askUser(' Proceed? [y/N] ');
|
|
152
|
+
if (!/^y(es)?$/i.test(answer)) {
|
|
153
|
+
console.log(' Cancelled.\n');
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
console.log('');
|
|
157
|
+
}
|
|
158
|
+
const results = await runUpdate(plan);
|
|
159
|
+
for (const r of results) {
|
|
160
|
+
const mark = r.ok ? '\x1b[32m✓\x1b[0m' : '\x1b[31m✗\x1b[0m';
|
|
161
|
+
console.log(` ${mark} ${renderStep(r.step)}`);
|
|
162
|
+
if (r.output)
|
|
163
|
+
console.log(r.output.split('\n').map((l) => ` ${l}`).join('\n'));
|
|
164
|
+
}
|
|
165
|
+
const failed = results.find((r) => !r.ok);
|
|
166
|
+
console.log('');
|
|
167
|
+
if (failed) {
|
|
168
|
+
console.log(' Update did not complete. Run the commands above manually to see the full error.\n');
|
|
169
|
+
process.exitCode = 1;
|
|
170
|
+
}
|
|
171
|
+
else if (result.installMethod === 'plugin') {
|
|
172
|
+
// The new version is on disk but the running session still holds the old
|
|
173
|
+
// one — without saying so, users update, re-run, and see identical output.
|
|
174
|
+
console.log(' \x1b[33m!\x1b[0m Restart Claude Code for the update to take effect.\n');
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
console.log(' \x1b[32m✓\x1b[0m Updated.\n');
|
|
178
|
+
}
|
|
179
|
+
});
|
|
104
180
|
// --- clean ---
|
|
105
181
|
program
|
|
106
182
|
.command('clean')
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { InstallMethod } from './update-check.js';
|
|
2
|
+
export interface UpdateStep {
|
|
3
|
+
file: string;
|
|
4
|
+
args: string[];
|
|
5
|
+
}
|
|
6
|
+
export interface UpdatePlan {
|
|
7
|
+
/** false when there is nothing to execute — see `guidance`. */
|
|
8
|
+
runnable: boolean;
|
|
9
|
+
steps: UpdateStep[];
|
|
10
|
+
/** Why nothing runs, for the methods where that is the correct answer. */
|
|
11
|
+
guidance?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function planUpdate(method: InstallMethod): UpdatePlan;
|
|
14
|
+
/** Human-readable rendering of a step, matching what the user would type. */
|
|
15
|
+
export declare function renderStep(step: UpdateStep): string;
|
|
16
|
+
export interface StepResult {
|
|
17
|
+
step: UpdateStep;
|
|
18
|
+
ok: boolean;
|
|
19
|
+
output: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Run the plan's steps in order, stopping at the first failure.
|
|
23
|
+
*
|
|
24
|
+
* Sequential rather than parallel, and halting on error, because step 2 depends
|
|
25
|
+
* on step 1 having refreshed the marketplace — running it against a stale
|
|
26
|
+
* manifest would report success while changing nothing.
|
|
27
|
+
*/
|
|
28
|
+
export declare function runUpdate(plan: UpdatePlan): Promise<StepResult[]>;
|
|
29
|
+
export type ConfirmDecision = 'run' | 'prompt' | 'refuse';
|
|
30
|
+
/**
|
|
31
|
+
* Decide how to gate an update that will modify the user's install.
|
|
32
|
+
*
|
|
33
|
+
* Refusing when there is no TTY is the important case: piped into a script or a
|
|
34
|
+
* CI job, there is nobody to answer the prompt, and silently proceeding would
|
|
35
|
+
* mean a tool that changes an installation without anyone agreeing to it.
|
|
36
|
+
* `--yes` is how you say so deliberately.
|
|
37
|
+
*/
|
|
38
|
+
export declare function confirmDecision(opts: {
|
|
39
|
+
yes?: boolean;
|
|
40
|
+
}, isTTY: boolean): ConfirmDecision;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
// Running the upgrade.
|
|
3
|
+
//
|
|
4
|
+
// v2.9.0 shipped detection only, on the reasoning that updating is the package
|
|
5
|
+
// manager's job. Half of that was right and half was not. Writing into the
|
|
6
|
+
// plugin directory ourselves would indeed corrupt an install — but *invoking*
|
|
7
|
+
// the package manager is something this tool already does elsewhere
|
|
8
|
+
// (`claude plugin disable` during unused-plugin cleanup). Refusing to invoke it
|
|
9
|
+
// here was inconsistent, not principled.
|
|
10
|
+
//
|
|
11
|
+
// So: still never write to those directories directly. Just run the command the
|
|
12
|
+
// user would have typed, after showing it to them.
|
|
13
|
+
//
|
|
14
|
+
// Every argv below is a fixed literal. Nothing from the environment, the
|
|
15
|
+
// manifest, or user input is interpolated, and execFile never routes through a
|
|
16
|
+
// shell — so there is no argument-injection surface to guard.
|
|
17
|
+
const STEP_TIMEOUT_MS = 180_000;
|
|
18
|
+
export function planUpdate(method) {
|
|
19
|
+
switch (method) {
|
|
20
|
+
case 'plugin':
|
|
21
|
+
return {
|
|
22
|
+
runnable: true,
|
|
23
|
+
steps: [
|
|
24
|
+
// Refresh the marketplace first; otherwise `plugin update` compares
|
|
25
|
+
// against a stale manifest and reports "already up to date".
|
|
26
|
+
{ file: 'claude', args: ['plugin', 'marketplace', 'update', 'claude-slim'] },
|
|
27
|
+
// The qualified id is required: the bare name fails with
|
|
28
|
+
// `Plugin "claude-slim" not found` when a marketplace shares its name.
|
|
29
|
+
{ file: 'claude', args: ['plugin', 'update', 'claude-slim@claude-slim'] },
|
|
30
|
+
],
|
|
31
|
+
};
|
|
32
|
+
case 'global':
|
|
33
|
+
return {
|
|
34
|
+
runnable: true,
|
|
35
|
+
steps: [{ file: 'npm', args: ['install', '-g', 'claude-slim@latest'] }],
|
|
36
|
+
};
|
|
37
|
+
case 'npx':
|
|
38
|
+
return {
|
|
39
|
+
runnable: false,
|
|
40
|
+
steps: [],
|
|
41
|
+
guidance: 'npx resolves the latest version on every invocation, so there is nothing to update. ' +
|
|
42
|
+
'If a stale copy is cached, run `npx claude-slim@latest` once to refresh it.',
|
|
43
|
+
};
|
|
44
|
+
case 'source':
|
|
45
|
+
return {
|
|
46
|
+
runnable: false,
|
|
47
|
+
steps: [],
|
|
48
|
+
guidance: 'This is a source checkout. Updating it means pulling your own repository, ' +
|
|
49
|
+
'which claude-slim will not do on your behalf — run `git pull && npm install` yourself.',
|
|
50
|
+
};
|
|
51
|
+
default:
|
|
52
|
+
return {
|
|
53
|
+
runnable: false,
|
|
54
|
+
steps: [],
|
|
55
|
+
guidance: 'Could not tell how this copy was installed, so no upgrade command can be chosen safely. ' +
|
|
56
|
+
'Update it the same way you installed it.',
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** Human-readable rendering of a step, matching what the user would type. */
|
|
61
|
+
export function renderStep(step) {
|
|
62
|
+
return [step.file, ...step.args].join(' ');
|
|
63
|
+
}
|
|
64
|
+
function runStep(step) {
|
|
65
|
+
return new Promise((resolve) => {
|
|
66
|
+
execFile(step.file, step.args, { timeout: STEP_TIMEOUT_MS }, (err, stdout, stderr) => {
|
|
67
|
+
const output = [stdout, stderr].map((s) => s?.trim()).filter(Boolean).join('\n');
|
|
68
|
+
resolve({ step, ok: !err, output: err ? `${err.message}${output ? `\n${output}` : ''}` : output });
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Run the plan's steps in order, stopping at the first failure.
|
|
74
|
+
*
|
|
75
|
+
* Sequential rather than parallel, and halting on error, because step 2 depends
|
|
76
|
+
* on step 1 having refreshed the marketplace — running it against a stale
|
|
77
|
+
* manifest would report success while changing nothing.
|
|
78
|
+
*/
|
|
79
|
+
export async function runUpdate(plan) {
|
|
80
|
+
const results = [];
|
|
81
|
+
for (const step of plan.steps) {
|
|
82
|
+
const result = await runStep(step);
|
|
83
|
+
results.push(result);
|
|
84
|
+
if (!result.ok)
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
return results;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Decide how to gate an update that will modify the user's install.
|
|
91
|
+
*
|
|
92
|
+
* Refusing when there is no TTY is the important case: piped into a script or a
|
|
93
|
+
* CI job, there is nobody to answer the prompt, and silently proceeding would
|
|
94
|
+
* mean a tool that changes an installation without anyone agreeing to it.
|
|
95
|
+
* `--yes` is how you say so deliberately.
|
|
96
|
+
*/
|
|
97
|
+
export function confirmDecision(opts, isTTY) {
|
|
98
|
+
if (opts.yes)
|
|
99
|
+
return 'run';
|
|
100
|
+
return isTTY ? 'prompt' : 'refuse';
|
|
101
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-slim",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.12.0",
|
|
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": {
|