squeezr-ai 1.15.0 → 1.16.1

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 (3) hide show
  1. package/README.md +11 -5
  2. package/bin/squeezr.js +120 -15
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  **Token compression proxy for AI coding CLIs.** Sits between your CLI and the API, compresses context on the fly, saves thousands of tokens per session.
4
4
 
5
- [![npm](https://img.shields.io/npm/v/squeezr-ai)](https://www.npmjs.com/package/squeezr-ai) [![license](https://img.shields.io/npm/l/squeezr-ai)](LICENSE) [![tests](https://img.shields.io/badge/tests-190%20passing-brightgreen)]()
5
+ [![npm](https://img.shields.io/npm/v/squeezr-ai)](https://www.npmjs.com/package/squeezr-ai) [![license](https://img.shields.io/npm/l/squeezr-ai)](LICENSE) [![tests](https://img.shields.io/badge/tests-219%20passing-brightgreen)]()
6
6
 
7
7
  ## Supported CLIs
8
8
 
@@ -24,7 +24,7 @@ squeezr start
24
24
  ```
25
25
 
26
26
  `squeezr setup` handles everything automatically:
27
- - Sets `ANTHROPIC_BASE_URL`, `GEMINI_API_BASE_URL`, `HTTPS_PROXY`, `NODE_EXTRA_CA_CERTS`, `NO_PROXY`
27
+ - Sets `ANTHROPIC_BASE_URL`, `GEMINI_API_BASE_URL`, `HTTPS_PROXY`, `NODE_EXTRA_CA_CERTS`
28
28
  - Registers auto-start (launchd on macOS, systemd on Linux, Task Scheduler/NSSM on Windows)
29
29
  - **Windows:** imports the MITM CA into the Windows Certificate Store (user-level, no admin required) so Rust-based CLIs like Codex trust the proxy's TLS certificates
30
30
  - **macOS/Linux:** generates a CA bundle at `~/.squeezr/mitm-ca/bundle.crt` for `SSL_CERT_FILE`
@@ -92,13 +92,16 @@ Compression aggressiveness scales with context window usage:
92
92
 
93
93
  Codex uses WebSocket over TLS to `chatgpt.com` with OAuth authentication — it cannot be proxied via `OPENAI_BASE_URL`. Squeezr runs a TLS-terminating MITM proxy on port 8081 that intercepts and compresses WebSocket frames. See [CODEX.md](CODEX.md) for the full technical breakdown.
94
94
 
95
+ The MITM proxy **only intercepts `chatgpt.com`** traffic. All other HTTPS requests (npm, git, curl, etc.) pass through as a transparent TCP tunnel — no certificate needed, no interference.
96
+
95
97
  ## Configuration
96
98
 
97
99
  ### Global config: `squeezr.toml` (next to the binary)
98
100
 
99
101
  ```toml
100
102
  [proxy]
101
- port = 8080
103
+ port = 8080 # HTTP proxy (Claude, Aider, Gemini)
104
+ mitm_port = 8081 # MITM proxy (Codex) — defaults to port + 1
102
105
 
103
106
  [compression]
104
107
  threshold = 800 # min chars to trigger compression
@@ -133,7 +136,8 @@ Project-level config is deep-merged over global config. Useful for per-repo tuni
133
136
 
134
137
  | Variable | Default | Description |
135
138
  |----------|---------|-------------|
136
- | `SQUEEZR_PORT` | `8080` | Proxy port (MITM port = this + 1) |
139
+ | `SQUEEZR_PORT` | `8080` | HTTP proxy port (Claude, Aider, Gemini) |
140
+ | `SQUEEZR_MITM_PORT` | `8081` | MITM proxy port (Codex) — defaults to SQUEEZR_PORT + 1 |
137
141
  | `SQUEEZR_THRESHOLD` | `800` | Min chars to compress |
138
142
  | `SQUEEZR_KEEP_RECENT` | `3` | Recent results to skip |
139
143
  | `SQUEEZR_DISABLED` | `false` | Disable all compression |
@@ -167,13 +171,15 @@ Squeezr uses cheap/free models for AI compression (the deterministic layer is pu
167
171
 
168
172
  ```bash
169
173
  squeezr setup # configure env vars, auto-start, CA trust
170
- squeezr start # start the proxy (foreground)
174
+ squeezr start # start the proxy (auto-restarts if version mismatch after update)
171
175
  squeezr stop # stop the proxy
172
176
  squeezr status # check if proxy is running
173
177
  squeezr logs # show last 50 log lines
174
178
  squeezr config # print current config
179
+ squeezr ports # change HTTP and MITM proxy ports
175
180
  squeezr gain # estimate token savings for a directory
176
181
  squeezr discover # detect which AI CLIs are installed
182
+ squeezr uninstall # remove Squeezr completely (env vars, CA, auto-start, logs)
177
183
  squeezr version # print version
178
184
  ```
179
185
 
package/bin/squeezr.js CHANGED
@@ -42,6 +42,7 @@ Usage:
42
42
  squeezr status Check if proxy is running
43
43
  squeezr config Print config file path and current settings
44
44
  squeezr ports Change HTTP and MITM proxy ports
45
+ squeezr uninstall Remove Squeezr completely (env vars, CA, auto-start, logs)
45
46
  squeezr version Print version
46
47
  squeezr help Show this help
47
48
  `
@@ -66,22 +67,32 @@ async function startDaemon() {
66
67
  process.exit(1)
67
68
  }
68
69
 
69
- // Check if already running
70
+ // Check if already running — and if the version matches
70
71
  const port = process.env.SQUEEZR_PORT || 8080
71
- const running = await new Promise(resolve => {
72
+ const runningVersion = await new Promise(resolve => {
72
73
  const req = http.get(`http://localhost:${port}/squeezr/health`, res => {
73
- resolve(res.statusCode === 200)
74
- 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
+ })
75
79
  })
76
- req.on('error', () => resolve(false))
77
- req.setTimeout(2000, () => { req.destroy(); resolve(false) })
80
+ req.on('error', () => resolve(null))
81
+ req.setTimeout(2000, () => { req.destroy(); resolve(null) })
78
82
  })
