squeezr-ai 1.19.0 → 1.20.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.
Files changed (2) hide show
  1. package/dist/gain.js +173 -70
  2. package/package.json +1 -1
package/dist/gain.js CHANGED
@@ -4,6 +4,7 @@ import { homedir } from 'os';
4
4
  import { join } from 'path';
5
5
  import { existsSync, unlinkSync } from 'fs';
6
6
  const args = process.argv.slice(2);
7
+ // ── Reset ────────────────────────────────────────────────────────────────────
7
8
  if (args.includes('--reset')) {
8
9
  const statsFile = join(homedir(), '.squeezr', 'stats.json');
9
10
  const cacheFile = join(homedir(), '.squeezr', 'cache.json');
@@ -17,81 +18,183 @@ if (args.includes('--reset')) {
17
18
  console.log('Stats reset.');
18
19
  process.exit(0);
19
20
  }
20
- const data = Stats.loadGlobal();
21
- if (!data || !data.requests) {
22
- console.log('No stats yet. Start Squeezr and make some requests.');
23
- process.exit(0);
24
- }
21
+ // ── Helpers ──────────────────────────────────────────────────────────────────
22
+ const W = 58; // inner width between │ and │
25
23
  const CPT = 3.5;
26
- const tok = (c) => Math.round(c / CPT);
27
24
  const fmtN = (n) => n.toLocaleString();
28
- const requests = data.requests;
29
- // Breakdown
30
- const detSaved = data.det_saved_chars ?? 0;
31
- const dedupSaved = data.dedup_saved_chars ?? 0;
32
- const aiSaved = data.ai_saved_chars ?? 0;
33
- const overheadAdded = data.overhead_chars ?? 0;
34
- const syspromptSaved = data.sysprompt_saved_chars ?? 0;
35
- const aiCalls = data.ai_compression_calls ?? 0;
36
- // Net
37
- const netSavedChars = detSaved + dedupSaved + aiSaved + syspromptSaved;
38
- const netSavedTokens = tok(netSavedChars);
39
- // Context reduction
40
- const originalChars = data.total_original_chars ?? 0;
41
- const savedChars = data.total_saved_chars ?? 0;
42
- const ctxPct = originalChars > 0 ? Math.round((savedChars / originalChars) * 1000) / 10 : 0;
43
- // By-tool
44
- const byTool = (data.by_tool ?? {});
45
- const W = 60;
46
- const line = '─'.repeat(W);
47
- const pad = (s, w) => s.padEnd(w);
48
- function row(label, chars, prefix = '-') {
49
- const c = `${prefix}${fmtN(chars)} chars`;
50
- const t = `~${fmtN(tok(chars))} tokens`;
51
- console.log(`│ ${label.padEnd(18)} ${c.padEnd(22)} ${t.padEnd(W - 43)}│`);
25
+ const tok = (c) => Math.round(c / CPT);
26
+ const fmtCh = (c) => `${fmtN(c)} ch`;
27
+ const fmtTk = (c) => `~${fmtN(tok(c))} tk`;
28
+ /** Print a line that is EXACTLY W chars between the two │ borders */
29
+ function row(content) {
30
+ const visible = content.length;
31
+ if (visible >= W) {
32
+ console.log(`│${content.slice(0, W)}│`);
33
+ }
34
+ else {
35
+ console.log(`│${content}${' '.repeat(W - visible)}│`);
36
+ }
37
+ }
38
+ /** Two-column row: label (left-aligned) + value (right-aligned) */
39
+ function kv(label, value) {
40
+ const gap = W - 2 - label.length - value.length;
41
+ if (gap < 1) {
42
+ row(` ${label} ${value}`);
43
+ }
44
+ else {
45
+ row(` ${label}${' '.repeat(gap)}${value}`);
46
+ }
47
+ }
48
+ /** Three-column row: label + chars + tokens */
49
+ function kv3(label, chars, prefix = '-') {
50
+ const ch = `${prefix}${fmtCh(chars)}`;
51
+ const tk = fmtTk(chars);
52
+ const col1 = 20;
53
+ const col2 = 16;
54
+ const padLabel = label.padEnd(col1);
55
+ const padCh = ch.padStart(col2);
56
+ const padTk = tk.padStart(W - 2 - col1 - col2 - 2);
57
+ row(` ${padLabel}${padCh} ${padTk}`);
58
+ }
59
+ function sep() { row(' ' + ' '.repeat(20) + '─'.repeat(16) + ' ' + '─'.repeat(W - 2 - 20 - 16 - 2)); }
60
+ function blank() { row(''); }
61
+ function topLine() { console.log(`┌${'─'.repeat(W)}┐`); }
62
+ function midLine() { console.log(`├${'─'.repeat(W)}┤`); }
63
+ function botLine() { console.log(`└${'─'.repeat(W)}┘`); }
64
+ function fmtUptime(secs) {
65
+ if (secs < 60)
66
+ return secs + 's';
67
+ if (secs < 3600)
68
+ return Math.floor(secs / 60) + 'm ' + (secs % 60) + 's';
69
+ return Math.floor(secs / 3600) + 'h ' + Math.floor((secs % 3600) / 60) + 'm';
52
70
  }
53
- console.log(`┌${line}┐`);
54
- console.log(`│${pad(' Squeezr Token Savings', W)}│`);
55
- console.log(`├${line}┤`);
56
- console.log(`│ Requests ${pad(String(requests), W - 21)}│`);
57
- console.log(`│${' '.repeat(W)}│`);
58
- if (detSaved > 0)
59
- row('Deterministic', detSaved);
60
- if (aiSaved > 0)
61
- row('AI compression', aiSaved);
62
- if (dedupSaved > 0)
63
- row('Read dedup', dedupSaved);
64
- if (syspromptSaved > 0)
65
- row('System prompt', syspromptSaved);
66
- if (overheadAdded > 0)
67
- row('Tag overhead', overheadAdded, '+');
68
- if (aiCalls > 0) {
69
- const cost = aiCalls * 1650;
70
- const c = `${aiCalls} calls`;
71
- const t = `~${fmtN(cost)} tokens cost`;
72
- console.log(`│ ${'AI compress'.padEnd(18)} ${c.padEnd(22)} ${t.padEnd(W - 43)}│`);
71
+ function loadHistoric() {
72
+ const data = Stats.loadGlobal();
73
+ if (!data || !data.requests)
74
+ return null;
75
+ const byTool = (data.by_tool ?? {});
76
+ return {
77
+ title: 'Token Savings (all-time)',
78
+ requests: data.requests,
79
+ detSaved: data.det_saved_chars ?? 0,
80
+ aiSaved: data.ai_saved_chars ?? 0,
81
+ dedupSaved: data.dedup_saved_chars ?? 0,
82
+ syspromptSaved: data.sysprompt_saved_chars ?? 0,
83
+ overheadAdded: data.overhead_chars ?? 0,
84
+ aiCalls: data.ai_compression_calls ?? 0,
85
+ originalChars: data.total_original_chars ?? 0,
86
+ savedChars: data.total_saved_chars ?? 0,
87
+ byTool,
88
+ };
73
89
  }
74
- console.log(`│ ${' '.repeat(18)} ${'─'.repeat(22)} ${'─'.repeat(W - 43)}│`);
75
- console.log(`│ ${'NET saved'.padEnd(18)} ${(fmtN(netSavedChars) + ' chars').padEnd(22)} ${'~' + fmtN(netSavedTokens) + ' tokens'}${' '.repeat(Math.max(0, W - 43 - ('~' + fmtN(netSavedTokens) + ' tokens').length))}│`);
76
- console.log(`│ ${'Context reduction'.padEnd(18)} ${(ctxPct + '%').padEnd(W - 21)}│`);
77
- if (Object.keys(byTool).length > 0) {
78
- console.log(`├${line}┤`);
79
- console.log(`│${pad(' By Tool', W)}│`);
80
- for (const [tool, d] of Object.entries(byTool).sort((a, b) => b[1].savedChars - a[1].savedChars)) {
81
- if (d.savedChars === 0)
82
- continue;
83
- const pct = d.originalChars > 0 ? Math.round((d.savedChars / d.originalChars) * 1000) / 10 : 0;
84
- const label = `${tool} (${d.count}x)`;
85
- const c = `-${fmtN(d.savedChars)} chars`;
86
- const t = `~${fmtN(tok(d.savedChars))} tok (-${pct}%)`;
87
- console.log(`│ ${label.padEnd(18)} ${c.padEnd(22)} ${t.padEnd(W - 43)}│`);
90
+ async function loadSession() {
91
+ const port = process.env.SQUEEZR_PORT ?? '8080';
92
+ try {
93
+ const resp = await fetch(`http://localhost:${port}/squeezr/stats`);
94
+ if (!resp.ok)
95
+ return null;
96
+ const d = await resp.json();
97
+ const bd = (d.breakdown ?? {});
98
+ const byTool = (d.by_tool ?? {});
99
+ // Convert by_tool format from summary() to gain format
100
+ const bt = {};
101
+ for (const [tool, t] of Object.entries(byTool)) {
102
+ bt[tool] = { count: t.count, savedChars: t.saved_chars, originalChars: Math.round(t.saved_chars / Math.max(t.avg_pct / 100, 0.01)) };
103
+ }
104
+ return {
105
+ title: 'Session Savings (live)',
106
+ requests: d.requests ?? 0,
107
+ detSaved: bd.deterministic ?? 0,
108
+ aiSaved: bd.ai_compression ?? 0,
109
+ dedupSaved: bd.read_dedup ?? 0,
110
+ syspromptSaved: bd.system_prompt ?? 0,
111
+ overheadAdded: bd.overhead ?? 0,
112
+ aiCalls: bd.ai_calls ?? 0,
113
+ originalChars: d.total_original_chars ?? 0,
114
+ savedChars: d.total_saved_chars ?? 0,
115
+ byTool: bt,
116
+ project: d.current_project ?? undefined,
117
+ uptime: d.uptime_seconds ?? undefined,
118
+ };
119
+ }
120
+ catch {
121
+ return null;
88
122
  }
89
123
  }
90
- console.log(`└${line}┘`);
91
- console.log(` All token counts are approximate (~3.5 chars/token)`);
92
- if (aiCalls > 0) {
93
- const cost = aiCalls * 1650;
94
- if (cost > netSavedTokens) {
95
- console.log(`\n ⚠ AI compression cost exceeds savings. Consider deterministic-only mode.`);
124
+ // ── Render ────────────────────────────────────────────────────────────────────
125
+ function render(d, showTools) {
126
+ const grossChars = d.detSaved + d.aiSaved + d.dedupSaved + d.syspromptSaved;
127
+ const aiCostChars = d.aiCalls * 1650 * CPT; // convert tokens back to chars for consistent display
128
+ const netChars = grossChars - aiCostChars;
129
+ const ctxPct = d.originalChars > 0 ? Math.round((d.savedChars / d.originalChars) * 1000) / 10 : 0;
130
+ topLine();
131
+ row(` Squeezr — ${d.title}`);
132
+ midLine();
133
+ if (d.project && d.project !== 'unknown')
134
+ kv('Project', d.project);
135
+ kv('Requests', String(d.requests));
136
+ if (d.uptime)
137
+ kv('Uptime', fmtUptime(d.uptime));
138
+ blank();
139
+ if (d.detSaved > 0)
140
+ kv3('Deterministic', d.detSaved);
141
+ if (d.aiSaved > 0)
142
+ kv3('AI compression', d.aiSaved);
143
+ if (d.dedupSaved > 0)
144
+ kv3('Read dedup', d.dedupSaved);
145
+ if (d.syspromptSaved > 0)
146
+ kv3('System prompt', d.syspromptSaved);
147
+ if (d.overheadAdded > 0)
148
+ kv3('Tag overhead', d.overheadAdded, '+');
149
+ if (d.aiCalls > 0) {
150
+ // AI compression cost: tokens spent on Haiku/GPT-mini calls
151
+ kv3('AI compress cost', Math.round(aiCostChars), '+');
152
+ }
153
+ sep();
154
+ kv3('NET saved', Math.max(0, Math.round(netChars)), ' ');
155
+ kv('Context reduction', `${ctxPct}%`);
156
+ const toolEntries = Object.entries(d.byTool)
157
+ .filter(([, t]) => t.savedChars > 0)
158
+ .sort((a, b) => b[1].savedChars - a[1].savedChars);
159
+ if (showTools && toolEntries.length > 0) {
160
+ midLine();
161
+ row(' By Tool');
162
+ for (const [tool, t] of toolEntries) {
163
+ const pct = t.originalChars > 0 ? Math.round((t.savedChars / t.originalChars) * 1000) / 10 : 0;
164
+ kv3(`${tool} (${t.count}x)`, t.savedChars);
165
+ }
166
+ }
167
+ botLine();
168
+ console.log(' ~3.5 chars/token');
169
+ if (d.aiCalls > 0) {
170
+ const cost = d.aiCalls * 1650;
171
+ if (cost > tok(netChars)) {
172
+ console.log(`\n ⚠ AI compression cost exceeds savings. Consider deterministic-only mode.`);
173
+ }
174
+ }
175
+ }
176
+ // ── Main ─────────────────────────────────────────────────────────────────────
177
+ async function main() {
178
+ const showDetails = args.includes('--details') || args.includes('-d');
179
+ const showSession = args.includes('--session') || args.includes('-s');
180
+ if (showSession) {
181
+ const session = await loadSession();
182
+ if (!session) {
183
+ console.log('Squeezr is not running. Start it with: squeezr start');
184
+ process.exit(1);
185
+ }
186
+ if (session.requests === 0) {
187
+ console.log('No requests in this session yet.');
188
+ process.exit(0);
189
+ }
190
+ render(session, true);
191
+ return;
192
+ }
193
+ const data = loadHistoric();
194
+ if (!data) {
195
+ console.log('No stats yet. Start Squeezr and make some requests.');
196
+ process.exit(0);
96
197
  }
198
+ render(data, showDetails);
97
199
  }
200
+ main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "squeezr-ai",
3
- "version": "1.19.0",
3
+ "version": "1.20.0",
4
4
  "description": "AI proxy that compresses Claude Code, Codex, Aider, Gemini CLI and Ollama context windows to save thousands of tokens per session",
5
5
  "keywords": [
6
6
  "claude",