squeezr-ai 1.90.0 → 1.99.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.
Files changed (43) hide show
  1. package/dist/__tests__/compressor.test.js +5 -1
  2. package/dist/__tests__/contentRouter.test.d.ts +1 -0
  3. package/dist/__tests__/contentRouter.test.js +76 -0
  4. package/dist/__tests__/controlEndpointGuard.test.d.ts +1 -0
  5. package/dist/__tests__/controlEndpointGuard.test.js +61 -0
  6. package/dist/__tests__/doctor.test.js +17 -1
  7. package/dist/__tests__/jsonCrush.test.js +59 -1
  8. package/dist/__tests__/outputSavings.test.d.ts +1 -0
  9. package/dist/__tests__/outputSavings.test.js +49 -0
  10. package/dist/__tests__/relevance.test.d.ts +1 -0
  11. package/dist/__tests__/relevance.test.js +66 -0
  12. package/dist/__tests__/secureKeyFile.test.d.ts +1 -0
  13. package/dist/__tests__/secureKeyFile.test.js +27 -0
  14. package/dist/__tests__/serverBinding.test.d.ts +1 -0
  15. package/dist/__tests__/serverBinding.test.js +18 -0
  16. package/dist/__tests__/statsOutput.test.d.ts +1 -0
  17. package/dist/__tests__/statsOutput.test.js +46 -0
  18. package/dist/__tests__/textCrusher.test.js +63 -1
  19. package/dist/codexMitm.d.ts +1 -0
  20. package/dist/codexMitm.js +26 -2
  21. package/dist/compressor.js +31 -3
  22. package/dist/contentRouter.d.ts +30 -0
  23. package/dist/contentRouter.js +112 -0
  24. package/dist/dashboard.d.ts +1 -1
  25. package/dist/dashboard.js +26 -7
  26. package/dist/deterministic.d.ts +1 -1
  27. package/dist/deterministic.js +14 -13
  28. package/dist/doctor.d.ts +5 -0
  29. package/dist/doctor.js +11 -0
  30. package/dist/index.js +4 -1
  31. package/dist/jsonCrush.d.ts +4 -0
  32. package/dist/jsonCrush.js +83 -18
  33. package/dist/outputSavings.d.ts +20 -0
  34. package/dist/outputSavings.js +52 -0
  35. package/dist/relevance.d.ts +27 -0
  36. package/dist/relevance.js +78 -0
  37. package/dist/server.js +84 -11
  38. package/dist/stats.d.ts +17 -0
  39. package/dist/stats.js +55 -0
  40. package/dist/systemPrompt.js +3 -2
  41. package/dist/textCrusher.d.ts +4 -0
  42. package/dist/textCrusher.js +69 -7
  43. package/package.json +69 -69
package/dist/server.js CHANGED
@@ -15,6 +15,27 @@ import { injectExpandToolAnthropic, injectExpandToolOpenAI, injectExpandDirectiv
15
15
  import { compressSystemPrompt } from './systemPrompt.js';
16
16
  import { shapeRequest } from './outputShaper.js';
17
17
  import { warnIfVolatile } from './cacheAligner.js';
18
+ import { echoRatio, extractAssistantTextFromSse, extractAssistantTextFromContent } from './outputSavings.js';
19
+ // Concatenated text the model saw (capped), used as the echo-measurement context.
20
+ function contextTextForEcho(messages) {
21
+ let out = '';
22
+ for (const m of messages) {
23
+ const c = m?.content;
24
+ if (typeof c === 'string')
25
+ out += c + '\n';
26
+ else if (Array.isArray(c)) {
27
+ for (const b of c) {
28
+ if (typeof b?.text === 'string')
29
+ out += b.text + '\n';
30
+ else if (typeof b?.content === 'string')
31
+ out += b.content + '\n';
32
+ }
33
+ }
34
+ if (out.length > 40000)
35
+ break;
36
+ }
37
+ return out;
38
+ }
18
39
  import { captureRequest } from './requestCapture.js';
19
40
  import { dedupSkillBlocks } from './skillDedup.js';
20
41
  import { collapseStaleTurns } from './staleTurns.js';
@@ -201,20 +222,48 @@ async function proxyStream(upstream, body, headers, params) {
201
222
  });
202
223
  }
203
224
  export const app = new Hono();
