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.
- package/dist/gain.js +173 -70
- 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
|
-
|
|
21
|
-
|
|
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
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
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