squeezr-ai 1.81.2 → 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 (69) hide show
  1. package/bin/squeezr.js +82 -1
  2. package/dist/__tests__/bench.test.d.ts +1 -0
  3. package/dist/__tests__/bench.test.js +55 -0
  4. package/dist/__tests__/cacheAligner.test.d.ts +1 -0
  5. package/dist/__tests__/cacheAligner.test.js +75 -0
  6. package/dist/__tests__/codeStructure.test.d.ts +1 -0
  7. package/dist/__tests__/codeStructure.test.js +74 -0
  8. package/dist/__tests__/compressor.test.js +5 -1
  9. package/dist/__tests__/contentRouter.test.d.ts +1 -0
  10. package/dist/__tests__/contentRouter.test.js +76 -0
  11. package/dist/__tests__/controlEndpointGuard.test.d.ts +1 -0
  12. package/dist/__tests__/controlEndpointGuard.test.js +61 -0
  13. package/dist/__tests__/deterministic.test.js +13 -0
  14. package/dist/__tests__/doctor.test.d.ts +1 -0
  15. package/dist/__tests__/doctor.test.js +86 -0
  16. package/dist/__tests__/jsonCrush.test.d.ts +1 -0
  17. package/dist/__tests__/jsonCrush.test.js +168 -0
  18. package/dist/__tests__/learn.test.d.ts +1 -0
  19. package/dist/__tests__/learn.test.js +159 -0
  20. package/dist/__tests__/outputSavings.test.d.ts +1 -0
  21. package/dist/__tests__/outputSavings.test.js +49 -0
  22. package/dist/__tests__/outputShaper.test.d.ts +1 -0
  23. package/dist/__tests__/outputShaper.test.js +216 -0
  24. package/dist/__tests__/relevance.test.d.ts +1 -0
  25. package/dist/__tests__/relevance.test.js +66 -0
  26. package/dist/__tests__/secureKeyFile.test.d.ts +1 -0
  27. package/dist/__tests__/secureKeyFile.test.js +27 -0
  28. package/dist/__tests__/serverBinding.test.d.ts +1 -0
  29. package/dist/__tests__/serverBinding.test.js +18 -0
  30. package/dist/__tests__/statsOutput.test.d.ts +1 -0
  31. package/dist/__tests__/statsOutput.test.js +46 -0
  32. package/dist/__tests__/textCrusher.test.d.ts +1 -0
  33. package/dist/__tests__/textCrusher.test.js +133 -0
  34. package/dist/bench.d.ts +45 -0
  35. package/dist/bench.js +114 -0
  36. package/dist/cacheAligner.d.ts +34 -0
  37. package/dist/cacheAligner.js +73 -0
  38. package/dist/codexMitm.d.ts +1 -0
  39. package/dist/codexMitm.js +26 -2
  40. package/dist/compressor.js +42 -4
  41. package/dist/config.d.ts +6 -0
  42. package/dist/config.js +25 -0
  43. package/dist/contentRouter.d.ts +30 -0
  44. package/dist/contentRouter.js +112 -0
  45. package/dist/dashboard.d.ts +1 -1
  46. package/dist/dashboard.js +26 -7
  47. package/dist/deterministic.d.ts +4 -1
  48. package/dist/deterministic.js +43 -10
  49. package/dist/doctor.d.ts +38 -0
  50. package/dist/doctor.js +113 -0
  51. package/dist/index.js +4 -1
  52. package/dist/jsonCrush.d.ts +34 -0
  53. package/dist/jsonCrush.js +177 -0
  54. package/dist/learn.d.ts +65 -0
  55. package/dist/learn.js +244 -0
  56. package/dist/outputSavings.d.ts +20 -0
  57. package/dist/outputSavings.js +52 -0
  58. package/dist/outputShaper.d.ts +71 -0
  59. package/dist/outputShaper.js +155 -0
  60. package/dist/relevance.d.ts +27 -0
  61. package/dist/relevance.js +78 -0
  62. package/dist/server.js +106 -11
  63. package/dist/stats.d.ts +17 -0
  64. package/dist/stats.js +55 -0
  65. package/dist/systemPrompt.js +3 -2
  66. package/dist/textCrusher.d.ts +35 -0
  67. package/dist/textCrusher.js +160 -0
  68. package/package.json +69 -69
  69. package/squeezr.toml +15 -1
package/bin/squeezr.js CHANGED
@@ -5,7 +5,7 @@ import http from 'http'
5
5
  import path from 'path'
6
6
  import fs from 'fs'
7
7
  import os from 'os'