79
- if (running) {
80
- const mitmPort = getMitmPort(port)
81
- console.log(`Squeezr is already running`)
82
- console.log(` HTTP proxy (Claude/Aider/Gemini): http://localhost:${port}`)
83
- console.log(` MITM proxy (Codex): http://localhost:${mitmPort}`)
84
- 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))
85
96
  }
86
97
 
87
98
  // Launch detached background process
@@ -153,14 +164,17 @@ function stopProxy() {
153
164
  } else {
154
165
  execSync(`kill -9 ${pid}`, { stdio: 'pipe' })
155
166
  }
156
- console.log(`Squeezr stopped (pid ${pid} on port ${p})`)
157
167
  killed = true
158
168
  } catch {}
159
169
  }
160
170
  } catch {}
161
171
  }
162
- if (!killed) {
163
- console.log(`Squeezr is not running on port ${port}`)
172
+ if (killed) {
173
+ console.log(`Squeezr stopped`)
174
+ console.log(` HTTP proxy (Claude/Aider/Gemini): http://localhost:${port}`)
175
+ console.log(` MITM proxy (Codex): http://localhost:${mitmPort}`)
176
+ } else {
177
+ console.log(`Squeezr is not running`)
164
178
  }
165
179
  }
166
180
 
@@ -281,6 +295,94 @@ async function configurePorts() {
281
295
  console.log(` squeezr stop && squeezr start`)
282
296
  }
283
297
 
