squeezr-ai 1.10.6 → 1.11.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.
package/README.md CHANGED
@@ -456,11 +456,28 @@ More importantly: sending 60-80% fewer tokens means Claude processes a smaller c
456
456
 
457
457
  The installer configures Squeezr to start automatically on login:
458
458
 
459
- | OS | Method |
460
- |---|---|
461
- | macOS | launchd (`~/Library/LaunchAgents/com.squeezr.plist`) |
462
- | Linux | systemd user service (`~/.config/systemd/user/squeezr.service`) |
463
- | Windows | Task Scheduler (runs at login, restarts on failure) |
459
+ | OS | Method | Fallback |
460
+ |---|---|---|
461
+ | macOS | launchd (`~/Library/LaunchAgents/com.squeezr.plist`) | Shell auto-heal |
462
+ | Linux | systemd user service (`~/.config/systemd/user/squeezr.service`) | Shell auto-heal |
463
+ | Windows | Task Scheduler (runs at login, restarts on failure) | — |
464
+ | **WSL2** | systemd → Task Scheduler (cascade) | Shell auto-heal |
465
+
466
+ ### WSL2 support
467
+
468
+ `squeezr setup` detects WSL2 automatically and configures both sides:
469
+
470
+ - **WSL shell**: env vars + auto-heal guard in `.bashrc` / `.zshrc`
471
+ - **Windows**: env vars via `setx` (persistent in registry)
472
+ - **Auto-start**: tries systemd first (WSL2 with `systemd=true` in `/etc/wsl.conf`), falls back to Windows Task Scheduler via `powershell.exe`
473
+
474
+ ### Auto-heal
475
+
476
+ On every platform, `squeezr setup` adds a lightweight guard to your shell profile. Each time you open a terminal, it checks if the proxy is alive (`curl localhost:8080/squeezr/health`). If not, it starts it in the background — silently, in ~100ms. This means:
477
+
478
+ - If the service manager fails, the proxy still starts on your next terminal
479
+ - If the proxy crashes mid-session, the next terminal restores it
480
+ - Zero manual intervention after `squeezr setup`, ever
464
481
 
465
482
  ---
466
483
 
package/bin/squeezr.js CHANGED
@@ -47,6 +47,46 @@ function runNode(script, extraArgs = []) {
47
47
  child.on('exit', code => process.exit(code ?? 0))
48
48
  }
49
49
 
