squeezr-ai 1.14.14 → 1.16.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/README.md CHANGED
@@ -98,7 +98,8 @@ Codex uses WebSocket over TLS to `chatgpt.com` with OAuth authentication — it
98
98
 
99
99
  ```toml
100
100
  [proxy]
101
- port = 8080
101
+ port = 8080 # HTTP proxy (Claude, Aider, Gemini)
102
+ mitm_port = 8081 # MITM proxy (Codex) — defaults to port + 1
102
103
 
103
104
  [compression]
104
105
  threshold = 800 # min chars to trigger compression
@@ -133,7 +134,8 @@ Project-level config is deep-merged over global config. Useful for per-repo tuni
133
134
 
134
135
  | Variable | Default | Description |
135
136
  |----------|---------|-------------|
136
- | `SQUEEZR_PORT` | `8080` | Proxy port (MITM port = this + 1) |
137
+ | `SQUEEZR_PORT` | `8080` | HTTP proxy port (Claude, Aider, Gemini) |
138
+ | `SQUEEZR_MITM_PORT` | `8081` | MITM proxy port (Codex) — defaults to SQUEEZR_PORT + 1 |
137
139
  | `SQUEEZR_THRESHOLD` | `800` | Min chars to compress |
138
140
  | `SQUEEZR_KEEP_RECENT` | `3` | Recent results to skip |
139
141
  | `SQUEEZR_DISABLED` | `false` | Disable all compression |
@@ -172,8 +174,10 @@ squeezr stop # stop the proxy
172
174
  squeezr status # check if proxy is running
173
175
  squeezr logs # show last 50 log lines
174
176
  squeezr config # print current config
177
+ squeezr ports # change HTTP and MITM proxy ports
175
178
  squeezr gain # estimate token savings for a directory
176
179
  squeezr discover # detect which AI CLIs are installed
180
+ squeezr uninstall # remove Squeezr completely (env vars, CA, auto-start, logs)
177
181
  squeezr version # print version
178
182
  ```
179
183
 
package/bin/squeezr.js CHANGED
@@ -16,6 +16,17 @@ const pkg = require(path.join(ROOT, 'package.json'))
16
16
  const args = process.argv.slice(2)
17
17
  const command = args[0]
18
18
 
19
+ function getMitmPort(port) {
20
+ const envMitm = process.env.SQUEEZR_MITM_PORT
21
+ if (envMitm) return parseInt(envMitm)
22
+ try {
23
+ const toml = fs.readFileSync(path.join(ROOT, 'squeezr.toml'), 'utf-8')
24
+ const m = toml.match(/^mitm_port\s*=\s*(\d+)/m)
25
+ if (m) return parseInt(m[1])
26
+ } catch {}
27
+ return Number(port) + 1
28
+ }
29
+
19
30
  const HELP = `
20
31
  Squeezr v${pkg.version} — AI context compressor for Claude Code, Codex, Aider, Gemini CLI and Ollama
21
32
 
@@ -30,6 +41,8 @@ Usage:
30
41
  squeezr discover Show pattern coverage report (proxy must be running)
31
42
  squeezr status Check if proxy is running
32
43
  squeezr config Print config file path and current settings
44
+ squeezr ports Change HTTP and MITM proxy ports
45
+ squeezr uninstall Remove Squeezr completely (env vars, CA, auto-start, logs)
33
46
  squeezr version Print version
34
47
  squeezr help Show this help
35
48
  `
@@ -54,22 +67,32 @@ async function startDaemon() {
54
67
  process.exit(1)
55
68
  }
56
69
 
57
- // Check if already running
70
+ // Check if already running — and if the version matches
58
71
  const port = process.env.SQUEEZR_PORT || 8080