298
+ // ── squeezr uninstall ─────────────────────────────────────────────────────────
299
+
300
+ async function uninstall() {
301
+ const { createInterface } = await import('readline')
302
+ const rl = createInterface({ input: process.stdin, output: process.stdout })
303
+ const answer = await new Promise(resolve => rl.question(
304
+ 'This will remove Squeezr completely: stop proxy, remove env vars, CA certs, auto-start, config, and logs.\nContinue? [y/N] ', resolve
305
+ ))
306
+ rl.close()
307
+ if (answer.trim().toLowerCase() !== 'y') {
308
+ console.log('Cancelled.')
309
+ return
310
+ }
311
+
312
+ console.log('\nUninstalling Squeezr...\n')
313
+
314
+ // 1. Stop proxy
315
+ stopProxy()
316
+
317
+ // 2. Remove env vars
318
+ if (process.platform === 'win32') {
319
+ 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']
320
+ for (const v of vars) {
321
+ try { execSync(`reg delete "HKCU\\Environment" /v ${v} /f`, { stdio: 'pipe' }) } catch {}
322
+ }
323
+ console.log(' [ok] Windows env vars removed')
324
+ } else {
325
+ // Remove squeezr block from shell profiles
326
+ const profiles = [
327
+ path.join(os.homedir(), '.zshrc'),
328
+ path.join(os.homedir(), '.bashrc'),
329
+ path.join(os.homedir(), '.bash_profile'),
330
+ ]
331
+ for (const p of profiles) {
332
+ try {
333
+ const content = fs.readFileSync(p, 'utf-8')
334
+ if (content.includes('# squeezr env vars')) {
335
+ const cleaned = content.replace(/\n?# squeezr env vars[\s\S]*?fi\n?/g, '\n')
336
+ fs.writeFileSync(p, cleaned)
337
+ console.log(` [ok] Cleaned ${p}`)
338
+ }
339
+ } catch {}
340
+ }
341
+ }
342
+
343
+ // 3. Remove CA from certificate stores
344
+ if (process.platform === 'win32') {
345
+ try { execSync('certutil -delstore -user Root "Squeezr-MITM-CA"', { stdio: 'pipe' }); console.log(' [ok] CA removed from user certificate store') } catch {}
346
+ try { execSync('certutil -delstore Root "Squeezr-MITM-CA"', { stdio: 'pipe' }) } catch {}
347
+ } else if (process.platform === 'darwin') {
348
+ try { execSync('security delete-certificate -c "Squeezr-MITM-CA" ~/Library/Keychains/login.keychain-db', { stdio: 'pipe' }); console.log(' [ok] CA removed from Keychain') } catch {}
349
+ }
350
+ // On Linux, CA is only in bundle.crt which gets deleted with ~/.squeezr below
351
+
352
+ // 4. Remove auto-start
353
+ if (process.platform === 'win32') {
354
+ try { execSync('nssm stop SqueezrProxy', { stdio: 'pipe' }) } catch {}
355
+ try { execSync('nssm remove SqueezrProxy confirm', { stdio: 'pipe' }) } catch {}
356
+ try { execSync('schtasks /Delete /TN "Squeezr" /F', { stdio: 'pipe' }); console.log(' [ok] Removed scheduled task') } catch {}
357
+ } else if (process.platform === 'darwin') {
358
+ const plistPath = path.join(os.homedir(), 'Library', 'LaunchAgents', 'com.squeezr.plist')
359
+ try { execSync(`launchctl unload "${plistPath}"`, { stdio: 'pipe' }) } catch {}
360
+ try { fs.unlinkSync(plistPath); console.log(' [ok] Removed launchd plist') } catch {}
361
+ } else {
362
+ try { execSync('systemctl --user disable --now squeezr', { stdio: 'pipe' }) } catch {}
363
+ const servicePath = path.join(os.homedir(), '.config', 'systemd', 'user', 'squeezr.service')
364
+ try { fs.unlinkSync(servicePath); console.log(' [ok] Removed systemd service') } catch {}
365
+ }
366
+
367
+ // 5. Remove ~/.squeezr (logs, cache, CA, stats)
368
+ const squeezrDir = path.join(os.homedir(), '.squeezr')
369
+ try {
370
+ fs.rmSync(squeezrDir, { recursive: true, force: true })
371
+ console.log(` [ok] Removed ${squeezrDir}`)
372
+ } catch {}
373
+
374
+ // 6. Remove global config
375
+ const tomlPath = path.join(ROOT, 'squeezr.toml')
376
+ try { fs.unlinkSync(tomlPath) } catch {}
377
+
378
+ console.log(`
379
+ Done! Squeezr has been completely removed.
380
+
381
+ To finish, run:
382
+ npm uninstall -g squeezr-ai
383
+ `)
384
+ }
385
+
284
386
  // ── squeezr setup ─────────────────────────────────────────────────────────────
285
387
 
286
388
  function setupWindows() {
@@ -764,6 +866,9 @@ switch (command) {
764
866
  case 'ports':
765
867
  await configurePorts()
766
868
  break
869
+ case 'uninstall':
870
+ await uninstall()
871
+ break
767
872
  case 'config':
768
873
  showConfig()
769
874
  break
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "squeezr-ai",
3
- "version": "1.15.0",
3
+ "version": "1.16.1",
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",