50
+ async function startDaemon() {
51
+ const distIndex = path.join(ROOT, 'dist', 'index.js')
52
+ if (!fs.existsSync(distIndex)) {
53
+ console.error(`Error: ${distIndex} not found. Run 'npm run build' first.`)
54
+ process.exit(1)
55
+ }
56
+
57
+ // Check if already running
58
+ const port = process.env.SQUEEZR_PORT || 8080
59
+ const running = await new Promise(resolve => {
60
+ const req = http.get(`http://localhost:${port}/squeezr/health`, res => {
61
+ resolve(res.statusCode === 200)
62
+ res.destroy()
63
+ })
64
+ req.on('error', () => resolve(false))
65
+ req.setTimeout(2000, () => { req.destroy(); resolve(false) })
66
+ })
67
+ if (running) {
68
+ console.log(`Squeezr is already running on port ${port}`)
69
+ return
70
+ }
71
+
72
+ // Launch detached background process
73
+ const logDir = path.join(os.homedir(), '.squeezr')
74
+ const logFile = path.join(logDir, 'squeezr.log')
75
+ fs.mkdirSync(logDir, { recursive: true })
76
+ const logFd = fs.openSync(logFile, 'a')
77
+ const child = spawn(process.execPath, [distIndex], {
78
+ detached: true,
79
+ stdio: ['ignore', logFd, logFd],
80
+ windowsHide: true,
81
+ cwd: ROOT,
82
+ env: { ...process.env, SQUEEZR_DAEMON: '1' },
83
+ })
84
+ child.unref()
85
+ fs.closeSync(logFd)
86
+ console.log(`Squeezr started in background (pid ${child.pid})`)
87
+ console.log(`Logs → ${logFile}`)
88
+ }
89
+
50
90
  function showLogs() {
51
91
  const logFile = path.join(os.homedir(), '.squeezr', 'squeezr.log')
52
92
  if (!fs.existsSync(logFile)) {
@@ -211,12 +251,20 @@ function setupUnix() {
211
251
 
212
252
  console.log(`Setting up Squeezr for ${platform === 'darwin' ? 'macOS' : 'Linux'}...\n`)
213
253
 
214
- // 1. Set env vars in shell profile
215
- const exportLines = [
216
- 'export ANTHROPIC_BASE_URL=http://localhost:8080',
217
- 'export OPENAI_BASE_URL=http://localhost:8080',
218
- 'export GEMINI_API_BASE_URL=http://localhost:8080',
219
- ]
254
+ // 1. Set env vars + auto-heal guard in shell profile
255
+ const distIndex = path.join(ROOT, 'dist', 'index.js')
256
+ const port = process.env.SQUEEZR_PORT || 8080
257
+ const shellBlock = [
258
+ `# squeezr env vars`,
259
+ `export ANTHROPIC_BASE_URL=http://localhost:${port}`,
260
+ `export OPENAI_BASE_URL=http://localhost:${port}`,
261
+ `export GEMINI_API_BASE_URL=http://localhost:${port}`,
262
+ `# squeezr auto-heal: start proxy if not running`,
263
+ `if ! curl -sf http://localhost:${port}/squeezr/health >/dev/null 2>&1; then`,
264
+ ` nohup ${nodeExe} ${distIndex} >> "${os.homedir()}/.squeezr/squeezr.log" 2>&1 &`,
265
+ ` disown`,
266
+ `fi`,
267
+ ].join('\n')
220
268
  const marker = '# squeezr env vars'
221
269
  const profiles = [
222
270
  path.join(os.homedir(), '.zshrc'),
@@ -226,10 +274,19 @@ function setupUnix() {
226
274
  const profile = profiles.find(p => fs.existsSync(p)) ?? profiles[0]
227
275
  const existing = fs.existsSync(profile) ? fs.readFileSync(profile, 'utf-8') : ''
228
276
  if (!existing.includes(marker)) {
229
- fs.appendFileSync(profile, `\n${marker}\n${exportLines.join('\n')}\n`)
230
- console.log(` [ok] Env vars added to ${profile}`)
277
+ fs.appendFileSync(profile, `\n${shellBlock}\n`)
278
+ console.log(` [ok] Env vars + auto-heal added to ${profile}`)
231
279
  } else {
232
- console.log(` [skip] Env vars already in ${profile}`)
280
+ if (!existing.includes('squeezr auto-heal')) {
281
+ const updatedContent = existing.replace(
282
+ /# squeezr env vars\n(?:export [A-Z_]+=http:\/\/localhost:\d+\n?)*/,
283
+ shellBlock + '\n'
284
+ )
285
+ fs.writeFileSync(profile, updatedContent)
286
+ console.log(` [ok] Auto-heal guard added to ${profile}`)
287
+ } else {
288
+ console.log(` [skip] Env vars + auto-heal already in ${profile}`)
289
+ }
233
290
  }
234
291
 
235
292
  // 2a. macOS — launchd
@@ -300,16 +357,184 @@ Done!
300
357
  `)
301
358
  }
302
359
 
360
+ // ── WSL2 detection ───────────────────────────────────────────────────────────
361
+
362
+ function isWSL() {
363
+ try {
364
+ const release = fs.readFileSync('/proc/version', 'utf-8')
365
+ return /microsoft|wsl/i.test(release)
366
+ } catch {
367
+ return false
368
+ }
369
+ }
370
+
371
+ // ── squeezr setup — WSL2 ────────────────────────────────────────────────────
372
+
373
+ function setupWSL() {
374
+ const nodeExe = process.execPath
375
+ const distIndex = path.join(ROOT, 'dist', 'index.js')
376
+
377
+ console.log('Setting up Squeezr for WSL2...\n')
378
+
379
+ // 1. Set env vars + auto-heal guard in WSL shell profile (.bashrc / .zshrc)
380
+ // The guard checks if the proxy is alive on terminal open. If not, it starts
381
+ // it in the background. This is the safety net for WSL2 where systemd and
382
+ // Task Scheduler may both fail.
383
+ const port = process.env.SQUEEZR_PORT || 8080
384
+ const shellBlock = [
385
+ `# squeezr env vars`,
386
+ `export ANTHROPIC_BASE_URL=http://localhost:${port}`,
387
+ `export OPENAI_BASE_URL=http://localhost:${port}`,
388
+ `export GEMINI_API_BASE_URL=http://localhost:${port}`,
389
+ `# squeezr auto-heal: start proxy if not running`,
390
+ `if ! curl -sf http://localhost:${port}/squeezr/health >/dev/null 2>&1; then`,
391
+ ` nohup ${nodeExe} ${distIndex} >> "${os.homedir()}/.squeezr/squeezr.log" 2>&1 &`,
392
+ ` disown`,
393
+ `fi`,
394
+ ].join('\n')
395
+ const marker = '# squeezr env vars'
396
+ const profiles = [
397
+ path.join(os.homedir(), '.zshrc'),
398
+ path.join(os.homedir(), '.bashrc'),
399
+ path.join(os.homedir(), '.bash_profile'),
400
+ ]
401
+ const profile = profiles.find(p => fs.existsSync(p)) ?? profiles[1]
402
+ const existing = fs.existsSync(profile) ? fs.readFileSync(profile, 'utf-8') : ''
403
+ if (!existing.includes(marker)) {
404
+ fs.appendFileSync(profile, `\n${shellBlock}\n`)
405
+ console.log(` [ok] Env vars + auto-heal added to ${profile}`)
406
+ } else {
407
+ // Update existing block to include auto-heal if missing
408
+ if (!existing.includes('squeezr auto-heal')) {
409
+ const updatedContent = existing.replace(
410
+ /# squeezr env vars\n(?:export [A-Z_]+=http:\/\/localhost:\d+\n?)*/,
411
+ shellBlock + '\n'
412
+ )
413
+ fs.writeFileSync(profile, updatedContent)
414
+ console.log(` [ok] Auto-heal guard added to ${profile}`)
415
+ } else {
416
+ console.log(` [skip] Env vars + auto-heal already in ${profile}`)
417
+ }
418
+ }
419
+
420
+ // 2. Set Windows env vars via setx.exe (so Windows-launched CLIs see them)
421
+ const setxExe = '/mnt/c/Windows/System32/setx.exe'
422
+ const winVars = {
423
+ ANTHROPIC_BASE_URL: 'http://localhost:8080',
424
+ OPENAI_BASE_URL: 'http://localhost:8080',
425
+ GEMINI_API_BASE_URL: 'http://localhost:8080',
426
+ }
427
+ if (fs.existsSync(setxExe)) {
428
+ for (const [key, value] of Object.entries(winVars)) {
429
+ try {
430
+ execSync(`"${setxExe}" ${key} "${value}"`, { stdio: 'pipe' })
431
+ console.log(` [ok] Windows env: ${key}=${value}`)
432
+ } catch {
433
+ console.log(` [skip] Windows env: ${key} could not be set`)
434
+ }
435
+ }
436
+ } else {
437
+ console.log(' [skip] setx.exe not found — Windows env vars not set')
438
+ }
439
+
440
+ // 3. Auto-start: try systemd first (WSL2 with systemd enabled), fallback to
441
+ // Windows Task Scheduler, then plain background process
442
+ let autoStartDone = false
443
+
444
+ // 3a. Try systemd (works on newer WSL2 with [boot] systemd=true in wsl.conf)
445
+ try {
446
+ const serviceDir = path.join(os.homedir(), '.config', 'systemd', 'user')
447
+ fs.mkdirSync(serviceDir, { recursive: true })
448
+ const servicePath = path.join(serviceDir, 'squeezr.service')
449
+ fs.writeFileSync(servicePath, `[Unit]
450
+ Description=Squeezr AI proxy
451
+ After=network.target
452
+
453
+ [Service]
454
+ ExecStart=${nodeExe} ${distIndex}
455
+ Restart=always
456
+ RestartSec=5
457
+ WorkingDirectory=${ROOT}
458
+
459
+ [Install]
460
+ WantedBy=default.target
461
+ `)
462
+ execSync('systemctl --user daemon-reload && systemctl --user enable --now squeezr', { stdio: 'pipe' })
463
+ console.log(' [ok] Auto-start registered via systemd')
464
+ autoStartDone = true
465
+ } catch {
466
+ // systemd not available — try Windows Task Scheduler
467
+ }
468
+
469
+ // 3b. Fallback: Windows Task Scheduler via powershell.exe
470
+ if (!autoStartDone) {
471
+ const winNodeExe = execSync('wslpath -w "$(which node)"', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim()
472
+ const winDistIndex = execSync(`wslpath -w "${distIndex}"`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim()
473
+ const winRoot = execSync(`wslpath -w "${ROOT}"`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim()
474
+ const taskName = 'Squeezr'
475
+ const ps = [
476
+ `$e = Get-ScheduledTask -TaskName '${taskName}' -ErrorAction SilentlyContinue`,
477
+ `if ($e) { Unregister-ScheduledTask -TaskName '${taskName}' -Confirm:$false }`,
478
+ `$a = New-ScheduledTaskAction -Execute 'wsl.exe' -Argument '-d ${os.hostname()} -- ${nodeExe} ${distIndex}' -WorkingDirectory '${winRoot}'`,
479
+ `$t = New-ScheduledTaskTrigger -AtLogon`,
480
+ `$s = New-ScheduledTaskSettingsSet -ExecutionTimeLimit 0 -RestartCount 5 -RestartInterval (New-TimeSpan -Minutes 1)`,
481
+ `Register-ScheduledTask -TaskName '${taskName}' -Action $a -Trigger $t -Settings $s -Force | Out-Null`,
482
+ ].join('; ')
483
+
484
+ try {
485
+ execSync(`powershell.exe -NoProfile -Command "${ps}"`, { stdio: 'pipe' })
486
+ console.log(' [ok] Auto-start registered via Windows Task Scheduler')
487
+ autoStartDone = true
488
+ } catch {
489
+ console.log(' [warn] Task Scheduler failed — run PowerShell as admin for auto-start')
490
+ }
491
+ }
492
+
493
+ // 4. Start proxy now as a detached background process
494
+ const logDir = path.join(os.homedir(), '.squeezr')
495
+ const logFile = path.join(logDir, 'squeezr.log')
496
+ fs.mkdirSync(logDir, { recursive: true })
497
+ const logFd = fs.openSync(logFile, 'a')
498
+ const child = spawn(nodeExe, [distIndex], {
499
+ detached: true,
500
+ stdio: ['ignore', logFd, logFd],
501
+ cwd: ROOT,
502
+ })
503
+ child.unref()
504
+ fs.closeSync(logFd)
505
+ console.log(` [ok] Squeezr started in background (pid ${child.pid})`)
506
+ console.log(` [ok] Logs → ${logFile}`)
507
+
508
+ // 5. Apply env vars to current process so calling `source` is not needed
509
+ // This won't affect the parent shell, but at least prints guidance.
510
+ console.log(`
511
+ Done!
512
+
513
+ Squeezr is running on http://localhost:8080
514
+ All CLIs (Claude Code, Codex, Aider, Gemini, Ollama) are configured.
515
+
516
+ Windows env vars are set (effective in new terminals immediately).
517
+ WSL env vars added to ${profile}.
518
+
519
+ To activate in THIS terminal: source ${profile}
520
+ New terminals will have everything configured automatically.
521
+
522
+ squeezr status — check it's running
523
+ squeezr gain — see token savings
524
+ `)
525
+ }
526
+
303
527
  // ── CLI router ────────────────────────────────────────────────────────────────
304
528
 
305
529
  switch (command) {
306
530
  case undefined:
307
531
  case 'start':
308
- runNode('index.js')
532
+ startDaemon()
309
533
  break
310
534
 
311
535
  case 'setup':
312
536
  if (process.platform === 'win32') setupWindows()
537
+ else if (isWSL()) setupWSL()
313
538
  else setupUnix()
314
539
  break
315
540
 
package/dist/index.js CHANGED
@@ -11,9 +11,19 @@ serve({ fetch: app.fetch, port: PORT }, () => {
11
11
  console.log(`Backends: Anthropic → Haiku | OpenAI → GPT-4o-mini | Gemini → Flash-8B | Local → ${config.localCompressionModel}`);
12
12
  console.log(`Stats: http://localhost:${PORT}/squeezr/stats`);
13
13
  });
14
- process.on('SIGINT', () => {
15
- const s = stats.summary();
16
- console.log(`\n[squeezr] Session summary: ${s.requests} requests | -${s.total_saved_chars.toLocaleString()} chars (~${s.total_saved_tokens.toLocaleString()} tokens, ${s.savings_pct}% saved)`);
17
- process.exit(0);
18
- });
14
+ const isDaemon = !!process.env.SQUEEZR_DAEMON;
15
+ if (isDaemon) {
16
+ // Daemon mode: ignore SIGINT (Ctrl+C) and SIGHUP (terminal close)
17
+ // Only stop via `squeezr stop` which sends SIGTERM
18
+ process.on('SIGINT', () => { });
19
+ process.on('SIGHUP', () => { });
20
+ }
21
+ else {
22
+ // Dev mode (npm run dev): allow Ctrl+C to stop
23
+ process.on('SIGINT', () => {
24
+ const s = stats.summary();
25
+ console.log(`\n[squeezr] Session summary: ${s.requests} requests | -${s.total_saved_chars.toLocaleString()} chars (~${s.total_saved_tokens.toLocaleString()} tokens, ${s.savings_pct}% saved)`);
26
+ process.exit(0);
27
+ });
28
+ }
19
29
  process.on('SIGTERM', () => process.exit(0));
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "1.10.6";
1
+ export declare const VERSION = "1.11.1";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '1.10.6';
1
+ export const VERSION = '1.11.1';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "squeezr-ai",
3
- "version": "1.10.6",
3
+ "version": "1.11.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",