59
- const running = await new Promise(resolve => {
72
+ const runningVersion = await new Promise(resolve => {
60
73
  const req = http.get(`http://localhost:${port}/squeezr/health`, res => {
61
- resolve(res.statusCode === 200)
62
- res.destroy()
74
+ let data = ''
75
+ res.on('data', chunk => { data += chunk })
76
+ res.on('end', () => {
77
+ try { resolve(JSON.parse(data).version) } catch { resolve('unknown') }
78
+ })
63
79
  })
64
- req.on('error', () => resolve(false))
65
- req.setTimeout(2000, () => { req.destroy(); resolve(false) })
80
+ req.on('error', () => resolve(null))
81
+ req.setTimeout(2000, () => { req.destroy(); resolve(null) })
66
82
  })
67
- if (running) {
68
- const mitmPort = Number(port) + 1
69
- console.log(`Squeezr is already running`)
70
- console.log(` HTTP proxy (Claude/Aider/Gemini): http://localhost:${port}`)
71
- console.log(` MITM proxy (Codex): http://localhost:${mitmPort}`)
72
- return
83
+ if (runningVersion) {
84
+ if (runningVersion === pkg.version) {
85
+ const mitmPort = getMitmPort(port)
86
+ console.log(`Squeezr is already running (v${pkg.version})`)
87
+ console.log(` HTTP proxy (Claude/Aider/Gemini): http://localhost:${port}`)
88
+ console.log(` MITM proxy (Codex): http://localhost:${mitmPort}`)
89
+ return
90
+ }
91
+ // Version mismatch — old process from before npm update. Kill and restart.
92
+ console.log(`Squeezr v${runningVersion} is running but v${pkg.version} is installed. Restarting...`)
93
+ stopProxy()
94
+ // Wait for ports to free up
95
+ await new Promise(r => setTimeout(r, 1500))
73
96
  }
74
97
 
75
98
  // Launch detached background process
@@ -86,7 +109,7 @@ async function startDaemon() {
86
109
  })
87
110
  child.unref()
88
111
  fs.closeSync(logFd)
89
- const mitmPort = Number(port) + 1
112
+ const mitmPort = getMitmPort(port)
90
113
  console.log(`Squeezr started (pid ${child.pid})`)
91
114
  console.log(` HTTP proxy (Claude/Aider/Gemini): http://localhost:${port}`)
92
115
  console.log(` MITM proxy (Codex): http://localhost:${mitmPort}`)
@@ -112,7 +135,7 @@ function showLogs() {
112
135
 
113
136
  function stopProxy() {
114
137
  const port = process.env.SQUEEZR_PORT || 8080
115
- const mitmPort = Number(port) + 1
138
+ const mitmPort = getMitmPort(port)
116
139
  const ports = [port, mitmPort]
117
140
  let killed = false
118
141
 
@@ -154,7 +177,7 @@ function stopProxy() {
154
177
 
155
178
  async function checkStatus() {
156
179
  const port = process.env.SQUEEZR_PORT || 8080
157
- const mitmPort = Number(port) + 1
180
+ const mitmPort = getMitmPort(port)
158
181
  return new Promise(resolve => {
159
182
  const req = http.get(`http://localhost:${port}/squeezr/health`, res => {
160
183
  let data = ''
@@ -194,6 +217,169 @@ function showConfig() {
194
217
  }
195
218
  }
196
219
 
220
+ // ── squeezr ports ─────────────────────────────────────────────────────────────
221
+
222
+ async function configurePorts() {
223
+ const { createInterface } = await import('readline')
224
+ const tomlPath = path.join(ROOT, 'squeezr.toml')
225
+ let tomlContent = fs.existsSync(tomlPath) ? fs.readFileSync(tomlPath, 'utf-8') : ''
226
+
227
+ // Read current ports from toml
228
+ const portMatch = tomlContent.match(/^port\s*=\s*(\d+)/m)
229
+ const mitmMatch = tomlContent.match(/^mitm_port\s*=\s*(\d+)/m)
230
+ const currentPort = portMatch ? parseInt(portMatch[1]) : 8080
231
+ const currentMitm = mitmMatch ? parseInt(mitmMatch[1]) : currentPort + 1
232
+
233
+ const rl = createInterface({ input: process.stdin, output: process.stdout })
234
+ const ask = (q) => new Promise(resolve => rl.question(q, resolve))
235
+
236
+ console.log(`\nCurrent ports:`)
237
+ console.log(` HTTP proxy (Claude/Aider/Gemini): ${currentPort}`)
238
+ console.log(` MITM proxy (Codex): ${currentMitm}\n`)
239
+
240
+ const newPort = await ask(`HTTP proxy port [${currentPort}]: `)
241
+ const newMitm = await ask(`MITM proxy port [${currentMitm}]: `)
242
+ rl.close()
243
+
244
+ const finalPort = newPort.trim() ? parseInt(newPort.trim()) : currentPort
245
+ const finalMitm = newMitm.trim() ? parseInt(newMitm.trim()) : currentMitm
246
+
247
+ if (isNaN(finalPort) || isNaN(finalMitm) || finalPort < 1 || finalMitm < 1 || finalPort > 65535 || finalMitm > 65535) {
248
+ console.error('Invalid port number. Must be between 1 and 65535.')
249
+ process.exit(1)
250
+ }
251
+ if (finalPort === finalMitm) {
252
+ console.error('HTTP and MITM ports must be different.')
253
+ process.exit(1)
254
+ }
255
+
256
+ // Update toml
257
+ if (portMatch) {
258
+ tomlContent = tomlContent.replace(/^port\s*=\s*\d+/m, `port = ${finalPort}`)
259
+ } else if (tomlContent.includes('[proxy]')) {
260
+ tomlContent = tomlContent.replace('[proxy]', `[proxy]\nport = ${finalPort}`)
261
+ } else {
262
+ tomlContent = `[proxy]\nport = ${finalPort}\n` + tomlContent
263
+ }
264
+
265
+ if (mitmMatch) {
266
+ tomlContent = tomlContent.replace(/^mitm_port\s*=\s*\d+/m, `mitm_port = ${finalMitm}`)
267
+ } else {
268
+ // Add after port line
269
+ tomlContent = tomlContent.replace(/^(port\s*=\s*\d+)/m, `$1\nmitm_port = ${finalMitm}`)
270
+ }
271
+
272
+ fs.writeFileSync(tomlPath, tomlContent)
273
+ console.log(`\nSaved to ${tomlPath}`)
274
+
275
+ // Update env vars
276
+ if (process.platform === 'win32') {
277
+ try { execSync(`setx SQUEEZR_PORT "${finalPort}"`, { stdio: 'pipe' }) } catch {}
278
+ try { execSync(`setx SQUEEZR_MITM_PORT "${finalMitm}"`, { stdio: 'pipe' }) } catch {}
279
+ try { execSync(`setx ANTHROPIC_BASE_URL "http://localhost:${finalPort}"`, { stdio: 'pipe' }) } catch {}
280
+ try { execSync(`setx GEMINI_API_BASE_URL "http://localhost:${finalPort}"`, { stdio: 'pipe' }) } catch {}
281
+ try { execSync(`setx HTTPS_PROXY "http://localhost:${finalMitm}"`, { stdio: 'pipe' }) } catch {}
282
+ console.log('Environment variables updated. Restart your terminal for changes to take effect.')
283
+ } else {
284
+ console.log(`\nUpdate your shell profile:`)
285
+ console.log(` export SQUEEZR_PORT=${finalPort}`)
286
+ console.log(` export SQUEEZR_MITM_PORT=${finalMitm}`)
287
+ console.log(` export ANTHROPIC_BASE_URL=http://localhost:${finalPort}`)
288
+ console.log(` export HTTPS_PROXY=http://localhost:${finalMitm}`)
289
+ }
290
+
291
+ console.log(`\nRestart squeezr for changes to take effect:`)
292
+ console.log(` squeezr stop && squeezr start`)
293
+ }
294
+
295
+ // ── squeezr uninstall ─────────────────────────────────────────────────────────
296
+
297
+ async function uninstall() {
298
+ const { createInterface } = await import('readline')
299
+ const rl = createInterface({ input: process.stdin, output: process.stdout })
300
+ const answer = await new Promise(resolve => rl.question(
301
+ 'This will remove Squeezr completely: stop proxy, remove env vars, CA certs, auto-start, config, and logs.\nContinue? [y/N] ', resolve
302
+ ))
303
+ rl.close()
304
+ if (answer.trim().toLowerCase() !== 'y') {
305
+ console.log('Cancelled.')
306
+ return
307
+ }
308
+
309
+ console.log('\nUninstalling Squeezr...\n')
310
+
311
+ // 1. Stop proxy
312
+ stopProxy()
313
+
314
+ // 2. Remove env vars
315
+ if (process.platform === 'win32') {
316
+ const vars = ['ANTHROPIC_BASE_URL', 'GEMINI_API_BASE_URL', 'HTTPS_PROXY', 'NODE_EXTRA_CA_CERTS', 'SQUEEZR_PORT', 'SQUEEZR_MITM_PORT', 'openai_base_url', 'NO_PROXY']
317
+ for (const v of vars) {
318
+ try { execSync(`reg delete "HKCU\\Environment" /v ${v} /f`, { stdio: 'pipe' }) } catch {}
319
+ }
320
+ console.log(' [ok] Windows env vars removed')
321
+ } else {
322
+ // Remove squeezr block from shell profiles
323
+ const profiles = [
324
+ path.join(os.homedir(), '.zshrc'),
325
+ path.join(os.homedir(), '.bashrc'),
326
+ path.join(os.homedir(), '.bash_profile'),
327
+ ]
328
+ for (const p of profiles) {
329
+ try {
330
+ const content = fs.readFileSync(p, 'utf-8')
331
+ if (content.includes('# squeezr env vars')) {
332
+ const cleaned = content.replace(/\n?# squeezr env vars[\s\S]*?fi\n?/g, '\n')
333
+ fs.writeFileSync(p, cleaned)
334
+ console.log(` [ok] Cleaned ${p}`)
335
+ }
336
+ } catch {}
337
+ }
338
+ }
339
+
340
+ // 3. Remove CA from certificate stores
341
+ if (process.platform === 'win32') {
342
+ try { execSync('certutil -delstore -user Root "Squeezr-MITM-CA"', { stdio: 'pipe' }); console.log(' [ok] CA removed from user certificate store') } catch {}
343
+ try { execSync('certutil -delstore Root "Squeezr-MITM-CA"', { stdio: 'pipe' }) } catch {}
344
+ } else if (process.platform === 'darwin') {
345
+ try { execSync('security delete-certificate -c "Squeezr-MITM-CA" ~/Library/Keychains/login.keychain-db', { stdio: 'pipe' }); console.log(' [ok] CA removed from Keychain') } catch {}
346
+ }
347
+ // On Linux, CA is only in bundle.crt which gets deleted with ~/.squeezr below
348
+
349
+ // 4. Remove auto-start
350
+ if (process.platform === 'win32') {
351
+ try { execSync('nssm stop SqueezrProxy', { stdio: 'pipe' }) } catch {}
352
+ try { execSync('nssm remove SqueezrProxy confirm', { stdio: 'pipe' }) } catch {}
353
+ try { execSync('schtasks /Delete /TN "Squeezr" /F', { stdio: 'pipe' }); console.log(' [ok] Removed scheduled task') } catch {}
354
+ } else if (process.platform === 'darwin') {
355
+ const plistPath = path.join(os.homedir(), 'Library', 'LaunchAgents', 'com.squeezr.plist')
356
+ try { execSync(`launchctl unload "${plistPath}"`, { stdio: 'pipe' }) } catch {}
357
+ try { fs.unlinkSync(plistPath); console.log(' [ok] Removed launchd plist') } catch {}
358
+ } else {
359
+ try { execSync('systemctl --user disable --now squeezr', { stdio: 'pipe' }) } catch {}
360
+ const servicePath = path.join(os.homedir(), '.config', 'systemd', 'user', 'squeezr.service')
361
+ try { fs.unlinkSync(servicePath); console.log(' [ok] Removed systemd service') } catch {}
362
+ }
363
+
364
+ // 5. Remove ~/.squeezr (logs, cache, CA, stats)
365
+ const squeezrDir = path.join(os.homedir(), '.squeezr')
366
+ try {
367
+ fs.rmSync(squeezrDir, { recursive: true, force: true })
368
+ console.log(` [ok] Removed ${squeezrDir}`)
369
+ } catch {}
370
+
371
+ // 6. Remove global config
372
+ const tomlPath = path.join(ROOT, 'squeezr.toml')
373
+ try { fs.unlinkSync(tomlPath) } catch {}
374
+
375
+ console.log(`
376
+ Done! Squeezr has been completely removed.
377
+
378
+ To finish, run:
379
+ npm uninstall -g squeezr-ai
380
+ `)
381
+ }
382
+
197
383
  // ── squeezr setup ─────────────────────────────────────────────────────────────
198
384
 
199
385
  function setupWindows() {
@@ -205,7 +391,7 @@ function setupWindows() {
205
391
 
206
392
  // 1. Set env vars permanently via setx (user scope, no admin needed)
207
393
  const port = process.env.SQUEEZR_PORT || 8080
208
- const mitmPort = Number(port) + 1
394
+ const mitmPort = getMitmPort(port)
209
395
  const caPath = path.join(os.homedir(), '.squeezr', 'mitm-ca', 'ca.crt')
210
396
  const vars = {
211
397
  ANTHROPIC_BASE_URL: `http://localhost:${port}`,
@@ -360,7 +546,7 @@ function setupUnix() {
360
546
  // 1. Set env vars + auto-heal guard in shell profile
361
547
  const distIndex = path.join(ROOT, 'dist', 'index.js')
362
548
  const port = process.env.SQUEEZR_PORT || 8080
363
- const mitmPort = Number(port) + 1
549
+ const mitmPort = getMitmPort(port)
364
550
  const bundlePath = path.join(os.homedir(), '.squeezr', 'mitm-ca', 'bundle.crt')
365
551
  const shellBlock = [
366
552
  `# squeezr env vars`,
@@ -493,7 +679,7 @@ function setupWSL() {
493
679
  // it in the background. This is the safety net for WSL2 where systemd and
494
680
  // Task Scheduler may both fail.
495
681
  const port = process.env.SQUEEZR_PORT || 8080
496
- const mitmPort = Number(port) + 1
682
+ const mitmPort = getMitmPort(port)
497
683
  const bundlePath = path.join(os.homedir(), '.squeezr', 'mitm-ca', 'bundle.crt')
498
684
  const shellBlock = [
499
685
  `# squeezr env vars`,
@@ -674,6 +860,12 @@ switch (command) {
674
860
  checkStatus()
675
861
  break
676
862
 
863
+ case 'ports':
864
+ await configurePorts()
865
+ break
866
+ case 'uninstall':
867
+ await uninstall()
868
+ break
677
869
  case 'config':
678
870
  showConfig()
679
871
  break
package/dist/codexMitm.js CHANGED
@@ -13,7 +13,7 @@ const CA_DIR = join(homedir(), '.squeezr', 'mitm-ca');
13
13
  const CA_KEY_PATH = join(CA_DIR, 'ca.key');
14
14
  const CA_CERT_PATH = join(CA_DIR, 'ca.crt');
15
15
  export const BUNDLE_PATH = join(CA_DIR, 'bundle.crt');
16
- export const MITM_PORT = (config.port ?? 8080) + 1;
16
+ export const MITM_PORT = config.mitmPort;
17
17
  // ── CA generation ─────────────────────────────────────────────────────────────
18
18
  function ensureCA() {
19
19
  if (fs.existsSync(CA_KEY_PATH) && fs.existsSync(CA_CERT_PATH))
package/dist/config.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export declare class Config {
2
2
  readonly port: number;
3
+ readonly mitmPort: number;
3
4
  readonly threshold: number;
4
5
  readonly keepRecent: number;
5
6
  readonly disabled: boolean;
package/dist/config.js CHANGED
@@ -40,6 +40,7 @@ function env(key, fallback) {
40
40
  }
41
41
  export class Config {
42
42
  port;
43
+ mitmPort;
43
44
  threshold;
44
45
  keepRecent;
45
46
  disabled;
@@ -67,6 +68,7 @@ export class Config {
67
68
  const ad = t.adaptive ?? {};
68
69
  const lo = t.local ?? {};
69
70
  this.port = parseInt(env('SQUEEZR_PORT', String(p.port ?? 8080)));
71
+ this.mitmPort = parseInt(env('SQUEEZR_MITM_PORT', String(p.mitm_port ?? this.port + 1)));
70
72
  this.threshold = parseInt(env('SQUEEZR_THRESHOLD', String(c.threshold ?? 800)));
71
73
  this.keepRecent = parseInt(env('SQUEEZR_KEEP_RECENT', String(c.keep_recent ?? 3)));
72
74
  this.disabled = env('SQUEEZR_DISABLED', String(c.disabled ?? false)) === '1' || env('SQUEEZR_DISABLED', '') === 'true';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "squeezr-ai",
3
- "version": "1.14.14",
3
+ "version": "1.16.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",