squeezr-ai 1.13.0 → 1.14.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
@@ -564,8 +564,30 @@ The installer configures Squeezr to start automatically on login:
564
564
  | macOS | launchd (`~/Library/LaunchAgents/com.squeezr.plist`) | Shell auto-heal |
565
565
  | Linux | systemd user service (`~/.config/systemd/user/squeezr.service`) | Shell auto-heal |
566
566
  | Windows | Task Scheduler (runs at login, restarts on failure) | — |
567
+ | Windows (robust) | **NSSM Windows Service** (auto-restart on crash) | — |
567
568
  | **WSL2** | systemd → Task Scheduler (cascade) | Shell auto-heal |
568
569
 
570
+ ### Windows: NSSM (recommended over Task Scheduler)
571
+
572
+ The built-in Task Scheduler setup requires admin on every reinstall and does **not** restart Squeezr if it crashes mid-session (e.g. due to `ECONNRESET`). For a more robust setup, use [NSSM](https://nssm.cc) to run Squeezr as a proper Windows service:
573
+
574
+ ```powershell
575
+ # Install NSSM
576
+ winget install nssm
577
+
578
+ # Create the service (run as Administrator, adjust paths if needed)
579
+ $node = (where.exe node | Select-Object -First 1)
580
+ $script = "$(npm root -g)\squeezr-ai\bin\squeezr.js"
581
+ nssm install SqueezrProxy $node $script
582
+ nssm set SqueezrProxy AppExit Default Restart
583
+ nssm set SqueezrProxy AppRestartDelay 3000
584
+ nssm start SqueezrProxy
585
+ ```
586
+
587
+ NSSM gives you: auto-start on boot, automatic restart on crash, stdout/stderr logs, and control via `services.msc`.
588
+
589
+ See [NSSM_WINDOWS_SERVICE.md](./NSSM_WINDOWS_SERVICE.md) for the full guide including log setup, troubleshooting, and uninstall steps.
590
+
569
591
  ### WSL2 support
570
592
 
571
593
  `squeezr setup` detects WSL2 automatically and configures both sides:
package/bin/squeezr.js CHANGED
@@ -113,12 +113,23 @@ function stopProxy() {
113
113
  const match = out.match(/LISTENING\s+(\d+)/)
114
114
  pid = match?.[1]
115
115
  } else {
116
- pid = execSync(`lsof -ti :${port}`, { encoding: 'utf-8', stdio: 'pipe' }).trim()
116
+ // Use -sTCP:LISTEN to get only the listening process, not connected clients.
117
+ // lsof may return multiple PIDs without this flag.
118
+ try {
119
+ pid = execSync(`lsof -ti :${port} -sTCP:LISTEN`, { encoding: 'utf-8', stdio: 'pipe' }).trim()
120
+ } catch {
121
+ // fallback: fuser (available on most Linux/WSL)
122
+ try {
123
+ pid = execSync(`fuser ${port}/tcp 2>/dev/null`, { encoding: 'utf-8', stdio: 'pipe' }).trim()
124
+ } catch {}
125
+ }
117
126
  }
118
127
  if (!pid) {
119
128
  console.log(`Squeezr is not running on port ${port}`)
120
129
  return
121
130
  }
131
+ // Take only the first PID in case multiple are returned
132
+ pid = pid.split(/\s+/)[0]
122
133
  if (process.platform === 'win32') {
123
134
  execSync(`taskkill /F /PID ${pid}`, { stdio: 'pipe' })
124
135
  } else {
@@ -193,29 +204,65 @@ function setupWindows() {
193
204
  }
194
205
  }
195
206
 
196
- // 2. Register Task Scheduler with hidden window so no console pops up on login.
197
- // The action runs: powershell -WindowStyle Hidden -Command "node dist/index.js"
198
- const taskName = 'Squeezr'
199
- const nodeArg = `${nodeExe} \`"${distIndex}\`"`
200
- const ps = [
201
- `$e = Get-ScheduledTask -TaskName '${taskName}' -ErrorAction SilentlyContinue`,
202
- `if ($e) { Unregister-ScheduledTask -TaskName '${taskName}' -Confirm:$false }`,
203
- `$a = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument '-WindowStyle Hidden -NonInteractive -Command "${nodeArg}"' -WorkingDirectory '${ROOT}'`,
204
- `$t = New-ScheduledTaskTrigger -AtLogon`,
205
- `$s = New-ScheduledTaskSettingsSet -ExecutionTimeLimit 0 -RestartCount 5 -RestartInterval (New-TimeSpan -Minutes 1)`,
206
- `Register-ScheduledTask -TaskName '${taskName}' -Action $a -Trigger $t -Settings $s -RunLevel Highest -Force | Out-Null`,
207
- ].join('; ')
207
+ // 2. Auto-start: try NSSM (Windows service, survives crashes) fallback to Task Scheduler
208
+ const logDir = path.join(os.homedir(), '.squeezr')
209
+ const serviceName = 'SqueezrProxy'
210
+ let autoStartOk = false
208
211
 
209
- try {
210
- execSync(`powershell -NoProfile -Command "${ps}"`, { stdio: 'pipe' })
211
- console.log(` [ok] Auto-start registered in Task Scheduler (hidden, starts on login)`)
212
- } catch {
213
- console.log(` [warn] Task Scheduler failed — run as admin for auto-start on login`)
212
+ const nssmAvailable = (() => {
213
+ try { execSync('where nssm', { stdio: 'pipe' }); return true } catch { return false }
214
+ })()
215
+
216
+ if (nssmAvailable) {
217
+ try {
218
+ // Remove existing service if present (ignore errors)
219
+ try { execSync(`nssm stop ${serviceName}`, { stdio: 'pipe' }) } catch {}
220
+ try { execSync(`nssm remove ${serviceName} confirm`, { stdio: 'pipe' }) } catch {}
221
+
222
+ execSync(`nssm install ${serviceName} "${nodeExe}" "${distIndex}"`, { stdio: 'pipe' })
223
+ execSync(`nssm set ${serviceName} AppDirectory "${ROOT}"`, { stdio: 'pipe' })
224
+ execSync(`nssm set ${serviceName} AppStdout "${logDir}\\service-stdout.log"`, { stdio: 'pipe' })
225
+ execSync(`nssm set ${serviceName} AppStderr "${logDir}\\service-stderr.log"`, { stdio: 'pipe' })
226
+ execSync(`nssm set ${serviceName} AppRotateFiles 1`, { stdio: 'pipe' })
227
+ execSync(`nssm set ${serviceName} AppRotateSeconds 86400`, { stdio: 'pipe' })
228
+ execSync(`nssm set ${serviceName} AppExit Default Restart`, { stdio: 'pipe' })
229
+ execSync(`nssm set ${serviceName} AppRestartDelay 3000`, { stdio: 'pipe' })
230
+ execSync(`nssm set ${serviceName} Description "Squeezr AI token compression proxy on port 8080"`, { stdio: 'pipe' })
231
+ execSync(`nssm start ${serviceName}`, { stdio: 'pipe' })
232
+ console.log(` [ok] Auto-start registered as Windows service via NSSM (auto-restart on crash)`)
233
+ autoStartOk = true
234
+ } catch (err) {
235
+ const msg = err.stderr?.toString() || err.message || ''
236
+ if (msg.includes('Access') || msg.includes('admin') || msg.includes('5')) {
237
+ console.log(` [warn] NSSM requires admin — run as Administrator for service install`)
238
+ } else {
239
+ console.log(` [warn] NSSM install failed: ${msg.trim().split('\n')[0]}`)
240
+ }
241
+ }
242
+ }
243
+
244
+ if (!autoStartOk) {
245
+ // Fallback: Task Scheduler (no crash recovery, but no admin needed for user tasks)
246
+ const taskName = 'Squeezr'
247
+ const nodeArg = `${nodeExe} \`"${distIndex}\`"`
248
+ const ps = [
249
+ `$e = Get-ScheduledTask -TaskName '${taskName}' -ErrorAction SilentlyContinue`,
250
+ `if ($e) { Unregister-ScheduledTask -TaskName '${taskName}' -Confirm:$false }`,
251
+ `$a = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument '-WindowStyle Hidden -NonInteractive -Command "${nodeArg}"' -WorkingDirectory '${ROOT}'`,
252
+ `$t = New-ScheduledTaskTrigger -AtLogon`,
253
+ `$s = New-ScheduledTaskSettingsSet -ExecutionTimeLimit 0 -RestartCount 5 -RestartInterval (New-TimeSpan -Minutes 1)`,
254
+ `Register-ScheduledTask -TaskName '${taskName}' -Action $a -Trigger $t -Settings $s -RunLevel Highest -Force | Out-Null`,
255
+ ].join('; ')
256
+ try {
257
+ execSync(`powershell -NoProfile -Command "${ps}"`, { stdio: 'pipe' })
258
+ console.log(` [ok] Auto-start registered in Task Scheduler (install NSSM for crash recovery)`)
259
+ } catch {
260
+ console.log(` [warn] Auto-start failed — install NSSM or run as admin: https://nssm.cc`)
261
+ }
214
262
  }
215
263
 
216
264
  // 3. Start Squeezr right now as a detached background process (no window)
217
265
  // Logs go to ~/.squeezr/squeezr.log
218
- const logDir = path.join(os.homedir(), '.squeezr')
219
266
  const logFile = path.join(logDir, 'squeezr.log')
220
267
  fs.mkdirSync(logDir, { recursive: true })
221
268
  const logFd = fs.openSync(logFile, 'a')
@@ -261,6 +308,7 @@ function setupUnix() {
261
308
  `export ANTHROPIC_BASE_URL=http://localhost:${port}`,
262
309
  `export openai_base_url=http://localhost:${port}`,
263
310
  `export GEMINI_API_BASE_URL=http://localhost:${port}`,
311
+ `# squeezr MITM proxy for Codex (TLS interception)`,
264
312
  `export HTTPS_PROXY=http://localhost:${mitmPort}`,
265
313
  `export SSL_CERT_FILE=${bundlePath}`,
266
314
  `# squeezr auto-heal: start proxy if not running`,
@@ -281,13 +329,14 @@ function setupUnix() {
281
329
  fs.appendFileSync(profile, `\n${shellBlock}\n`)
282
330
  console.log(` [ok] Env vars + auto-heal added to ${profile}`)
283
331
  } else {
284
- if (!existing.includes('squeezr auto-heal')) {
332
+ if (!existing.includes('SSL_CERT_FILE') || !existing.includes('squeezr MITM')) {
333
+ // Re-write block to include MITM vars
285
334
  const updatedContent = existing.replace(
286
- /# squeezr env vars\n(?:export [A-Z_]+=http:\/\/localhost:\d+\n?)*/,
335
+ /# squeezr env vars[\s\S]*?fi\n/,
287
336
  shellBlock + '\n'
288
337
  )
289
338
  fs.writeFileSync(profile, updatedContent)
290
- console.log(` [ok] Auto-heal guard added to ${profile}`)
339
+ console.log(` [ok] Shell profile updated with MITM proxy vars`)
291
340
  } else {
292
341
  console.log(` [skip] Env vars + auto-heal already in ${profile}`)
293
342
  }
@@ -412,16 +461,16 @@ function setupWSL() {
412
461
  fs.appendFileSync(profile, `\n${shellBlock}\n`)
413
462
  console.log(` [ok] Env vars + auto-heal added to ${profile}`)
414
463
  } else {
415
- // Update existing block to include auto-heal if missing
416
- if (!existing.includes('squeezr auto-heal')) {
464
+ // Update existing block if missing MITM proxy vars
465
+ if (!existing.includes('SSL_CERT_FILE') || !existing.includes('HTTPS_PROXY')) {
417
466
  const updatedContent = existing.replace(
418
- /# squeezr env vars\n(?:export [A-Z_]+=http:\/\/localhost:\d+\n?)*/,
467
+ /# squeezr env vars[\s\S]*?fi\n/,
419
468
  shellBlock + '\n'
420
469
  )
421
470
  fs.writeFileSync(profile, updatedContent)
422
- console.log(` [ok] Auto-heal guard added to ${profile}`)
471
+ console.log(` [ok] Shell profile updated with MITM proxy vars`)
423
472
  } else {
424
- console.log(` [skip] Env vars + auto-heal already in ${profile}`)
473
+ console.log(` [skip] Env vars already in ${profile}`)
425
474
  }
426
475
  }
427
476
 
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "1.13.0";
1
+ export declare const VERSION = "1.14.0";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '1.13.0';
1
+ export const VERSION = '1.14.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "squeezr-ai",
3
- "version": "1.13.0",
3
+ "version": "1.14.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",