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 +22 -5
- package/bin/squeezr.js +235 -10
- package/dist/index.js +15 -5
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
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
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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.
|
|
1
|
+
export declare const VERSION = "1.11.1";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '1.
|
|
1
|
+
export const VERSION = '1.11.1';
|
package/package.json
CHANGED