8
- import { fileURLToPath } from 'url'
8
+ import { fileURLToPath, pathToFileURL } from 'url'
9
9
  import { createRequire } from 'module'
10
10
 
11
11
  const __dirname = path.dirname(fileURLToPath(import.meta.url))
@@ -250,8 +250,14 @@ Usage:
250
250
  squeezr gain Show token savings stats
251
251
  squeezr gain --reset Reset saved stats
252
252
  squeezr discover Show pattern coverage report (proxy must be running)
253
+ squeezr learn Mine Claude Code sessions for token-wasting loops
254
+ squeezr learn --apply Write the learned rules to ./CLAUDE.local.md
255
+ squeezr bench Run the compression + fact-recall benchmark
253
256
  squeezr status Check if proxy is running
257
+ squeezr doctor Diagnose proxy/version/routing/bypass (exit 0/1/2)
254
258
  squeezr config Print config file path and current settings
259
+ squeezr rc [args...] Launch 'claude --rc' (Remote Control) direct to api.anthropic.com
260
+ (proxy bypassed for that session — Claude Code requires it)
255
261
  squeezr mcp install Register Squeezr MCP server in Claude Code, Cursor, Windsurf & Cline
256
262
  squeezr mcp uninstall Remove Squeezr MCP registration
257
263
  squeezr ports Change HTTP and MITM proxy ports
@@ -284,6 +290,47 @@ function runNode(script, extraArgs = []) {
284
290
  child.on('exit', code => process.exit(code ?? 0))
285
291
  }
286
292
 
