cc-cream 0.1.0 → 0.1.2
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/CHANGELOG.md +24 -0
- package/README.md +20 -0
- package/package.json +2 -2
- package/src/install.js +106 -6
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,28 @@ All notable changes to cc-cream are documented here. Format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow
|
|
5
5
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.1.2] — 2026-05-29
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- **No install-time lifecycle scripts in the published package.** The git-hook
|
|
11
|
+
registration moved off the `prepare` lifecycle to an opt-in `npm run hooks`,
|
|
12
|
+
so `npm install`/`npx cc-cream` runs nothing automatically. Improves the
|
|
13
|
+
supply-chain posture (and Socket score) with no change to the runtime.
|
|
14
|
+
|
|
15
|
+
## [0.1.1] — 2026-05-29
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- **Uninstall** — `node src/install.js --uninstall` (and the `/cc-cream:uninstall`
|
|
19
|
+
plugin command) removes cc-cream's `statusLine` block, but only when it is
|
|
20
|
+
cc-cream's; a foreign statusLine is left untouched. Offers to delete the copied
|
|
21
|
+
runtime and session-state files, and keeps `~/.claude/cc-cream.json` unless
|
|
22
|
+
`--purge` is passed.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
- **Never overwrite a malformed `settings.json`** — the installer now refuses to
|
|
26
|
+
write when `settings.json` exists but fails to parse (or is not a JSON object),
|
|
27
|
+
instead of silently starting fresh and erasing the user's other settings.
|
|
28
|
+
|
|
7
29
|
## [0.1.0] — 2026-05-29
|
|
8
30
|
|
|
9
31
|
Initial public release. cc-cream reads the JSON Claude Code pipes to its status
|
|
@@ -37,4 +59,6 @@ line and prints a colored ≤3-row bar — zero tokens, the model never sees it.
|
|
|
37
59
|
- Supports **macOS and Linux**; Windows is a planned fast-follow.
|
|
38
60
|
- Requires Claude Code **2.1.132+** (`effort` / `thinking` need 2.1.145+).
|
|
39
61
|
|
|
62
|
+
[0.1.2]: https://github.com/bart-turczynski/cc-cream/compare/v0.1.1...v0.1.2
|
|
63
|
+
[0.1.1]: https://github.com/bart-turczynski/cc-cream/compare/v0.1.0...v0.1.1
|
|
40
64
|
[0.1.0]: https://github.com/bart-turczynski/cc-cream/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -109,6 +109,26 @@ The installer:
|
|
|
109
109
|
After install, Claude Code must be **trusted** for the directory (if prompted),
|
|
110
110
|
and you may need to **restart** it for the bar to appear.
|
|
111
111
|
|
|
112
|
+
### Uninstall
|
|
113
|
+
|
|
114
|
+
Plugin users:
|
|
115
|
+
```
|
|
116
|
+
/cc-cream:uninstall
|
|
117
|
+
/plugin uninstall cc-cream
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
npm / manual users:
|
|
121
|
+
```bash
|
|
122
|
+
node $(npm root -g)/cc-cream/src/install.js --uninstall # npm
|
|
123
|
+
node cc-cream/src/install.js --uninstall # manual clone
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Uninstall removes the `statusLine` block **only if it is cc-cream's** — a
|
|
127
|
+
statusLine you wired for something else is left untouched. It then offers to
|
|
128
|
+
delete the copied runtime and session-state files, and **keeps your
|
|
129
|
+
`~/.claude/cc-cream.json` config** unless you add `--purge`. Restart Claude Code
|
|
130
|
+
to clear the bar.
|
|
131
|
+
|
|
112
132
|
## Configuration
|
|
113
133
|
|
|
114
134
|
Every display decision is read from `~/.claude/cc-cream.json`. Edit it by hand
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cc-cream",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Claude Code cache/context/cost status-line tool",
|
|
5
5
|
"directories": {
|
|
6
6
|
"doc": "docs"
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"test:manual": "cucumber-js --profile manual",
|
|
15
15
|
"coverage": "c8 cucumber-js",
|
|
16
16
|
"watch": "cucumber-js --watch",
|
|
17
|
-
"
|
|
17
|
+
"hooks": "simple-git-hooks",
|
|
18
18
|
"prepublishOnly": "npm test"
|
|
19
19
|
},
|
|
20
20
|
"simple-git-hooks": {
|
package/src/install.js
CHANGED
|
@@ -42,6 +42,44 @@ function isInstalled(existing, command) {
|
|
|
42
42
|
);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
// True if an existing statusLine belongs to cc-cream under ANY install strategy
|
|
46
|
+
// (dev repo, copied home runtime, or the plugin cache-glob) — every command
|
|
47
|
+
// references the cc-cream entrypoint. Used by uninstall so we never touch a
|
|
48
|
+
// statusLine the user wired for something else.
|
|
49
|
+
function isCcCreamStatusLine(existing) {
|
|
50
|
+
return (
|
|
51
|
+
!!existing &&
|
|
52
|
+
typeof existing === 'object' &&
|
|
53
|
+
existing.type === 'command' &&
|
|
54
|
+
typeof existing.command === 'string' &&
|
|
55
|
+
existing.command.includes('cc-cream')
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Decide what an uninstall should do. Pure: returns { settings, changed, messages }.
|
|
60
|
+
// Removes ONLY a cc-cream statusLine; a foreign statusLine is left verbatim.
|
|
61
|
+
export function planUninstall(settings) {
|
|
62
|
+
const s = settings && typeof settings === 'object' ? settings : {};
|
|
63
|
+
const existing = s.statusLine;
|
|
64
|
+
const messages = [];
|
|
65
|
+
|
|
66
|
+
if (!isCcCreamStatusLine(existing)) {
|
|
67
|
+
if (existing && typeof existing === 'object') {
|
|
68
|
+
messages.push(
|
|
69
|
+
`Your statusLine is not cc-cream's — leaving it untouched:\n ${JSON.stringify(existing)}`,
|
|
70
|
+
);
|
|
71
|
+
} else {
|
|
72
|
+
messages.push('No cc-cream statusLine found in settings.json — nothing to remove.');
|
|
73
|
+
}
|
|
74
|
+
return { settings: s, changed: false, messages };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
messages.push(`Removing cc-cream statusLine:\n ${JSON.stringify(existing)}`);
|
|
78
|
+
const next = { ...s };
|
|
79
|
+
delete next.statusLine;
|
|
80
|
+
return { settings: next, changed: true, messages };
|
|
81
|
+
}
|
|
82
|
+
|
|
45
83
|
// Decide what to do. Returns { settings, changed, messages, needsConsent }.
|
|
46
84
|
// `consent` is the user's yes/no when an existing statusLine must be replaced.
|
|
47
85
|
//
|
|
@@ -96,6 +134,32 @@ function destinationPath() {
|
|
|
96
134
|
return path.join(os.homedir(), '.claude', 'cc-cream', 'cc-cream.js');
|
|
97
135
|
}
|
|
98
136
|
|
|
137
|
+
// Read settings.json safely. A MISSING or empty file -> {} (fresh start, nothing
|
|
138
|
+
// to lose). A file that exists with content but fails to parse, or parses to a
|
|
139
|
+
// non-object, is REFUSED: we exit rather than overwrite and erase the user's
|
|
140
|
+
// other settings (permissions, hooks, plugins...). This guards the one path
|
|
141
|
+
// where a blind write would be destructive.
|
|
142
|
+
function readSettings(file) {
|
|
143
|
+
if (!fs.existsSync(file)) return {};
|
|
144
|
+
const raw = fs.readFileSync(file, 'utf8');
|
|
145
|
+
if (raw.trim() === '') return {};
|
|
146
|
+
let parsed;
|
|
147
|
+
try {
|
|
148
|
+
parsed = JSON.parse(raw);
|
|
149
|
+
} catch {
|
|
150
|
+
console.error(`Error: ${file} is not valid JSON.`);
|
|
151
|
+
console.error('Refusing to write it — that would erase your other settings.');
|
|
152
|
+
console.error('Fix the JSON (or move the file aside) and re-run.');
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
156
|
+
console.error(`Error: ${file} does not contain a JSON object.`);
|
|
157
|
+
console.error('Refusing to overwrite it. Move it aside and re-run if intended.');
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
return parsed;
|
|
161
|
+
}
|
|
162
|
+
|
|
99
163
|
function runtimeFiles(sourceFile) {
|
|
100
164
|
const sourceDir = path.dirname(sourceFile);
|
|
101
165
|
return fs.readdirSync(sourceDir)
|
|
@@ -143,19 +207,55 @@ function ask(question) {
|
|
|
143
207
|
}));
|
|
144
208
|
}
|
|
145
209
|
|
|
210
|
+
// Remove the cc-cream wiring (and, with consent, its install artifacts). Keeps
|
|
211
|
+
// the user's config (~/.claude/cc-cream.json) unless `--purge` is passed.
|
|
212
|
+
async function uninstall({ purge }) {
|
|
213
|
+
const file = settingsPath();
|
|
214
|
+
const settings = readSettings(file);
|
|
215
|
+
const result = planUninstall(settings);
|
|
216
|
+
for (const m of result.messages) console.log(m);
|
|
217
|
+
if (result.changed) {
|
|
218
|
+
fs.writeFileSync(file, `${JSON.stringify(result.settings, null, 2)}\n`);
|
|
219
|
+
console.log(`\nUpdated ${file}.`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const home = path.join(os.homedir(), '.claude');
|
|
223
|
+
const runtimeDir = path.join(home, 'cc-cream');
|
|
224
|
+
const stateFile = path.join(home, 'cc-cream-state.json');
|
|
225
|
+
const configFile = path.join(home, 'cc-cream.json');
|
|
226
|
+
|
|
227
|
+
const artifacts = [runtimeDir, stateFile].filter((p) => fs.existsSync(p));
|
|
228
|
+
if (artifacts.length) {
|
|
229
|
+
const remove = purge || (await ask(`Also delete the copied runtime and session state?\n ${artifacts.join('\n ')}`));
|
|
230
|
+
if (remove) {
|
|
231
|
+
for (const p of artifacts) fs.rmSync(p, { recursive: true, force: true });
|
|
232
|
+
console.log('Removed runtime and state files.');
|
|
233
|
+
} else {
|
|
234
|
+
console.log('Left runtime and state files in place.');
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (purge && fs.existsSync(configFile)) {
|
|
238
|
+
fs.rmSync(configFile, { force: true });
|
|
239
|
+
console.log(`Removed config ${configFile}.`);
|
|
240
|
+
} else if (fs.existsSync(configFile)) {
|
|
241
|
+
console.log(`Kept your config ${configFile} (pass --purge to remove it too).`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
console.log('\nRestart Claude Code to drop the bar.');
|
|
245
|
+
}
|
|
246
|
+
|
|
146
247
|
async function main() {
|
|
147
248
|
const args = process.argv.slice(2);
|
|
249
|
+
if (args.includes('--uninstall')) {
|
|
250
|
+
await uninstall({ purge: args.includes('--purge') });
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
148
253
|
const plugin = args.includes('--plugin');
|
|
149
254
|
// First non-flag arg is an optional local source path (manual mode only).
|
|
150
255
|
const positional = args.filter((a) => !a.startsWith('--'));
|
|
151
256
|
|
|
152
257
|
const file = settingsPath();
|
|
153
|
-
|
|
154
|
-
try {
|
|
155
|
-
settings = JSON.parse(fs.readFileSync(file, 'utf8')) || {};
|
|
156
|
-
} catch {
|
|
157
|
-
settings = {}; // missing or malformed -> start fresh, don't clobber blindly below
|
|
158
|
-
}
|
|
258
|
+
const settings = readSettings(file);
|
|
159
259
|
|
|
160
260
|
// planOpts holds whatever the chosen strategy needs to build its command.
|
|
161
261
|
let planOpts;
|