204
- // ── CORS middleware (required for Cursor IDE and browser-based tools) ─────────
205
- // Cursor's Electron renderer sends OPTIONS preflight before every POST.
206
- // Without this the request is blocked and Cursor shows a network error.
225
+ // ── CORS + CSRF guard middleware ─────────────────────────────────────────────
226
+ // Cursor's Electron renderer sends OPTIONS preflight before every POST, so the
227
+ // proxy endpoints (/v1, /v1beta) stay permissive for browser-based IDE tooling.
228
+ // The control endpoints (/squeezr/*) are different: they change state and the
229
+ // process holds the user's API keys, so they must NOT be reachable cross-origin.
230
+ // Combined with the loopback bind, this kills the CSRF/DoS vector where a page
231
+ // open in the user's browser POSTs to http://localhost:<port>/squeezr/control/*.
232
+ // Only browser origins on loopback may touch the control endpoints.
233
+ const LOOPBACK_ORIGIN = /^https?:\/\/(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/i;
234
+ const isLoopbackOrigin = (origin) => origin !== undefined && LOOPBACK_ORIGIN.test(origin);
207
235
  app.use('*', async (c, next) => {
236
+ const origin = c.req.header('origin');
237
+ const isControl = c.req.path.startsWith('/squeezr/');
238
+ const isMutation = !['GET', 'HEAD', 'OPTIONS'].includes(c.req.method);
239
+ // CSRF guard: a state-changing request to a control endpoint that carries a
240
+ // non-loopback browser Origin is a cross-site attack — reject it. Requests
241
+ // without an Origin (curl, MCP, native clients over loopback) are allowed;
242
+ // the loopback bind already limits those to processes on this machine.
243
+ if (isControl && isMutation && origin !== undefined && !isLoopbackOrigin(origin)) {
244
+ return c.json({ error: 'forbidden: cross-origin control request rejected' }, 403);
245
+ }
246
+ // CORS: control endpoints reflect ONLY loopback origins (never wildcard, so a
247
+ // malicious page cannot read control/state responses). Proxy endpoints keep
248
+ // the permissive wildcard that Cursor and other browser tools need.
249
+ const allowOrigin = isControl ? (isLoopbackOrigin(origin) ? origin : '') : '*';
208
250
  if (c.req.method === 'OPTIONS') {
209
- return c.body(null, 204, {
210
- 'Access-Control-Allow-Origin': '*',
251
+ const headers = {
211
252
  'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
212
253
  'Access-Control-Allow-Headers': '*',
213
254
  'Access-Control-Max-Age': '86400',
214
- });
255
+ };
256
+ if (allowOrigin)
257
+ headers['Access-Control-Allow-Origin'] = allowOrigin;
258
+ if (isControl)
259
+ headers['Vary'] = 'Origin';
260
+ return c.body(null, 204, headers);
215
261
  }
216
262
  await next();
217
- c.res.headers.set('Access-Control-Allow-Origin', '*');
263
+ if (allowOrigin)
264
+ c.res.headers.set('Access-Control-Allow-Origin', allowOrigin);
265
+ if (isControl)
266
+ c.res.headers.set('Vary', 'Origin');
218
267
  c.res.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');
219
268
  c.res.headers.set('Access-Control-Allow-Headers', '*');
220
269
  });
@@ -315,10 +364,12 @@ app.post('/v1/messages', async (c) => {
315
364
  savings,
316
365
  });
317
366
  }