293
+ // Launch Claude Code with Remote Control, bypassing the Squeezr proxy for THIS
294
+ // session only. Claude Code refuses Remote Control unless it talks DIRECTLY to
295
+ // api.anthropic.com — the binary guard is literally "Remote Control is only
296
+ // available when using Claude via api.anthropic.com." Squeezr's terminal setup
297
+ // exports ANTHROPIC_BASE_URL=http://localhost:<port>, which is not that host, so
298
+ // `claude --rc` errors out. We strip that var (and the MITM CA) from the CHILD
299
+ // env only. Global config is untouched: every other `claude` session still routes
300
+ // through Squeezr and compresses normally. Trade-off: this remote session is not
301
+ // compressed — remote sessions are short/interactive, so that's the right call.
302
+ function claudeRc(extraArgs) {
303
+ const env = { ...process.env }
304
+ delete env.ANTHROPIC_BASE_URL
305
+ delete env.anthropic_base_url
306
+ delete env.NODE_EXTRA_CA_CERTS // Squeezr's MITM CA — irrelevant talking direct
307
+
308
+ // Ensure --rc / --remote-control is present exactly once, then pass the rest through.
309
+ const rest = extraArgs.filter(a => a !== 'rc' && a !== 'remote-control')
310
+ const hasRc = rest.some(a => a === '--rc' || a === '--remote-control')
311
+ const claudeArgs = hasRc ? rest : ['--rc', ...rest]
312
+
313
+ console.log('Launching Claude Code with Remote Control — direct to api.anthropic.com')
314
+ console.log('(Squeezr proxy bypassed for THIS session only; global config untouched).\n')
315
+
316
+ // Never resolves — the child owns the terminal (stdio inherited) and we exit the
317
+ // wrapper with the child's code. Awaiting this blocks the trailing update banner
318
+ // from printing over Claude's interactive UI.
319
+ return new Promise(() => {
320
+ const child = spawn('claude', claudeArgs, {
321
+ stdio: 'inherit',
322
+ env,
323
+ shell: process.platform === 'win32', // resolve claude.exe via PATH on Windows
324
+ })
325
+ child.on('exit', code => process.exit(code ?? 0))
326
+ child.on('error', err => {
327
+ console.error(`\nFailed to launch claude: ${err.message}`)
328
+ console.error('Is Claude Code installed and on your PATH? Try running `claude --version`.')
329
+ process.exit(1)
330
+ })
331
+ })
332
+ }
333
+
287
334
  async function startDaemon() {
288
335
  const distIndex = path.join(ROOT, 'dist', 'index.js')
289
336
  if (!fs.existsSync(distIndex)) {
@@ -2504,6 +2551,11 @@ switch (command) {
2504
2551
  showConfig()
2505
2552
  break
2506
2553
 
2554
+ case 'rc':
2555
+ case 'remote-control':
2556
+ await claudeRc(args.slice(1))
2557
+ break
2558
+
2507
2559
  case 'mcp': {
2508
2560
  const subCmd = args[1] ?? 'install'
2509
2561
  if (subCmd === 'uninstall') await mcpUninstall()
@@ -2514,6 +2566,35 @@ case 'zest':
2514
2566
  await installZest()
2515
2567
  break
2516
2568
 
2569
+ case 'doctor': {
2570
+ const { runDoctor } = await import(pathToFileURL(path.join(ROOT, 'dist', 'doctor.js')).href)
2571
+ const res = await runDoctor()
2572
+ console.log(res.report)
2573
+ if (command !== 'update') await showUpdateBanner()
2574
+ process.exit(res.exitCode)
2575
+ break
2576
+ }
2577
+
2578
+ case 'bench': {
2579
+ const { runBench, formatBench } = await import(pathToFileURL(path.join(ROOT, 'dist', 'bench.js')).href)
2580
+ console.log(formatBench(runBench()))
2581
+ break
2582
+ }
2583
+
2584
+ case 'learn': {
2585
+ const apply = args.includes('--apply')
2586
+ const tIdx = args.indexOf('--target')
2587
+ const targetFile = tIdx >= 0 ? args[tIdx + 1] : undefined
2588
+ const { runLearn } = await import(pathToFileURL(path.join(ROOT, 'dist', 'learn.js')).href)
2589
+ const res = runLearn({ apply, targetFile })
2590
+ console.log(res.report)
2591
+ if (res.loops.length > 0) {
2592
+ if (res.applied) console.log(`\nWrote ${res.loops.length} rule(s) to ${res.targetFile}`)
2593
+ else console.log(`\nDry run. Re-run with --apply to write these rules to ${res.targetFile}.`)
2594
+ }
2595
+ break
2596
+ }
2597
+
2517
2598
  case 'version':
2518
2599
  case '--version':
2519
2600
  case '-v':
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,55 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { factRecall, runBench, FIXTURES } from '../bench.js';
3
+ describe('factRecall', () => {
4
+ it('returns 1 when every fact is present verbatim', () => {
5
+ expect(factRecall('error E123 in file foo.ts at line 42', ['E123', 'foo.ts', '42'])).toBe(1);
6
+ });
7
+ it('returns the fraction present', () => {
8
+ expect(factRecall('only E123 here', ['E123', 'MISSING'])).toBe(0.5);
9
+ });
10
+ it('returns 1 for an empty fact list (nothing to lose)', () => {
11
+ expect(factRecall('whatever', [])).toBe(1);
12
+ });
13
+ });
14
+ describe('FIXTURES', () => {
15
+ it('every fixture declares its critical facts', () => {
16
+ expect(FIXTURES.length).toBeGreaterThan(0);
17
+ for (const f of FIXTURES) {
18
+ expect(f.facts.length).toBeGreaterThan(0);
19
+ // sanity: the facts must actually be in the original content
20
+ for (const fact of f.facts)
21
+ expect(f.content.includes(fact)).toBe(true);
22
+ }
23
+ });
24
+ });
25
+ describe('runBench', () => {
26
+ it('produces a row per fixture with a valid shape', () => {
27
+ const summary = runBench();
28
+ expect(summary.rows.length).toBe(FIXTURES.length);
29
+ for (const r of summary.rows) {
30
+ expect(r.originalChars).toBeGreaterThan(0);
31
+ expect(r.compressedChars).toBeGreaterThan(0);
32
+ expect(r.compressionPct).toBeGreaterThanOrEqual(0);
33
+ expect(r.inlineRecall).toBeGreaterThanOrEqual(0);
34
+ expect(r.inlineRecall).toBeLessThanOrEqual(1);
35
+ }
36
+ });
37
+ it('achieves real compression on at least one fixture', () => {
38
+ const summary = runBench();
39
+ expect(summary.rows.some(r => r.compressionPct > 10)).toBe(true);
40
+ });
41
+ it('never drops a critical fact from the highly-compressible JSON fixture', () => {
42
+ // The homogeneous JSON array is crushed to a table — all VALUES must stay inline.
43
+ const summary = runBench();
44
+ const json = summary.rows.find(r => r.name.includes('json'));
45
+ expect(json).toBeDefined();
46
+ expect(json.compressionPct).toBeGreaterThan(10);
47
+ expect(json.inlineRecall).toBe(1);
48
+ });
49
+ it('reports honest averages', () => {
50
+ const summary = runBench();
51
+ expect(summary.avgCompressionPct).toBeGreaterThan(0);
52
+ expect(summary.avgInlineRecall).toBeGreaterThan(0);
53
+ expect(summary.avgInlineRecall).toBeLessThanOrEqual(1);
54
+ });
55
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,75 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { detectVolatile, analyzeSystemPrompt, formatVolatileWarning, _resetWarnedForTest, warnIfVolatile, } from '../cacheAligner.js';
3
+ describe('detectVolatile', () => {
4
+ it('detects a UUID', () => {
5
+ const f = detectVolatile('session 550e8400-e29b-41d4-a716-446655440000 started');
6
+ expect(f.find(x => x.kind === 'uuid')?.count).toBe(1);
7
+ });
8
+ it('detects ISO timestamps', () => {
9
+ const f = detectVolatile('at 2026-07-21T10:00:00Z and 2026-07-21T11:00:00Z');
10
+ expect(f.find(x => x.kind === 'timestamp')?.count).toBe(2);
11
+ });
12
+ it('detects a JWT', () => {
13
+ const jwt = 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
14
+ const f = detectVolatile(`token=${jwt}`);
15
+ expect(f.find(x => x.kind === 'jwt')?.count).toBe(1);
16
+ });
17
+ it('detects long hex hashes', () => {
18
+ const sha = 'a'.repeat(64);
19
+ const f = detectVolatile(`commit ${sha}`);
20
+ expect(f.find(x => x.kind === 'hash')?.count).toBe(1);
21
+ });
22
+ it('returns nothing for clean stable text', () => {
23
+ expect(detectVolatile('You are a helpful coding assistant. Follow the rules.')).toEqual([]);
24
+ });
25
+ it('does not double-count a JWT as a hash', () => {
26
+ const jwt = 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
27
+ const f = detectVolatile(jwt);
28
+ expect(f.find(x => x.kind === 'hash')).toBeUndefined();
29
+ expect(f.find(x => x.kind === 'jwt')?.count).toBe(1);
30
+ });
31
+ });
32
+ describe('analyzeSystemPrompt', () => {
33
+ it('handles a string system prompt', () => {
34
+ const r = analyzeSystemPrompt('build id 550e8400-e29b-41d4-a716-446655440000');
35
+ expect(r.hasVolatile).toBe(true);
36
+ expect(r.findings.length).toBeGreaterThan(0);
37
+ });
38
+ it('aggregates across an array of text blocks', () => {
39
+ const sys = [
40
+ { type: 'text', text: 'stable preamble' },
41
+ { type: 'text', text: 'ts 2026-07-21T10:00:00Z' },
42
+ ];
43
+ const r = analyzeSystemPrompt(sys);
44
+ expect(r.findings.find(f => f.kind === 'timestamp')?.count).toBe(1);
45
+ });
46
+ it('reports clean when there is nothing volatile', () => {
47
+ const r = analyzeSystemPrompt('You are Claude, a coding assistant.');
48
+ expect(r.hasVolatile).toBe(false);
49
+ expect(r.findings).toEqual([]);
50
+ });
51
+ it('ignores non-text blocks and undefined', () => {
52
+ expect(analyzeSystemPrompt(undefined).hasVolatile).toBe(false);
53
+ const sys = [{ type: 'image', source: {} }, { type: 'text', text: 'clean' }];
54
+ expect(analyzeSystemPrompt(sys).hasVolatile).toBe(false);
55
+ });
56
+ });
57
+ describe('formatVolatileWarning', () => {
58
+ it('mentions each detected kind', () => {
59
+ const msg = formatVolatileWarning([{ kind: 'uuid', count: 2 }, { kind: 'timestamp', count: 1 }]);
60
+ expect(msg.includes('uuid')).toBe(true);
61
+ expect(msg.includes('timestamp')).toBe(true);
62
+ });
63
+ });
64
+ describe('warnIfVolatile', () => {
65
+ it('warns once per distinct finding signature (no per-request spam)', () => {
66
+ _resetWarnedForTest();
67
+ const sys = 'id 550e8400-e29b-41d4-a716-446655440000';
68
+ expect(warnIfVolatile(sys)).toBe(true); // first time → warns
69
+ expect(warnIfVolatile(sys)).toBe(false); // same signature → suppressed
70
+ });
71
+ it('does not warn on clean prompts', () => {
72
+ _resetWarnedForTest();
73
+ expect(warnIfVolatile('perfectly stable system prompt')).toBe(false);
74
+ });
75
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,74 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { detectCodeLanguage, extractCodeStructure } from '../deterministic.js';
3
+ import { retrieveOriginal } from '../expand.js';
4
+ describe('detectCodeLanguage', () => {
5
+ it('detects the existing languages', () => {
6
+ expect(detectCodeLanguage("import x from 'y'\nexport function f(): void {}")).toBe('ts');
7
+ expect(detectCodeLanguage('from os import path\ndef f():\n pass')).toBe('py');
8
+ expect(detectCodeLanguage('package main\nfunc main() {}')).toBe('go');
9
+ expect(detectCodeLanguage('use std::io;\npub fn main() {}')).toBe('rs');
10
+ });
11
+ it('detects Java', () => {
12
+ const java = 'package com.acme.app;\nimport java.util.List;\npublic class Service {\n public void run() {}\n}';
13
+ expect(detectCodeLanguage(java)).toBe('java');
14
+ });
15
+ it('detects C++ (has std::/class/namespace)', () => {
16
+ const cpp = '#include <vector>\n#include <string>\nnamespace app {\nclass Widget {\n void draw() {}\n};\n}';
17
+ expect(detectCodeLanguage(cpp)).toBe('cpp');
18
+ });
19
+ it('detects C (includes, no C++ markers)', () => {
20
+ const c = '#include <stdio.h>\n#include <stdlib.h>\nint main(int argc, char **argv) {\n return 0;\n}';
21
+ expect(detectCodeLanguage(c)).toBe('c');
22
+ });
23
+ it('returns null for non-code prose', () => {
24
+ expect(detectCodeLanguage('This is a plain english log line.\nAnother sentence here.')).toBeNull();
25
+ });
26
+ });
27
+ describe('extractCodeStructure — Java', () => {
28
+ function bigJava() {
29
+ const lines = ['package com.acme;', 'import java.util.List;', '', 'public class Orders {'];
30
+ for (let m = 0; m < 6; m++) {
31
+ lines.push(` public int method${m}(int a, int b) {`);
32
+ for (let b = 0; b < 15; b++)
33
+ lines.push(` int local${b} = a + b + ${m};`);
34
+ lines.push(' return a;');
35
+ lines.push(' }');
36
+ }
37
+ lines.push('}');
38
+ return lines.join('\n');
39
+ }
40
+ it('keeps signatures, elides bodies, and stays smaller', () => {
41
+ const input = bigJava();
42
+ const out = extractCodeStructure(input, 'java');
43
+ expect(out).not.toBe(input);
44
+ expect(out.length).toBeLessThan(input.length);
45
+ // signatures survive
46
+ expect(out.includes('public int method0(int a, int b)')).toBe(true);
47
+ expect(out.includes('public class Orders')).toBe(true);
48
+ // an omitted-lines marker is present
49
+ expect(/implementation lines omitted|lines omitted/.test(out)).toBe(true);
50
+ });
51
+ it('is fully recoverable via the parent expand id', () => {
52
+ const input = bigJava();
53
+ const out = extractCodeStructure(input, 'java');
54
+ const m = out.match(/squeezr_expand\("([0-9a-f]{6,})"\)/);
55
+ expect(m).not.toBeNull();
56
+ expect(retrieveOriginal(m[1])).toBe(input);
57
+ });
58
+ });
59
+ describe('extractCodeStructure — C++', () => {
60
+ it('keeps class + method signatures', () => {
61
+ const lines = ['#include <vector>', '', 'class Engine {', 'public:'];
62
+ for (let m = 0; m < 5; m++) {
63
+ lines.push(` void step${m}(double dt) {`);
64
+ for (let b = 0; b < 12; b++)
65
+ lines.push(` acc += dt * ${b};`);
66
+ lines.push(' }');
67
+ }
68
+ lines.push('};');
69
+ const input = lines.join('\n');
70
+ const out = extractCodeStructure(input, 'cpp');
71
+ expect(out.includes('void step0(double dt)')).toBe(true);
72
+ expect(out.length).toBeLessThan(input.length);
73
+ });
74
+ });
@@ -296,7 +296,11 @@ describe('compressGeminiContents', () => {
296
296
  const cts = makeContents(['g'.repeat(200), 'h'.repeat(200)]);
297
297
  await compressGeminiContents(cts, 'my-google-key', baseConfig);
298
298
  expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining('generativelanguage.googleapis.com'), expect.any(Object));
299
- expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining('my-google-key'), expect.any(Object));
299
+ // Security: the key travels in the x-goog-api-key header, never in the URL.
300
+ const [calledUrl, calledOpts] = mockFetch.mock.calls.at(-1);
301
+ expect(calledUrl).not.toContain('my-google-key');
302
+ expect(calledUrl).not.toContain('key=');
303
+ expect(calledOpts.headers['x-goog-api-key']).toBe('my-google-key');
300
304
  });
301
305
  it('returns dry-run without modifications', async () => {
302
306
  const oldText = 'g'.repeat(200);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,76 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { findObjectArraySpans, crushEmbeddedJson, TABLE_MARKER_RE } from '../contentRouter.js';
3
+ import { retrieveOriginal } from '../expand.js';
4
+ function bigArray(prefix = '', suffix = '') {
5
+ const rows = Array.from({ length: 20 }, (_, i) => ({ id: i, name: `svc-${i}`, status: 'ok', region: 'us-east-1' }));
6
+ return `${prefix}${JSON.stringify(rows)}${suffix}`;
7
+ }
8
+ describe('findObjectArraySpans', () => {
9
+ it('finds a whole-text array of objects', () => {
10
+ const t = JSON.stringify([{ a: 1 }, { a: 2 }]);
11
+ const spans = findObjectArraySpans(t);
12
+ expect(spans.length).toBe(1);
13
+ expect(t.slice(spans[0].start, spans[0].end)).toBe(t);
14
+ });
15
+ it('finds an array embedded between prose', () => {
16
+ const t = bigArray('Here are the pods:\n', '\nDone.');
17
+ const spans = findObjectArraySpans(t);
18
+ expect(spans.length).toBe(1);
19
+ const sub = t.slice(spans[0].start, spans[0].end);
20
+ expect(sub.startsWith('[{')).toBe(true);
21
+ expect(sub.endsWith('}]')).toBe(true);
22
+ });
23
+ it('handles brackets inside string values without breaking', () => {
24
+ const t = 'x ' + JSON.stringify([{ note: 'has ] and } inside' }, { note: 'more [ [ {' }]) + ' y';
25
+ const spans = findObjectArraySpans(t);
26
+ expect(spans.length).toBe(1);
27
+ expect(() => JSON.parse(t.slice(spans[0].start, spans[0].end))).not.toThrow();
28
+ });
29
+ it('finds two separate embedded arrays', () => {
30
+ const t = `first ${JSON.stringify([{ a: 1 }, { a: 2 }])} middle ${JSON.stringify([{ b: 3 }, { b: 4 }])} end`;
31
+ expect(findObjectArraySpans(t).length).toBe(2);
32
+ });
33
+ it('ignores arrays of scalars (not object arrays)', () => {
34
+ expect(findObjectArraySpans('[1, 2, 3, 4, 5]').length).toBe(0);
35
+ });
36
+ });
37
+ describe('crushEmbeddedJson', () => {
38
+ it('crushes an array embedded in prose, preserving the surrounding text', () => {
39
+ const t = bigArray('POD STATUS REPORT\n', '\n(end of report)');
40
+ const res = crushEmbeddedJson(t);
41
+ expect(res.savedChars).toBeGreaterThan(0);
42
+ expect(res.text.startsWith('POD STATUS REPORT')).toBe(true);
43
+ expect(res.text.includes('(end of report)')).toBe(true);
44
+ expect(TABLE_MARKER_RE.test(res.text)).toBe(true);
45
+ });
46
+ it('keeps the embedded original recoverable via expand', () => {
47
+ const t = bigArray('label: ', '');
48
+ const res = crushEmbeddedJson(t);
49
+ const m = res.text.match(TABLE_MARKER_RE);
50
+ expect(m).not.toBeNull();
51
+ const original = retrieveOriginal(m[1]);
52
+ expect(original).not.toBeUndefined();
53
+ expect(() => JSON.parse(original)).not.toThrow();
54
+ });
55
+ it('crushes the whole-text case too (parity with jsonCrush)', () => {
56
+ const res = crushEmbeddedJson(bigArray());
57
+ expect(res.savedChars).toBeGreaterThan(0);
58
+ expect(TABLE_MARKER_RE.test(res.text)).toBe(true);
59
+ });
60
+ it('leaves text with no crushable arrays unchanged', () => {
61
+ const t = 'just a normal log line\nwith no json at all';
62
+ const res = crushEmbeddedJson(t);
63
+ expect(res.savedChars).toBe(0);
64
+ expect(res.text).toBe(t);
65
+ });
66
+ it('leaves a small (non-crushable) embedded array untouched, surroundings intact', () => {
67
+ const t = 'before [{"a":1},{"a":2}] after';
68
+ const res = crushEmbeddedJson(t);
69
+ expect(res.text).toBe(t);
70
+ expect(res.savedChars).toBe(0);
71
+ });
72
+ it('is deterministic', () => {
73
+ const t = bigArray('p ', ' s');
74
+ expect(crushEmbeddedJson(t).text).toBe(crushEmbeddedJson(t).text);
75
+ });
76
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,61 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { app } from '../server.js';
3
+ // Security regression: control endpoints (/squeezr/*) must not be usable from a
4
+ // cross-origin browser context. A malicious page open in the user's browser must
5
+ // not be able to change state or read control responses on http://localhost:<port>.
6
+ // We hit *non-existent* control paths so the guard runs but no real handler fires
7
+ // (403 = guard blocked; 404 = guard allowed and routing missed) — no side effects.
8
+ describe('control-endpoint CSRF/CORS guard', () => {
9
+ it('rejects a cross-origin mutation to a control endpoint with 403', async () => {
10
+ const res = await app.request('/squeezr/__guard_probe__', {
11
+ method: 'POST',
12
+ headers: { origin: 'http://evil.example' },
13
+ });
14
+ expect(res.status).toBe(403);
15
+ });
16
+ it('rejects a file:// (null) origin mutation to a control endpoint', async () => {
17
+ const res = await app.request('/squeezr/__guard_probe__', {
18
+ method: 'POST',
19
+ headers: { origin: 'null' },
20
+ });
21
+ expect(res.status).toBe(403);
22
+ });
23
+ it('allows a mutation with no Origin (curl / MCP / native over loopback)', async () => {
24
+ const res = await app.request('/squeezr/__guard_probe__', { method: 'POST' });
25
+ expect(res.status).toBe(404); // guard passed; route simply does not exist
26
+ });
27
+ it('allows a mutation from a loopback browser origin', async () => {
28
+ const res = await app.request('/squeezr/__guard_probe__', {
29
+ method: 'POST',
30
+ headers: { origin: 'http://localhost:8899' },
31
+ });
32
+ expect(res.status).toBe(404); // guard passed; route simply does not exist
33
+ });
34
+ it('does not guard the proxy endpoints (Cursor needs cross-origin)', async () => {
35
+ const res = await app.request('/v1/__proxy_probe__', {
36
+ method: 'POST',
37
+ headers: { origin: 'http://evil.example' },
38
+ });
39
+ expect(res.status).not.toBe(403);
40
+ });
41
+ it('never reflects a wildcard ACAO on control endpoints', async () => {
42
+ const res = await app.request('/squeezr/health', {
43
+ headers: { origin: 'http://evil.example' },
44
+ });
45
+ expect(res.headers.get('access-control-allow-origin')).not.toBe('*');
46
+ });
47
+ it('reflects the loopback origin (not wildcard) on control endpoints', async () => {
48
+ const res = await app.request('/squeezr/health', {
49
+ headers: { origin: 'http://127.0.0.1:1234' },
50
+ });
51
+ expect(res.headers.get('access-control-allow-origin')).toBe('http://127.0.0.1:1234');
52
+ expect(res.headers.get('vary')).toBe('Origin');
53
+ });
54
+ it('keeps wildcard ACAO on proxy endpoints for browser tooling', async () => {
55
+ const res = await app.request('/v1/__proxy_probe__', {
56
+ method: 'OPTIONS',
57
+ headers: { origin: 'http://evil.example' },
58
+ });
59
+ expect(res.headers.get('access-control-allow-origin')).toBe('*');
60
+ });
61
+ });
@@ -105,6 +105,19 @@ describe('preprocess - base pipeline', () => {
105
105
  const input = 'a\nb\na';
106
106
  expect(preprocess(input)).toBe('a\nb\na');
107
107
  });
108
+ // Regression (1.82.0): repeated bare object keys like `body:` were folded into
109
+ // "... [repeated N more times]", which corrupted code when it round-tripped to disk.
110
+ it('never folds repeated bare object keys (body:)', () => {
111
+ // Mirrors the real incident: identical keys, unique values on the next line.
112
+ const input = Array.from({ length: 5 }, (_, i) => ` body:\n "unique text ${i}",`).join('\n');
113
+ const out = preprocess(input);
114
+ expect(out).not.toContain('repeated');
115
+ expect(out.split('\n').filter(l => l.trim() === 'body:')).toHaveLength(5);
116
+ });
117
+ it('still folds genuine repeated log prose (colon with a value after)', () => {
118
+ const input = Array(5).fill('ERROR: connection refused').join('\n');
119
+ expect(preprocess(input)).toContain('repeated 4 more times');
120
+ });
108
121
  it('minifies inline JSON blobs > 200 chars', () => {
109
122
  const obj = Object.fromEntries(Array.from({ length: 20 }, (_, i) => [`key_${i}`, `value_number_${i}`]));
110
123
  const pretty = JSON.stringify(obj, null, 2);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,86 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { checkProxy, checkVersion, checkEnv, checkBypass, checkSavings, computeExitCode, formatDoctor, } from '../doctor.js';
3
+ describe('checkProxy', () => {
4
+ it('passes when health is present', () => {
5
+ expect(checkProxy({ identity: 'squeezr', version: '1.89.0' }).status).toBe('pass');
6
+ });
7
+ it('fails when the proxy is unreachable', () => {
8
+ expect(checkProxy(null).status).toBe('fail');
9
+ });
10
+ });
11
+ describe('checkVersion', () => {
12
+ it('passes when installed matches running', () => {
13
+ expect(checkVersion('1.89.0', '1.89.0').status).toBe('pass');
14
+ });
15
+ it('warns on a stale running proxy', () => {
16
+ const r = checkVersion('1.89.0', '1.88.0');
17
+ expect(r.status).toBe('warn');
18
+ expect(r.detail.includes('1.88.0')).toBe(true);
19
+ });
20
+ it('skips when the running version is unknown (proxy down)', () => {
21
+ expect(checkVersion('1.89.0', null).status).toBe('skip');
22
+ });
23
+ });
24
+ describe('checkEnv', () => {
25
+ it('passes when ANTHROPIC_BASE_URL points at the local proxy', () => {
26
+ expect(checkEnv('http://localhost:8080', 8080).status).toBe('pass');
27
+ expect(checkEnv('http://127.0.0.1:8080', 8080).status).toBe('pass');
28
+ });
29
+ it('warns when unset (traffic not routed through squeezr)', () => {
30
+ expect(checkEnv(undefined, 8080).status).toBe('warn');
31
+ expect(checkEnv('', 8080).status).toBe('warn');
32
+ });
33
+ it('fails when it points somewhere else', () => {
34
+ expect(checkEnv('https://api.anthropic.com', 8080).status).toBe('fail');
35
+ expect(checkEnv('http://localhost:9999', 8080).status).toBe('fail');
36
+ });
37
+ });
38
+ describe('checkBypass', () => {
39
+ it('warns when bypass is on (compression disabled)', () => {
40
+ expect(checkBypass(true).status).toBe('warn');
41
+ });
42
+ it('passes when bypass is off', () => {
43
+ expect(checkBypass(false).status).toBe('pass');
44
+ });
45
+ it('skips when unknown', () => {
46
+ expect(checkBypass(undefined).status).toBe('skip');
47
+ });
48
+ });
49
+ describe('checkSavings', () => {
50
+ it('skips when the proxy is down (requests unknown)', () => {
51
+ expect(checkSavings(undefined, undefined).status).toBe('skip');
52
+ });
53
+ it('skips when there is no traffic yet', () => {
54
+ expect(checkSavings(0, 0).status).toBe('skip');
55
+ });
56
+ it('warns when traffic is flowing but nothing is being saved', () => {
57
+ expect(checkSavings(120, 0).status).toBe('warn');
58
+ });
59
+ it('passes when savings are flowing', () => {
60
+ const r = checkSavings(120, 34);
61
+ expect(r.status).toBe('pass');
62
+ expect(r.detail.includes('34')).toBe(true);
63
+ });
64
+ });
65
+ describe('computeExitCode', () => {
66
+ it('0 when all pass/skip', () => {
67
+ expect(computeExitCode([{ name: 'a', status: 'pass', detail: '' }, { name: 'b', status: 'skip', detail: '' }])).toBe(0);
68
+ });
69
+ it('1 when there is a warning but no failure', () => {
70
+ expect(computeExitCode([{ name: 'a', status: 'pass', detail: '' }, { name: 'b', status: 'warn', detail: '' }])).toBe(1);
71
+ });
72
+ it('2 when there is any failure', () => {
73
+ expect(computeExitCode([{ name: 'a', status: 'warn', detail: '' }, { name: 'b', status: 'fail', detail: '' }])).toBe(2);
74
+ });
75
+ });
76
+ describe('formatDoctor', () => {
77
+ it('renders every check line and a summary', () => {
78
+ const out = formatDoctor([
79
+ { name: 'Proxy', status: 'pass', detail: 'running v1.89.0' },
80
+ { name: 'Env', status: 'warn', detail: 'not set' },
81
+ ], 1);
82
+ expect(out.includes('Proxy')).toBe(true);
83
+ expect(out.includes('Env')).toBe(true);
84
+ expect(out.toLowerCase().includes('warn')).toBe(true);
85
+ });
86
+ });
@@ -0,0 +1 @@
1
+ export {};