clawmux 0.1.6 → 0.1.8
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 +10 -31
- package/dist/cli.cjs +165 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,42 +12,25 @@ Smart model routing + context compression proxy for OpenClaw.
|
|
|
12
12
|
- 📊 **Cost Tracking**: Real-time savings stats at /stats endpoint
|
|
13
13
|
- 🔄 **Hot Reload**: Config changes apply without restart
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## Installation
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
### For Humans
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
npx clawmux init
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
This will:
|
|
24
|
-
1. Detect your OpenClaw config at `~/.openclaw/openclaw.json`
|
|
25
|
-
2. Create `clawmux.json` from the default template
|
|
26
|
-
3. Register 6 ClawMux providers in your OpenClaw config
|
|
27
|
-
4. Install a system service for auto-start on boot (systemd on Linux, launchd on macOS)
|
|
19
|
+
Copy and paste this prompt to your LLM agent (OpenClaw, Claude Code, etc.):
|
|
28
20
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
openclaw provider clawmux-anthropic
|
|
33
|
-
openclaw chat
|
|
21
|
+
```
|
|
22
|
+
Read the following guide and execute every step without asking for confirmation:
|
|
23
|
+
https://raw.githubusercontent.com/HyeokjaeLee/ClawMux/refs/heads/main/docs/guide/installation.md
|
|
34
24
|
```
|
|
35
25
|
|
|
36
|
-
|
|
26
|
+
Or install manually, but seriously, let an agent do it. Agents don't fat-finger configs.
|
|
37
27
|
|
|
38
|
-
|
|
39
|
-
clawmux status # check if running
|
|
40
|
-
clawmux stop # stop the service
|
|
41
|
-
clawmux start # manual foreground start
|
|
42
|
-
clawmux uninstall # remove service + OpenClaw providers
|
|
43
|
-
```
|
|
28
|
+
### For OpenClaw Agents
|
|
44
29
|
|
|
45
|
-
|
|
30
|
+
Fetch the installation guide and follow it:
|
|
46
31
|
|
|
47
32
|
```bash
|
|
48
|
-
|
|
49
|
-
cd ClawMux
|
|
50
|
-
bun install && bun run dev
|
|
33
|
+
curl -s https://raw.githubusercontent.com/HyeokjaeLee/ClawMux/refs/heads/main/docs/guide/installation.md
|
|
51
34
|
```
|
|
52
35
|
|
|
53
36
|
## Configuration
|
|
@@ -173,7 +156,3 @@ clawmux uninstall
|
|
|
173
156
|
```
|
|
174
157
|
|
|
175
158
|
Stops the system service, removes the service file, and removes all `clawmux-*` providers from your OpenClaw config. A backup is created before any changes.
|
|
176
|
-
|
|
177
|
-
## License
|
|
178
|
-
|
|
179
|
-
MIT
|
package/dist/cli.cjs
CHANGED
|
@@ -80,7 +80,7 @@ async function writeWebResponse(res, response) {
|
|
|
80
80
|
|
|
81
81
|
// src/cli.ts
|
|
82
82
|
var import_promises = require("node:fs/promises");
|
|
83
|
-
var
|
|
83
|
+
var import_node_path2 = require("node:path");
|
|
84
84
|
var import_node_child_process = require("node:child_process");
|
|
85
85
|
var import_node_os = require("node:os");
|
|
86
86
|
|
|
@@ -247,6 +247,75 @@ function createNodeServer(config) {
|
|
|
247
247
|
};
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
+
// src/utils/logger.ts
|
|
251
|
+
var import_node_fs = require("node:fs");
|
|
252
|
+
var import_node_path = require("node:path");
|
|
253
|
+
var LOG_DIR = import_node_path.join(process.env.HOME ?? "/root", ".openclaw", "clawmux");
|
|
254
|
+
var MAX_DAYS = 7;
|
|
255
|
+
var fileStream = null;
|
|
256
|
+
var currentDate = "";
|
|
257
|
+
function todayString() {
|
|
258
|
+
const d = new Date;
|
|
259
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
260
|
+
}
|
|
261
|
+
function logPath(date) {
|
|
262
|
+
return import_node_path.join(LOG_DIR, `${date}.log`);
|
|
263
|
+
}
|
|
264
|
+
function rotateIfNeeded() {
|
|
265
|
+
const today = todayString();
|
|
266
|
+
if (today === currentDate && fileStream)
|
|
267
|
+
return;
|
|
268
|
+
if (fileStream) {
|
|
269
|
+
fileStream.end();
|
|
270
|
+
}
|
|
271
|
+
currentDate = today;
|
|
272
|
+
fileStream = import_node_fs.createWriteStream(logPath(today), { flags: "a" });
|
|
273
|
+
purgeOldLogs();
|
|
274
|
+
}
|
|
275
|
+
function purgeOldLogs() {
|
|
276
|
+
try {
|
|
277
|
+
const files = import_node_fs.readdirSync(LOG_DIR).filter((f) => f.endsWith(".log")).sort();
|
|
278
|
+
while (files.length > MAX_DAYS) {
|
|
279
|
+
const oldest = files.shift();
|
|
280
|
+
import_node_fs.unlinkSync(import_node_path.join(LOG_DIR, oldest));
|
|
281
|
+
}
|
|
282
|
+
} catch (_) {}
|
|
283
|
+
}
|
|
284
|
+
function formatTimestamp() {
|
|
285
|
+
return new Date().toISOString();
|
|
286
|
+
}
|
|
287
|
+
function writeLine(level, args) {
|
|
288
|
+
rotateIfNeeded();
|
|
289
|
+
const message = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
|
|
290
|
+
const line = `${formatTimestamp()} [${level}] ${message}
|
|
291
|
+
`;
|
|
292
|
+
if (fileStream) {
|
|
293
|
+
fileStream.write(line);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
function initLogger() {
|
|
297
|
+
import_node_fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
298
|
+
rotateIfNeeded();
|
|
299
|
+
const origLog = console.log.bind(console);
|
|
300
|
+
const origError = console.error.bind(console);
|
|
301
|
+
const origWarn = console.warn.bind(console);
|
|
302
|
+
console.log = (...args) => {
|
|
303
|
+
origLog(...args);
|
|
304
|
+
writeLine("INFO", args);
|
|
305
|
+
};
|
|
306
|
+
console.error = (...args) => {
|
|
307
|
+
origError(...args);
|
|
308
|
+
writeLine("ERROR", args);
|
|
309
|
+
};
|
|
310
|
+
console.warn = (...args) => {
|
|
311
|
+
origWarn(...args);
|
|
312
|
+
writeLine("WARN", args);
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
function getLogDir() {
|
|
316
|
+
return LOG_DIR;
|
|
317
|
+
}
|
|
318
|
+
|
|
250
319
|
// src/cli.ts
|
|
251
320
|
var VERSION2 = process.env.npm_package_version ?? "0.1.0";
|
|
252
321
|
var SERVICE_NAME = "clawmux";
|
|
@@ -257,6 +326,7 @@ Commands:
|
|
|
257
326
|
start Start the proxy server (foreground)
|
|
258
327
|
stop Stop the system service
|
|
259
328
|
status Check if ClawMux service is running
|
|
329
|
+
update Update to the latest version and restart service
|
|
260
330
|
uninstall Remove system service and OpenClaw providers
|
|
261
331
|
version Print version
|
|
262
332
|
help Show this help message
|
|
@@ -294,10 +364,10 @@ function resolveClawmuxBin() {
|
|
|
294
364
|
function getHomeDir() {
|
|
295
365
|
return process.env.HOME ?? "/root";
|
|
296
366
|
}
|
|
297
|
-
var SYSTEMD_DIR =
|
|
298
|
-
var SYSTEMD_PATH =
|
|
299
|
-
var LAUNCHD_DIR =
|
|
300
|
-
var LAUNCHD_PATH =
|
|
367
|
+
var SYSTEMD_DIR = import_node_path2.join(getHomeDir(), ".config", "systemd", "user");
|
|
368
|
+
var SYSTEMD_PATH = import_node_path2.join(SYSTEMD_DIR, `${SERVICE_NAME}.service`);
|
|
369
|
+
var LAUNCHD_DIR = import_node_path2.join(getHomeDir(), "Library", "LaunchAgents");
|
|
370
|
+
var LAUNCHD_PATH = import_node_path2.join(LAUNCHD_DIR, `com.${SERVICE_NAME}.plist`);
|
|
301
371
|
function buildSystemdUnit(bin, port, workDir) {
|
|
302
372
|
return `[Unit]
|
|
303
373
|
Description=ClawMux - Smart model routing proxy
|
|
@@ -316,7 +386,7 @@ WantedBy=default.target
|
|
|
316
386
|
`;
|
|
317
387
|
}
|
|
318
388
|
function buildLaunchdPlist(bin, port, workDir) {
|
|
319
|
-
const logDir =
|
|
389
|
+
const logDir = import_node_path2.join(getHomeDir(), ".openclaw", "clawmux");
|
|
320
390
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
321
391
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
322
392
|
<plist version="1.0">
|
|
@@ -371,7 +441,7 @@ async function installService(port, workDir) {
|
|
|
371
441
|
}
|
|
372
442
|
} else if (os === "darwin") {
|
|
373
443
|
await import_promises.mkdir(LAUNCHD_DIR, { recursive: true });
|
|
374
|
-
const logDir =
|
|
444
|
+
const logDir = import_node_path2.join(getHomeDir(), ".openclaw", "clawmux");
|
|
375
445
|
await import_promises.mkdir(logDir, { recursive: true });
|
|
376
446
|
await import_promises.writeFile(LAUNCHD_PATH, buildLaunchdPlist(bin, port, workDir));
|
|
377
447
|
try {
|
|
@@ -450,11 +520,86 @@ async function removeService() {
|
|
|
450
520
|
}
|
|
451
521
|
}
|
|
452
522
|
}
|
|
523
|
+
async function checkForUpdate() {
|
|
524
|
+
try {
|
|
525
|
+
const res = await fetch("https://registry.npmjs.org/clawmux/latest", {
|
|
526
|
+
signal: AbortSignal.timeout(3000)
|
|
527
|
+
});
|
|
528
|
+
if (!res.ok)
|
|
529
|
+
return;
|
|
530
|
+
const data = await res.json();
|
|
531
|
+
const latest = data.version;
|
|
532
|
+
if (!latest || latest === VERSION2)
|
|
533
|
+
return;
|
|
534
|
+
const [curMajor, curMinor, curPatch] = VERSION2.split(".").map(Number);
|
|
535
|
+
const [latMajor, latMinor, latPatch] = latest.split(".").map(Number);
|
|
536
|
+
const isNewer = latMajor > curMajor || latMajor === curMajor && latMinor > curMinor || latMajor === curMajor && latMinor === curMinor && latPatch > curPatch;
|
|
537
|
+
if (isNewer) {
|
|
538
|
+
console.log(`[clawmux] Update available: ${VERSION2} → ${latest}`);
|
|
539
|
+
console.log(`[clawmux] Run 'clawmux update' to upgrade`);
|
|
540
|
+
}
|
|
541
|
+
} catch (_) {}
|
|
542
|
+
}
|
|
543
|
+
function detectPackageManager() {
|
|
544
|
+
try {
|
|
545
|
+
import_node_child_process.execSync("which bun", { stdio: "pipe" });
|
|
546
|
+
return "bunx";
|
|
547
|
+
} catch {
|
|
548
|
+
return "npx";
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
async function update() {
|
|
552
|
+
const pm = detectPackageManager();
|
|
553
|
+
console.log(`[clawmux] Checking for updates...`);
|
|
554
|
+
try {
|
|
555
|
+
const res = await fetch("https://registry.npmjs.org/clawmux/latest", {
|
|
556
|
+
signal: AbortSignal.timeout(5000)
|
|
557
|
+
});
|
|
558
|
+
if (!res.ok) {
|
|
559
|
+
console.error("[error] Failed to check npm registry");
|
|
560
|
+
process.exit(1);
|
|
561
|
+
}
|
|
562
|
+
const data = await res.json();
|
|
563
|
+
const latest = data.version;
|
|
564
|
+
if (!latest) {
|
|
565
|
+
console.error("[error] Could not determine latest version");
|
|
566
|
+
process.exit(1);
|
|
567
|
+
}
|
|
568
|
+
if (latest === VERSION2) {
|
|
569
|
+
console.log(`[clawmux] Already on latest version (${VERSION2})`);
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
console.log(`[clawmux] Updating ${VERSION2} → ${latest}...`);
|
|
573
|
+
if (pm === "bunx") {
|
|
574
|
+
import_node_child_process.execSync("bun pm cache rm clawmux 2>/dev/null; bunx clawmux@latest version", { stdio: "inherit" });
|
|
575
|
+
} else {
|
|
576
|
+
import_node_child_process.execSync("npx clawmux@latest version", { stdio: "inherit" });
|
|
577
|
+
}
|
|
578
|
+
const os = import_node_os.platform();
|
|
579
|
+
if (os === "linux") {
|
|
580
|
+
try {
|
|
581
|
+
import_node_child_process.execSync(`systemctl --user restart ${SERVICE_NAME}`, { stdio: "pipe" });
|
|
582
|
+
console.log("[clawmux] Service restarted");
|
|
583
|
+
} catch (_) {}
|
|
584
|
+
} else if (os === "darwin") {
|
|
585
|
+
try {
|
|
586
|
+
import_node_child_process.execSync(`launchctl unload ${LAUNCHD_PATH}`, { stdio: "pipe" });
|
|
587
|
+
import_node_child_process.execSync(`launchctl load -w ${LAUNCHD_PATH}`, { stdio: "pipe" });
|
|
588
|
+
console.log("[clawmux] Service restarted");
|
|
589
|
+
} catch (_) {}
|
|
590
|
+
}
|
|
591
|
+
console.log(`[clawmux] Updated to ${latest}`);
|
|
592
|
+
} catch (err) {
|
|
593
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
594
|
+
console.error(`[error] Update failed: ${msg}`);
|
|
595
|
+
process.exit(1);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
453
598
|
async function init() {
|
|
454
599
|
const args = process.argv.slice(2);
|
|
455
600
|
const noService = args.includes("--no-service");
|
|
456
601
|
const homeDir = getHomeDir();
|
|
457
|
-
const openclawConfigPath = process.env.OPENCLAW_CONFIG_PATH ??
|
|
602
|
+
const openclawConfigPath = process.env.OPENCLAW_CONFIG_PATH ?? import_node_path2.join(homeDir, ".openclaw", "openclaw.json");
|
|
458
603
|
if (!await fileExistsLocal(openclawConfigPath)) {
|
|
459
604
|
console.error(`[error] OpenClaw config not found at ${openclawConfigPath}`);
|
|
460
605
|
console.error("Set OPENCLAW_CONFIG_PATH or ensure ~/.openclaw/openclaw.json exists");
|
|
@@ -464,8 +609,8 @@ async function init() {
|
|
|
464
609
|
const backupPath = `${openclawConfigPath}.bak.${Date.now()}`;
|
|
465
610
|
await import_promises.copyFile(openclawConfigPath, backupPath);
|
|
466
611
|
console.log(`[info] Backup created: ${backupPath}`);
|
|
467
|
-
const clawmuxJsonPath =
|
|
468
|
-
const examplePath =
|
|
612
|
+
const clawmuxJsonPath = import_node_path2.join(process.cwd(), "clawmux.json");
|
|
613
|
+
const examplePath = import_node_path2.join(process.cwd(), "clawmux.example.json");
|
|
469
614
|
if (!await fileExistsLocal(clawmuxJsonPath)) {
|
|
470
615
|
if (await fileExistsLocal(examplePath)) {
|
|
471
616
|
await import_promises.copyFile(examplePath, clawmuxJsonPath);
|
|
@@ -531,14 +676,17 @@ function start() {
|
|
|
531
676
|
if (portIdx !== -1 && args[portIdx + 1]) {
|
|
532
677
|
port = parseInt(args[portIdx + 1], 10);
|
|
533
678
|
}
|
|
679
|
+
initLogger();
|
|
534
680
|
const server = createServer({ port, host: "127.0.0.1" });
|
|
535
681
|
server.start();
|
|
536
682
|
console.log(`[clawmux] Proxy server running on http://127.0.0.1:${port}`);
|
|
683
|
+
console.log(`[clawmux] Logs: ${getLogDir()}`);
|
|
684
|
+
checkForUpdate();
|
|
537
685
|
}
|
|
538
686
|
async function uninstall() {
|
|
539
687
|
await removeService();
|
|
540
688
|
const homeDir = getHomeDir();
|
|
541
|
-
const openclawConfigPath = process.env.OPENCLAW_CONFIG_PATH ??
|
|
689
|
+
const openclawConfigPath = process.env.OPENCLAW_CONFIG_PATH ?? import_node_path2.join(homeDir, ".openclaw", "openclaw.json");
|
|
542
690
|
if (await fileExistsLocal(openclawConfigPath)) {
|
|
543
691
|
const backupPath = `${openclawConfigPath}.bak.${Date.now()}`;
|
|
544
692
|
await import_promises.copyFile(openclawConfigPath, backupPath);
|
|
@@ -578,6 +726,12 @@ switch (command) {
|
|
|
578
726
|
case "status":
|
|
579
727
|
getStatus();
|
|
580
728
|
break;
|
|
729
|
+
case "update":
|
|
730
|
+
update().catch((err) => {
|
|
731
|
+
console.error(`[error] ${err.message}`);
|
|
732
|
+
process.exit(1);
|
|
733
|
+
});
|
|
734
|
+
break;
|
|
581
735
|
case "uninstall":
|
|
582
736
|
uninstall().catch((err) => {
|
|
583
737
|
console.error(`[error] ${err.message}`);
|