318
- // Bypass mode: skip all compression, still record request stats
367
+ // Bypass mode: skip all compression. Do NOT record processed/saved/cost stats — Squeezr
368
+ // did nothing to this traffic, and counting it (as it did before) inflated "processed"
369
+ // and dragged the savings ratio / cost to ~0 after a day spent bypassed. Track it in a
370
+ // separate bypassed-only counter instead.
319
371
  if (isBypassed()) {
320
- stats.recordWithProject(project, originalRequestChars, originalRequestChars, emptySavings(), undefined, clientId, modelId);
321
- recordRequest(project, 0, 0, [], originalRequestChars);
372
+ stats.recordBypassed();
322
373
  storeKey('anthropic', apiKey);
323
374
  const fwdHeaders = forwardHeaders(c.req.raw.headers);
324
375
  if (body.stream) {
@@ -473,6 +524,7 @@ app.post('/v1/messages', async (c) => {
473
524
  effortRouting: config.outputEffortRouting,
474
525
  mechanicalThinkingFloor: config.outputMechanicalThinkingFloor,
475
526
  });
527
+ stats.recordShaping(shaped.steered, shaped.effortLowered);
476
528
  if (shaped.effortLowered || shaped.steered) {
477
529
  console.log(`[squeezr/output-shaper] turn=${shaped.turn} steered=${shaped.steered} effort-lowered=${shaped.effortLowered}`);
478
530
  }
@@ -508,6 +560,8 @@ app.post('/v1/messages', async (c) => {
508
560
  if (!SKIP_RESP_HEADERS.has(k.toLowerCase()))
509
561
  c.header(k, v);
510
562
  }
563
+ const measureEcho = config.outputShaperEnabled;
564
+ let sseBuf = '';
511
565
  return stream(c, async (s) => {
512
566
  const reader = upstream.body.getReader();
513
567
  const decoder = new TextDecoder();
@@ -517,7 +571,18 @@ app.post('/v1/messages', async (c) => {
517
571
  if (done)
518
572
  break;
519
573
  await s.write(value);
520
- sseParser(decoder.decode(value, { stream: true }));
574
+ const chunk = decoder.decode(value, { stream: true });
575
+ sseParser(chunk);
576
+ if (measureEcho)
577
+ sseBuf += chunk;
578
+ }
579
+ if (measureEcho) {
580
+ const outText = extractAssistantTextFromSse(sseBuf);
581
+ if (outText.length > 40) {
582
+ const echo = echoRatio(outText, contextTextForEcho(body.messages));
583
+ stats.recordEcho(echo);
584
+ console.log(`[squeezr/output] echo=${Math.round(echo * 100)}% of assistant output restated existing context (${outText.length} chars)`);
585
+ }
521
586
  }
522
587
  });
523
588
  }
@@ -533,6 +598,14 @@ app.post('/v1/messages', async (c) => {
533
598
  const u = respBody.usage;
534
599
  addAnthropicUsage(u.input_tokens ?? 0, u.output_tokens ?? 0, u.cache_creation_input_tokens ?? 0, u.cache_read_input_tokens ?? 0);
535
600
  }
601
+ if (config.outputShaperEnabled) {
602
+ const outText = extractAssistantTextFromContent(respBody.content);
603
+ if (outText.length > 40) {
604
+ const echo = echoRatio(outText, contextTextForEcho(body.messages));
605
+ stats.recordEcho(echo);
606
+ console.log(`[squeezr/output] echo=${Math.round(echo * 100)}% of assistant output restated existing context (${outText.length} chars)`);
607
+ }
608
+ }
536
609
  // Handle expand() call if model requested one (track expand rate)
537
610
  const expandCall = handleAnthropicExpandCall(respBody);
538
611
  if (expandCall) {
package/dist/stats.d.ts CHANGED
@@ -35,6 +35,16 @@ export declare class Stats {
35
35
  private expandHits;
36
36
  private expandMisses;
37
37
  private expandPartial;
38
+ private outputEchoSamples;
39
+ private outputEchoSum;
40
+ private outputSteered;
41
+ private outputEffortLowered;
42
+ /** Record one measured assistant response's echo ratio (0..1). */
43
+ recordEcho(echo: number): void;
44
+ /** Record one shaped request: whether verbosity steering was applied / effort was lowered. */
45
+ recordShaping(steered: boolean, effortLowered: boolean): void;
46
+ private bypassedRequests;
47
+ recordBypassed(): void;
38
48
  record(originalChars: number, compressedChars: number, savings: Savings, latency?: LatencyInfo, client?: string, model?: string): void;
39
49
  /** @deprecated Pass savings.syspromptSavedChars instead */
40
50
  recordSystemPromptSaved(originalLen: number, compressedLen: number): void;
@@ -65,6 +75,7 @@ export declare class Stats {
65
75
  current_project: string;
66
76
  last_original_chars: number;
67
77
  last_compressed_chars: number;
78
+ bypassed_requests: number;
68
79
  breakdown: {
69
80
  tool_results_det: number;
70
81
  tool_results_ai: number;
@@ -103,6 +114,12 @@ export declare class Stats {
103
114
  last: number;
104
115
  };
105
116
  };
117
+ output: {
118
+ echo_samples: number;
119
+ avg_echo_pct: number;
120
+ steered: number;
121
+ effort_lowered: number;
122
+ };
106
123
  expand: {
107
124
  calls: number;
108
125
  hits: number;
package/dist/stats.js CHANGED
@@ -75,6 +75,46 @@ export class Stats {
75
75
  expandMisses = Stats.persistedNum('expand_misses');
76
76
  // Partial expands = squeezr_expand("<id>~i") (recover one segment) vs whole-block.
77
77
  expandPartial = Stats.persistedNum('expand_partial');
78
+ // Output-side metrics (output shaper + echo). Persisted via the main persist() snapshot
79
+ // (no per-call disk write). echoSum is the running sum of echo ratios (0..1); avg = sum/samples.
80
+ outputEchoSamples = Stats.persistedNum('output_echo_samples');
81
+ outputEchoSum = Stats.persistedNum('output_echo_sum');
82
+ outputSteered = Stats.persistedNum('output_steered');
83
+ outputEffortLowered = Stats.persistedNum('output_effort_lowered');
84
+ /** Record one measured assistant response's echo ratio (0..1). */
85
+ recordEcho(echo) {
86
+ this.outputEchoSamples++;
87
+ this.outputEchoSum += echo;
88
+ }
89
+ /** Record one shaped request: whether verbosity steering was applied / effort was lowered. */
90
+ recordShaping(steered, effortLowered) {
91
+ if (steered)
92
+ this.outputSteered++;
93
+ if (effortLowered)
94
+ this.outputEffortLowered++;
95
+ }
96
+ // Requests that passed through while BYPASSED. Tracked separately and NEVER added to
97
+ // processed/saved/cost totals — bypass means Squeezr did nothing, so counting that
98
+ // traffic as "processed, 0 saved" would drag the ratio to ~0 and inflate cost (the
99
+ // "$7048 processed / $0.55 saved after a bypass day" confusion). Throttled persist to
100
+ // avoid disk thrash under heavy bypass traffic.
101
+ bypassedRequests = Stats.persistedNum('bypassed_requests');
102
+ recordBypassed() {
103
+ this.bypassedRequests++;
104
+ if (this.bypassedRequests % 25 === 0) {
105
+ try {
106
+ const dir = join(homedir(), '.squeezr');
107
+ if (!existsSync(dir))
108
+ mkdirSync(dir, { recursive: true });
109
+ const existing = Stats.loadGlobal();
110
+ existing.bypassed_requests = this.bypassedRequests;
111
+ const tmp = STATS_FILE + '.tmp';
112
+ writeFileSync(tmp, JSON.stringify(existing));
113
+ renameSync(tmp, STATS_FILE);
114
+ }
115
+ catch { /* ignore */ }
116
+ }
117
+ }
78
118
  record(originalChars, compressedChars, savings, latency, client, model) {
79
119
  this.requests++;
80
120
  this.totalOriginalChars += originalChars;
@@ -218,6 +258,7 @@ export class Stats {
218
258
  current_project: this.currentProject,
219
259
  last_original_chars: this.lastOriginalChars,
220
260
  last_compressed_chars: this.lastCompressedChars,
261
+ bypassed_requests: this.bypassedRequests,
221
262
  // Savings breakdown for honest dashboard reporting
222
263
  breakdown: {
223
264
  tool_results_det: this.totalDetSaved,
@@ -237,6 +278,16 @@ export class Stats {
237
278
  deterministic: this.latencyDet.summary(),
238
279
  ai: this.latencyAi.summary(),
239
280
  },
281
+ // Output-side metrics (output shaper). avg_echo_pct = mean % of assistant output
282
+ // that merely restated existing context (lower is better; steering targets it).
283
+ output: {
284
+ echo_samples: this.outputEchoSamples,
285
+ avg_echo_pct: this.outputEchoSamples > 0
286
+ ? Math.round((this.outputEchoSum / this.outputEchoSamples) * 1000) / 10
287
+ : 0,
288
+ steered: this.outputSteered,
289
+ effort_lowered: this.outputEffortLowered,
290
+ },
240
291
  // Expand rate — key quality metric (high = compression too aggressive)
241
292
  expand: {
242
293
  calls: this.expandCalls,
@@ -318,6 +369,10 @@ export class Stats {
318
369
  existing.expand_hits = this.expandHits;
319
370
  existing.expand_misses = this.expandMisses;
320
371
  existing.expand_partial = this.expandPartial;
372
+ existing.output_echo_samples = this.outputEchoSamples;
373
+ existing.output_echo_sum = this.outputEchoSum;
374
+ existing.output_steered = this.outputSteered;
375
+ existing.output_effort_lowered = this.outputEffortLowered;
321
376
  // Local AI calls (Zest/Ollama) — persisted separately, not added to cost counters
322
377
  if (savings.localAiCalls != null && savings.localAiCalls > 0) {
323
378
  existing.local_ai_calls = (existing.local_ai_calls ?? 0) + savings.localAiCalls;
@@ -81,10 +81,11 @@ export async function compressSystemPrompt(prompt, apiKey, backend) {
81
81
  compressed = resp.choices[0].message.content ?? prompt;
82
82
  }
83
83
  else if (backend === 'gemini-flash') {
84
- const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-8b:generateContent?key=${apiKey}`;
84
+ // Key in the x-goog-api-key header, never in the URL query (URLs leak to logs).
85
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-8b:generateContent`;
85
86
  const resp = await fetch(url, {
86
87
  method: 'POST',
87
- headers: { 'content-type': 'application/json' },
88
+ headers: { 'content-type': 'application/json', 'x-goog-api-key': apiKey },
88
89
  body: JSON.stringify({ contents: [{ role: 'user', parts: [{ text: input }] }] }),
89
90
  });
90
91
  const data = (await resp.json());
@@ -20,9 +20,13 @@ export interface CrushTextOpts {
20
20
  maxLines: number;
21
21
  headKeep: number;
22
22
  tailKeep: number;
23
+ query: string;
23
24
  }
24
25
  /** Collapse volatile tokens so near-duplicate lines share a key. */
25
26
  export declare function normalizeLineKey(line: string): string;
27
+ /** Word 3-gram shingles of a line (falls back to the word set for short lines). */
28
+ export declare function wordShingles(line: string, k?: number): Set<string>;
29
+ export declare function jaccard(a: Set<string>, b: Set<string>): number;
26
30
  export interface CrushResult {
27
31
  text: string;
28
32
  kept: number;
@@ -16,7 +16,7 @@
16
16
  * It never drops content irrecoverably: callers wrap the result with makeRecoverable so
17
17
  * the full original is one squeezr_expand away.
18
18
  */
19
- const DEFAULTS = { maxLines: 60, headKeep: 5, tailKeep: 5 };
19
+ const DEFAULTS = { maxLines: 60, headKeep: 5, tailKeep: 5, query: '' };
20
20
  const SIGNAL_RE = /\b(error|err|warn|warning|fail|failed|failure|exception|fatal|panic|traceback|denied|refused|timeout|timed out|cannot|unable|missing|undefined|null pointer|segfault|assert)\b/i;
21
21
  /** Collapse volatile tokens so near-duplicate lines share a key. */
22
22
  export function normalizeLineKey(line) {
@@ -32,6 +32,37 @@ export function normalizeLineKey(line) {
32
32
  function isSignal(line) {
33
33
  return SIGNAL_RE.test(line);
34
34
  }
35
+ // ── Shingle-based near-duplicate detection (catches REWORDED dups) ────────────
36
+ // Bigram shingles (k=2) + a 0.6 Jaccard threshold: catches a 1-word rewording in a
37
+ // typical log line while leaving genuinely different lines (Jaccard ~0) untouched.
38
+ // (Sentence-level 0.85 like headroom is too strict for short log lines.)
39
+ const SHINGLE_K = 2;
40
+ const NEAR_DUP_JACCARD = 0.6;
41
+ /** Word 3-gram shingles of a line (falls back to the word set for short lines). */
42
+ export function wordShingles(line, k = SHINGLE_K) {
43
+ const words = (line.toLowerCase().match(/[a-z0-9_]+/g) ?? []);
44
+ const set = new Set();
45
+ if (words.length < k) {
46
+ for (const w of words)
47
+ set.add(w);
48
+ return set;
49
+ }
50
+ for (let i = 0; i + k <= words.length; i++)
51
+ set.add(words.slice(i, i + k).join(' '));
52
+ return set;
53
+ }
54
+ export function jaccard(a, b) {
55
+ if (a.size === 0 && b.size === 0)
56
+ return 1;
57
+ if (a.size === 0 || b.size === 0)
58
+ return 0;
59
+ let inter = 0;
60
+ for (const x of a)
61
+ if (b.has(x))
62
+ inter++;
63
+ return inter / (a.size + b.size - inter);
64
+ }
65
+ import { bm25Scores } from './relevance.js';
35
66
  export function crushText(text, opts = {}) {
36
67
  const o = { ...DEFAULTS, ...opts };
37
68
  const lines = text.split('\n');
@@ -60,17 +91,48 @@ export function crushText(text, opts = {}) {
60
91
  if (anchor || isSignal(lines[i]))
61
92
  tryKeep(i, key);
62
93
  }
63
- // Fill remaining budget with the FIRST occurrence of each not-yet-seen normalized key,
64
- // in original order near-duplicates collapse to their first representative.
65
- for (let i = 0; i < n && budget > 0; i++) {
94
+ // Candidate lines for the remaining budget: first occurrence of each not-yet-seen
95
+ // normalized key (so near-duplicates collapse to their first representative).
96
+ const candidates = [];
97
+ const candSeen = new Set(seenKeys);
98
+ for (let i = 0; i < n; i++) {
66
99
  if (keep[i])
67
100
  continue;
68
101
  const key = normalizeLineKey(lines[i]);
69
- if (key === '')
102
+ if (key === '' || candSeen.has(key))
70
103
  continue;
71
- if (seenKeys.has(key))
104
+ candSeen.add(key);
105
+ candidates.push(i);
106
+ }
107
+ // Order candidates: by BM25 relevance to the task when a query is given (keep the
108
+ // lines that matter for what the user is doing), else by original order. Relevance
109
+ // is only ever driven from OUTSIDE the cached prefix (see compressor), so this stays
110
+ // cache-safe. Kept lines are still emitted in original order below.
111
+ let ordered = candidates;
112
+ if (o.query.trim() !== '' && candidates.length > 0) {
113
+ const scores = bm25Scores(o.query, candidates.map(i => lines[i]));
114
+ ordered = candidates
115
+ .map((idx, k) => ({ idx, score: scores[k], k }))
116
+ .sort((a, b) => (b.score - a.score) || (a.k - b.k))
117
+ .map(x => x.idx);
118
+ }
119
+ // Keep candidates, but skip any that is a REWORDED near-duplicate (shingle Jaccard
120
+ // ≥ threshold) of a line already kept in this fill pass — catches dups that survived
121
+ // the exact normalized-key check because their wording differs.
122
+ // Seed with the shingles of lines already kept (anchors + signal) so a candidate that
123
+ // merely rewords an anchor/signal line is also dropped.
124
+ const keptShingles = [];
125
+ for (let i = 0; i < n; i++)
126
+ if (keep[i])
127
+ keptShingles.push(wordShingles(lines[i]));
128
+ for (const i of ordered) {
129
+ if (budget <= 0)
130
+ break;
131
+ const sh = wordShingles(lines[i]);
132
+ if (keptShingles.some(s => jaccard(sh, s) >= NEAR_DUP_JACCARD))
72
133
  continue;
73
- tryKeep(i, key);
134
+ tryKeep(i, normalizeLineKey(lines[i]));
135
+ keptShingles.push(sh);
74
136
  }
75
137
  // Reconstruct in original order, summarizing runs of dropped lines with one marker.
76
138
  const out = [];
package/package.json CHANGED
@@ -1,69 +1,69 @@
1
- {
2
- "name": "squeezr-ai",
3
- "version": "1.90.0",
4
- "description": "AI proxy that compresses Claude Code, Claude Desktop, Codex, Codex Desktop, Aider, Gemini CLI and Ollama context windows to save thousands of tokens per session",
5
- "keywords": [
6
- "claude",
7
- "claude-code",
8
- "codex",
9
- "ollama",
10
- "aider",
11
- "gemini",
12
- "token",
13
- "compression",
14
- "proxy",
15
- "llm",
16
- "ai"
17
- ],
18
- "license": "MIT",
19
- "repository": {
20
- "type": "git",
21
- "url": "git+https://github.com/sergioramosv/Squeezr.git"
22
- },
23
- "homepage": "https://github.com/sergioramosv/Squeezr#readme",
24
- "type": "module",
25
- "bin": {
26
- "squeezr": "bin/squeezr.js",
27
- "squeezr-mcp": "dist/mcp.js"
28
- },
29
- "scripts": {
30
- "build": "tsc",
31
- "prepack": "node -e \"['dist/cursorMitm.js','dist/cursorMitm.d.ts','dist/__tests__/cursorMitm.test.js','dist/__tests__/cursorMitm.test.d.ts'].forEach(f=>{try{require('fs').unlinkSync(f)}catch{}})\"",
32
- "dev": "tsx src/index.ts",
33
- "start": "node dist/index.js",
34
- "gain": "node dist/gain.js",
35
- "discover": "node dist/discover.js",
36
- "test": "vitest run",
37
- "test:watch": "vitest",
38
- "test:quality": "vitest run src/__tests__/qualityHarness.test.ts"
39
- },
40
- "files": [
41
- "bin/",
42
- "dist/",
43
- "squeezr.toml"
44
- ],
45
- "dependencies": {
46
- "@anthropic-ai/sdk": "^0.39.0",
47
- "@bufbuild/protobuf": "^2.11.0",
48
- "@hono/node-server": "^1.13.7",
49
- "@modelcontextprotocol/sdk": "^1.29.0",
50
- "@types/diff": "^7.0.2",
51
- "diff": "^9.0.0",
52
- "hono": "^4.7.5",
53
- "node-forge": "^1.4.0",
54
- "openai": "^4.93.0",
55
- "smol-toml": "^1.3.1",
56
- "zod": "^3.24.0"
57
- },
58
- "devDependencies": {
59
- "@types/node": "^22.14.0",
60
- "@types/node-forge": "^1.3.14",
61
- "@vitest/coverage-v8": "^4.1.2",
62
- "tsx": "^4.19.3",
63
- "typescript": "^5.8.3",
64
- "vitest": "^4.1.2"
65
- },
66
- "engines": {
67
- "node": ">=18"
68
- }
69
- }
1
+ {
2
+ "name": "squeezr-ai",
3
+ "version": "1.99.2",
4
+ "description": "AI proxy that compresses Claude Code, Claude Desktop, Codex, Codex Desktop, Aider, Gemini CLI and Ollama context windows to save thousands of tokens per session",
5
+ "keywords": [
6
+ "claude",
7
+ "claude-code",
8
+ "codex",
9
+ "ollama",
10
+ "aider",
11
+ "gemini",
12
+ "token",
13
+ "compression",
14
+ "proxy",
15
+ "llm",
16
+ "ai"
17
+ ],
18
+ "license": "MIT",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/sergioramosv/Squeezr.git"
22
+ },
23
+ "homepage": "https://github.com/sergioramosv/Squeezr#readme",
24
+ "type": "module",
25
+ "bin": {
26
+ "squeezr": "bin/squeezr.js",
27
+ "squeezr-mcp": "dist/mcp.js"
28
+ },
29
+ "scripts": {
30
+ "build": "tsc",
31
+ "prepack": "node -e \"['dist/cursorMitm.js','dist/cursorMitm.d.ts','dist/__tests__/cursorMitm.test.js','dist/__tests__/cursorMitm.test.d.ts'].forEach(f=>{try{require('fs').unlinkSync(f)}catch{}})\"",
32
+ "dev": "tsx src/index.ts",
33
+ "start": "node dist/index.js",
34
+ "gain": "node dist/gain.js",
35
+ "discover": "node dist/discover.js",
36
+ "test": "vitest run",
37
+ "test:watch": "vitest",
38
+ "test:quality": "vitest run src/__tests__/qualityHarness.test.ts"
39
+ },
40
+ "files": [
41
+ "bin/",
42
+ "dist/",
43
+ "squeezr.toml"
44
+ ],
45
+ "dependencies": {
46
+ "@anthropic-ai/sdk": "^0.39.0",
47
+ "@bufbuild/protobuf": "^2.11.0",
48
+ "@hono/node-server": "^1.13.7",
49
+ "@modelcontextprotocol/sdk": "^1.29.0",
50
+ "@types/diff": "^7.0.2",
51
+ "diff": "^9.0.0",
52
+ "hono": "^4.7.5",
53
+ "node-forge": "^1.4.0",
54
+ "openai": "^4.93.0",
55
+ "smol-toml": "^1.3.1",
56
+ "zod": "^3.24.0"
57
+ },
58
+ "devDependencies": {
59
+ "@types/node": "^22.14.0",
60
+ "@types/node-forge": "^1.3.14",
61
+ "@vitest/coverage-v8": "^4.1.2",
62
+ "tsx": "^4.19.3",
63
+ "typescript": "^5.8.3",
64
+ "vitest": "^4.1.2"
65
+ },
66
+ "engines": {
67
+ "node": ">=18"
68
+ }
69
+ }