@wong2kim/wmux 1.1.2 → 2.0.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
@@ -1,112 +1,158 @@
1
- # wmux
1
+ # wmux — AI Agent Terminal for Windows
2
2
 
3
- **AI Agent Terminal for Windows**
3
+ > **Run Claude Code + Codex + Gemini CLI side by side.**
4
+ > Split terminals, browser automation, MCP integration — the only proper way to use AI agents on Windows.
4
5
 
5
- Run Claude Code, Codex, Gemini CLI side by side — with built-in browser, smart notifications, and MCP integration.
6
+ [![Windows 10/11](https://img.shields.io/badge/Windows-10%2F11-0078D6?logo=windows&logoColor=white)](https://github.com/openwong2kim/wmux/releases/latest)
7
+ [![npm](https://img.shields.io/npm/v/@wong2kim/wmux?color=CB3837&logo=npm)](https://www.npmjs.com/package/@wong2kim/wmux)
8
+ [![Electron 41](https://img.shields.io/badge/Electron-41-47848F?logo=electron&logoColor=white)](https://www.electronjs.org/)
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
10
+ [![GitHub stars](https://img.shields.io/github/stars/openwong2kim/wmux?style=social)](https://github.com/openwong2kim/wmux)
6
11
 
7
- Inspired by [cmux](https://github.com/manaflow-ai/cmux) (macOS), wmux brings the same philosophy to Windows: **a primitive, not a solution.** Composable building blocks for multi-agent workflows.
12
+ ---
13
+
14
+ ## Still using one terminal for your AI coding agents on Windows?
15
+
16
+ macOS has [cmux](https://github.com/manaflow-ai/cmux) — a tmux-based terminal multiplexer for AI agents.
17
+
18
+ **Windows has no tmux.** Without WSL, there was no way.
8
19
 
9
- ![Windows](https://img.shields.io/badge/Windows-10%2F11-0078D6?logo=windows)
10
- ![Electron](https://img.shields.io/badge/Electron-41-47848F?logo=electron)
11
- ![License](https://img.shields.io/badge/License-MIT-green)
20
+ wmux fixes this. Native Windows terminal multiplexer + browser automation + MCP server. Your AI agent reads the terminal, controls the browser, and works autonomously.
21
+
22
+ ```
23
+ Claude Code writes the backend on the left
24
+ Codex builds the frontend on the right
25
+ Gemini CLI runs tests at the bottom
26
+ — all on one screen, simultaneously.
27
+ ```
12
28
 
13
29
  ---
14
30
 
15
- ## Install
31
+ ## Install in 30 seconds
32
+
33
+ **Installer:**
16
34
 
17
- **Download:** [wmux-1.1.2 Setup.exe](https://github.com/openwong2kim/wmux/releases/latest)
35
+ [Download wmux Setup.exe](https://github.com/openwong2kim/wmux/releases/latest)
18
36
 
19
- Or build from source:
37
+ **One-liner (PowerShell):**
20
38
  ```powershell
21
39
  irm https://raw.githubusercontent.com/openwong2kim/wmux/main/install.ps1 | iex
22
40
  ```
23
41
 
42
+ **npm (CLI + MCP server):**
43
+ ```bash
44
+ npm install -g @wong2kim/wmux
45
+ ```
46
+
24
47
  ---
25
48
 
26
49
  ## Why wmux?
27
50
 
28
- | Problem | wmux |
29
- |---------|------|
30
- | Windows has no cmux | Native Windows terminal multiplexer for AI agents |
31
- | Agents can't see the browser | Built-in browser with MCP — Claude clicks, fills, evaluates JS |
32
- | "Is it done yet?" | Smart activity-based notifications + taskbar flash |
33
- | Can't compare agents | Multiview — Ctrl+click workspaces to view side by side |
34
- | Hard to describe UI elements to LLM | Inspector — click any element, LLM-friendly context copied |
51
+ ### 1. Your AI agent controls the browser — for real
52
+
53
+ Tell Claude Code "search Google for this" and it actually does it.
54
+
55
+ wmux's built-in browser connects via Chrome DevTools Protocol. Click, type, screenshot, execute JS — all done by the AI directly. Works perfectly with React controlled inputs and CJK text.
56
+
57
+ ```
58
+ You: "Search for wmux on Google"
59
+ Claude: browser_open → browser_snapshot → browser_fill(ref=13, "wmux") → browser_press_key("Enter")
60
+ → Actually searches Google. Done.
61
+ ```
62
+
63
+ ### 2. Multiple terminals in one window
64
+
65
+ `Ctrl+D` to split, `Ctrl+N` for new workspace. Place multiple terminals and browsers in each workspace. `Ctrl+click` for multiview — see multiple workspaces at once.
66
+
67
+ ConPTY-based native Windows terminal. xterm.js + WebGL hardware-accelerated rendering. 999K lines of scrollback. Terminal content persists even after restart.
68
+
69
+ ### 3. No more asking "is it done yet?"
70
+
71
+ wmux tells you when your AI agent finishes.
72
+
73
+ - Task complete → desktop notification + taskbar flash
74
+ - Abnormal exit → immediate warning
75
+ - `git push --force`, `rm -rf`, `DROP TABLE` → dangerous action detection
76
+
77
+ Not pattern matching — output throughput-based detection. Works with any agent.
78
+
79
+ ### 4. Automatic Claude Code integration
80
+
81
+ Launch wmux and the MCP server registers automatically. Claude Code just works:
82
+
83
+ | What Claude can do | MCP Tool |
84
+ |---|---|
85
+ | Open browser | `browser_open` |
86
+ | Navigate to URL | `browser_navigate` |
87
+ | Take screenshot | `browser_screenshot` |
88
+ | Read page structure | `browser_snapshot` |
89
+ | Click element | `browser_click` |
90
+ | Fill form | `browser_fill` / `browser_type` |
91
+ | Execute JS | `browser_evaluate` |
92
+ | Press key | `browser_press_key` |
93
+ | Read terminal | `terminal_read` |
94
+ | Send command | `terminal_send` |
95
+ | Manage workspaces | `workspace_list` / `surface_list` / `pane_list` |
96
+
97
+ **Multi-agent:** Every browser tool accepts `surfaceId` — each Claude Code session controls its own browser independently.
98
+
99
+ ### 5. Security that actually matters
100
+
101
+ - Token authentication on all IPC pipes
102
+ - SSRF protection — blocks private IPs, `file://`, `javascript:` schemes
103
+ - PTY input sanitization — prevents command injection
104
+ - Randomized CDP port — no fixed debug port
105
+ - Memory pressure watchdog — reaps dead sessions at 750MB, blocks new ones at 1GB
106
+ - Electron Fuses — RunAsNode disabled, cookie encryption enabled
35
107
 
36
108
  ---
37
109
 
38
- ## Features
110
+ ## All Features
39
111
 
40
112
  ### Terminal
41
- - **xterm.js + WebGL** GPU-accelerated rendering
42
- - **ConPTY** native Windows pseudo-terminal
43
- - **Split panes** — `Ctrl+D` horizontal, `Ctrl+Shift+D` vertical
44
- - **Tabs** — multiple surfaces per pane
45
- - **Vi copy mode** — `Ctrl+Shift+X`
46
- - **Search** — `Ctrl+F`
47
- - **Unlimited scrollback** 999,999 lines default
48
- - **Scrollback persistence** — terminal content saved to disk, restored on restart
113
+ - xterm.js + WebGL GPU-accelerated rendering
114
+ - ConPTY native Windows pseudo-terminal
115
+ - Split panes — `Ctrl+D` horizontal, `Ctrl+Shift+D` vertical
116
+ - Tabs — multiple surfaces per pane
117
+ - Vi copy mode — `Ctrl+Shift+X`
118
+ - Search — `Ctrl+F`
119
+ - 999K line scrollback with disk persistence
49
120
 
50
121
  ### Workspaces
51
122
  - Sidebar with drag-and-drop reordering
52
- - `Ctrl+1` ~ `Ctrl+9` quick switch
53
- - **Multiview** — `Ctrl+click` workspaces to split-view them simultaneously
54
- - `Ctrl+Shift+G` to exit multiview
55
- - **Session persistence** workspace layout, tabs, cwd, and terminal scrollback all restored on restart
123
+ - `Ctrl+1~9` quick switch
124
+ - Multiview — `Ctrl+click` to view multiple workspaces side by side
125
+ - Full session persistence — layout, tabs, cwd, scrollback all restored
126
+ - One-click reset in Settings
56
127
 
57
- ### Browser
128
+ ### Browser + CDP Automation
58
129
  - Built-in browser panel — `Ctrl+Shift+L`
59
130
  - Navigation bar, DevTools, back/forward
60
- - **Element Inspector**magnifying glass button to inspect elements
61
- - Hover to highlight, click to copy LLM-friendly context:
62
- ```
63
- [Inspector] Google (https://www.google.com/)
64
- selector: input.gLFyf
65
- <input type="text" name="q" aria-label="Search">
66
- text: ""
67
- parent: div.RNNXgb > siblings: button"Google Search", button"I'm Feeling Lucky"
68
- ```
69
- - Paste directly into Claude — it understands the element immediately
131
+ - Element Inspector — hover to highlight, click to copy LLM-friendly context
132
+ - Full CDP automation: click, fill, type, screenshot, JS eval, key press
70
133
 
71
134
  ### Notifications
72
- - **Activity-based detection** — monitors output throughput, no fragile pattern matching
73
- - **Taskbar flash** orange flash when notifications arrive while unfocused
74
- - **Windows toast** — native OS notification with click-to-focus
75
- - **Process exit alerts** notifies on non-zero exit codes
76
- - **Notification panel** `Ctrl+I`, read/unread tracking, per-workspace filtering
77
- - **Sound** — Web Audio synthesized tones per notification type
78
-
79
- ### MCP Server (Claude Code Integration)
80
- wmux automatically registers its MCP server when launched. Claude Code can:
81
-
82
- | Tool | What it does |
83
- |------|-------------|
84
- | `browser_open` | Open a new browser panel |
85
- | `browser_navigate` | Go to URL |
86
- | `browser_snapshot` | Get full page HTML |
87
- | `browser_click` | Click element by CSS selector |
88
- | `browser_fill` | Fill input field |
89
- | `browser_eval` | Execute JavaScript |
90
- | `terminal_read` | Read terminal screen |
91
- | `terminal_send` | Send text to terminal |
92
- | `terminal_send_key` | Send key (enter, ctrl+c, etc.) |
93
- | `workspace_list` | List all workspaces |
94
- | `surface_list` | List surfaces |
95
- | `pane_list` | List panes |
96
-
97
- **Multi-agent:** All browser tools accept `surfaceId` — each Claude Code session controls its own browser independently.
98
-
99
- ### Agent Status Detection
100
- Gate-based detection for AI coding agents:
101
- - Claude Code, Cursor, Aider, Codex CLI, Gemini CLI, OpenCode, GitHub Copilot CLI
102
- - Detects agent startup → activates monitoring
103
- - Critical action warnings (git push --force, rm -rf, DROP TABLE, etc.)
135
+ - Output throughput-based activity detection
136
+ - Taskbar flash + Windows toast notifications
137
+ - Process exit alerts
138
+ - Notification panel`Ctrl+I`
139
+ - Web Audio sound effects
140
+
141
+ ### Agent Detection
142
+ Claude Code, Cursor, Aider, Codex CLI, Gemini CLI, OpenCode, GitHub Copilot CLI
143
+ - Detects agent start activates monitoring
144
+ - Critical action warnings
145
+
146
+ ### Daemon Process
147
+ - Background session management (survives app restart)
148
+ - Scrollback buffer dump and auto-recovery
149
+ - Dead session TTL reaping (24h default)
104
150
 
105
151
  ### Themes
106
152
  Catppuccin, Tokyo Night, Dracula, Nord, Gruvbox, Solarized, One Dark, and more.
107
153
 
108
154
  ### i18n
109
- English, 한국어, 日本語, 中文
155
+ English, Korean, Japanese, Chinese
110
156
 
111
157
  ---
112
158
 
@@ -120,7 +166,7 @@ English, 한국어, 日本語, 中文
120
166
  | `Ctrl+W` | Close tab |
121
167
  | `Ctrl+N` | New workspace |
122
168
  | `Ctrl+1~9` | Switch workspace |
123
- | `Ctrl+click` | Add workspace to multiview |
169
+ | `Ctrl+click` | Add to multiview |
124
170
  | `Ctrl+Shift+G` | Exit multiview |
125
171
  | `Ctrl+Shift+L` | Open browser |
126
172
  | `Ctrl+B` | Toggle sidebar |
@@ -129,8 +175,6 @@ English, 한국어, 日本語, 中文
129
175
  | `Ctrl+,` | Settings |
130
176
  | `Ctrl+F` | Search terminal |
131
177
  | `Ctrl+Shift+X` | Vi copy mode |
132
- | `Ctrl+Shift+H` | Flash pane |
133
- | `Alt+Ctrl+Arrow` | Focus adjacent pane |
134
178
  | `F12` | Browser DevTools |
135
179
 
136
180
  ---
@@ -159,7 +203,7 @@ npm start # Dev mode
159
203
  npm run make # Build installer
160
204
  ```
161
205
 
162
- ### Requirements (development only)
206
+ ### Requirements (dev only)
163
207
  - Node.js 18+
164
208
  - Python 3.x (for node-gyp)
165
209
  - Visual Studio Build Tools with C++ workload
@@ -176,37 +220,44 @@ Electron Main Process
176
220
  ├── PTYBridge (data forwarding + ActivityMonitor)
177
221
  ├── AgentDetector (gate-based agent status)
178
222
  ├── SessionManager (atomic save with .bak recovery)
179
- ├── ScrollbackPersistence (dump/load terminal buffers)
180
- ├── PipeServer (Named Pipe JSON-RPC)
223
+ ├── ScrollbackPersistence (terminal buffer dump/load)
224
+ ├── PipeServer (Named Pipe JSON-RPC + token auth)
181
225
  ├── McpRegistrar (auto-registers MCP in ~/.claude.json)
182
- ├── DaemonClient (optional daemon mode connector)
226
+ ├── WebviewCdpManager (CDP proxy to <webview>)
227
+ ├── DaemonClient (daemon mode connector)
183
228
  └── ToastManager (OS notifications + taskbar flash)
184
229
 
185
230
  Renderer Process (React 19 + Zustand)
186
231
  ├── PaneContainer (recursive split layout)
187
232
  ├── Terminal (xterm.js + WebGL + scrollback restore)
188
- ├── BrowserPanel (webview + Inspector)
233
+ ├── BrowserPanel (webview + Inspector + CDP)
189
234
  ├── NotificationPanel
235
+ ├── SettingsPanel (workspace reset)
190
236
  └── Multiview grid
191
237
 
192
- Daemon Process (optional, standalone)
238
+ Daemon Process (standalone)
193
239
  ├── DaemonSessionManager (ConPTY lifecycle)
194
240
  ├── RingBuffer (circular scrollback buffer)
195
241
  ├── StateWriter (session suspend/resume)
196
- └── DaemonPipeServer (Named Pipe RPC)
242
+ ├── ProcessMonitor (external process watchdog)
243
+ ├── Watchdog (memory pressure escalation)
244
+ └── DaemonPipeServer (Named Pipe RPC + token auth)
197
245
 
198
246
  MCP Server (stdio)
199
- └── Bridges Claude Code ↔ wmux via Named Pipe RPC
247
+ ├── PlaywrightEngine (CDP connection, fast-fail)
248
+ ├── CDP RPC fallback (screenshot, evaluate, type, click)
249
+ └── Claude Code <-> wmux Named Pipe RPC bridge
200
250
  ```
201
251
 
202
252
  ---
203
253
 
204
254
  ## Acknowledgments
205
255
 
206
- - [cmux](https://github.com/manaflow-ai/cmux) — The macOS AI agent terminal that inspired wmux. Same philosophy: primitives over prescriptive workflows.
256
+ - [cmux](https://github.com/manaflow-ai/cmux) — The macOS AI agent terminal that inspired wmux
207
257
  - [xterm.js](https://xtermjs.org/) — Terminal rendering
208
258
  - [node-pty](https://github.com/microsoft/node-pty) — Pseudo-terminal
209
259
  - [Electron](https://www.electronjs.org/) — Desktop framework
260
+ - [Playwright](https://playwright.dev/) — Browser automation engine
210
261
 
211
262
  ---
212
263
 
@@ -1,8 +1,19 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.handleSystem = handleSystem;
4
+ const fs_1 = require("fs");
5
+ const path_1 = require("path");
4
6
  const client_1 = require("../client");
5
7
  const utils_1 = require("../utils");
8
+ function getFallbackVersion() {
9
+ try {
10
+ const pkg = JSON.parse((0, fs_1.readFileSync)((0, path_1.join)(__dirname, '..', '..', 'package.json'), 'utf-8'));
11
+ return pkg.version ?? '0.0.0';
12
+ }
13
+ catch {
14
+ return '0.0.0';
15
+ }
16
+ }
6
17
  async function handleSystem(cmd, args, jsonMode) {
7
18
  let response;
8
19
  switch (cmd) {
@@ -18,7 +29,7 @@ async function handleSystem(cmd, args, jsonMode) {
18
29
  }
19
30
  const info = response.result;
20
31
  console.log(`app: ${info?.app ?? 'wmux'}`);
21
- console.log(`version: ${info?.version ?? '1.0.0'}`);
32
+ console.log(`version: ${info?.version ?? getFallbackVersion()}`);
22
33
  console.log(`platform: ${info?.platform ?? process.platform}`);
23
34
  }
24
35
  break;
@@ -34,6 +34,12 @@ exports.ALL_RPC_METHODS = [
34
34
  'browser.type.humanlike',
35
35
  'browser.cdp.target',
36
36
  'browser.cdp.info',
37
+ 'browser.cdp.send',
38
+ 'browser.screenshot',
39
+ 'browser.evaluate',
40
+ 'browser.type.cdp',
41
+ 'browser.click.cdp',
42
+ 'browser.press.cdp',
37
43
  'daemon.createSession',
38
44
  'daemon.destroySession',
39
45
  'daemon.attachSession',
@@ -7,19 +7,22 @@ exports.validateMessage = validateMessage;
7
7
  exports.createSurface = createSurface;
8
8
  exports.createLeafPane = createLeafPane;
9
9
  exports.createWorkspace = createWorkspace;
10
+ exports.validateNavigationUrl = validateNavigationUrl;
10
11
  // === Utility: generate unique IDs ===
11
12
  function generateId(prefix) {
12
13
  return `${prefix}-${crypto.randomUUID()}`;
13
14
  }
14
15
  // === Security: sanitize text before PTY write ===
15
16
  /**
16
- * Strips control characters (\r, \n, \x00-\x1f except \t) from text
17
- * that will be written to a PTY, preventing embedded command injection.
17
+ * Strips dangerous control characters from text before writing to a PTY.
18
+ * Removes: NULL byte (\x00) and C1 control characters (\x80-\x9f).
19
+ * Preserves: CR (\r), LF (\n), Tab (\t), ESC sequences (\x1b[...),
20
+ * and other standard terminal control characters needed for normal operation.
18
21
  */
19
22
  function sanitizePtyText(text) {
20
- // Remove all control chars except tab (\x09)
23
+ // Remove NULL byte and C1 control characters (U+0080–U+009F)
21
24
  // eslint-disable-next-line no-control-regex
22
- return text.replace(/[\x00-\x08\x0a-\x1f\x7f\u0080-\u009f]/g, '');
25
+ return text.replace(/[\x00\u0080-\u009f]/g, '');
23
26
  }
24
27
  /**
25
28
  * Validates and clamps a user-supplied name string.
@@ -77,3 +80,104 @@ function createWorkspace(name) {
77
80
  activePaneId: rootPane.id,
78
81
  };
79
82
  }
83
+ // === Security: URL validation for SSRF prevention ===
84
+ /**
85
+ * Validates a URL for safe navigation. Blocks dangerous schemes and private
86
+ * network addresses to prevent SSRF attacks from AI agent-driven browsing.
87
+ *
88
+ * Allows localhost/127.0.0.1/[::1] for local development servers.
89
+ *
90
+ * NOTE (v1 limitation): This is string-based validation only. DNS-resolved IPs
91
+ * are not checked, so DNS rebinding attacks are not mitigated. A future version
92
+ * should resolve hostnames and re-validate the resolved IP.
93
+ */
94
+ function validateNavigationUrl(url) {
95
+ let parsed;
96
+ try {
97
+ parsed = new URL(url);
98
+ }
99
+ catch {
100
+ return { valid: false, reason: 'Invalid URL' };
101
+ }
102
+ // Only allow http and https schemes
103
+ const scheme = parsed.protocol.toLowerCase();
104
+ if (scheme !== 'http:' && scheme !== 'https:') {
105
+ return { valid: false, reason: `Blocked URL scheme: ${scheme}` };
106
+ }
107
+ // Extract hostname (strip brackets from IPv6)
108
+ const hostname = parsed.hostname.toLowerCase();
109
+ // Allow localhost and IPv4/IPv6 loopback
110
+ if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1') {
111
+ return { valid: true };
112
+ }
113
+ // Block IPv6 private/link-local ranges
114
+ if (hostname.startsWith('[') || hostname.includes(':')) {
115
+ // Hostname is an IPv6 address (URL parser strips brackets in .hostname)
116
+ const addr = hostname;
117
+ // Block fc00::/7 (unique local) — starts with fc or fd
118
+ if (addr.startsWith('fc') || addr.startsWith('fd')) {
119
+ return { valid: false, reason: 'Blocked private IPv6 address (fc00::/7)' };
120
+ }
121
+ // Block fe80::/10 (link-local) — starts with fe8, fe9, fea, feb
122
+ if (/^fe[89ab]/.test(addr)) {
123
+ return { valid: false, reason: 'Blocked link-local IPv6 address (fe80::/10)' };
124
+ }
125
+ // ::1 already allowed above; block any other loopback representation
126
+ // Normalize: collapse :: and check
127
+ if (addr === '0:0:0:0:0:0:0:1' || addr === '0000:0000:0000:0000:0000:0000:0000:0001') {
128
+ return { valid: true };
129
+ }
130
+ // Block null IPv6 address (:: or 0:0:0:0:0:0:0:0) — equivalent to 0.0.0.0
131
+ if (addr === '::' || addr === '0:0:0:0:0:0:0:0' || addr === '0000:0000:0000:0000:0000:0000:0000:0000') {
132
+ return { valid: false, reason: 'Blocked null IPv6 address (equivalent to 0.0.0.0)' };
133
+ }
134
+ // Block IPv4-mapped IPv6 (::ffff:x.x.x.x) and IPv4-compatible IPv6 (::x.x.x.x)
135
+ // These resolve to their embedded IPv4 address, bypassing IPv4 private IP checks.
136
+ const v4MappedMatch = /^::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/.exec(addr);
137
+ const v4CompatMatch = !v4MappedMatch ? /^::(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/.exec(addr) : null;
138
+ const embeddedV4 = v4MappedMatch?.[1] ?? v4CompatMatch?.[1];
139
+ if (embeddedV4) {
140
+ // Recursively validate the embedded IPv4 through the same checks
141
+ const embeddedResult = validateNavigationUrl(`http://${embeddedV4}/`);
142
+ if (!embeddedResult.valid) {
143
+ return { valid: false, reason: `Blocked IPv4-mapped/compatible IPv6: embedded ${embeddedV4} — ${embeddedResult.reason}` };
144
+ }
145
+ }
146
+ return { valid: true };
147
+ }
148
+ // Check for IPv4 addresses
149
+ const ipv4Match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(hostname);
150
+ if (ipv4Match) {
151
+ const octets = [
152
+ parseInt(ipv4Match[1], 10),
153
+ parseInt(ipv4Match[2], 10),
154
+ parseInt(ipv4Match[3], 10),
155
+ parseInt(ipv4Match[4], 10),
156
+ ];
157
+ // 127.0.0.1 already allowed above; block other 127.x.x.x
158
+ if (octets[0] === 127) {
159
+ return { valid: false, reason: 'Blocked loopback address' };
160
+ }
161
+ // Block 10.0.0.0/8
162
+ if (octets[0] === 10) {
163
+ return { valid: false, reason: 'Blocked private IP address (10.0.0.0/8)' };
164
+ }
165
+ // Block 172.16.0.0/12 (172.16.x.x – 172.31.x.x)
166
+ if (octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31) {
167
+ return { valid: false, reason: 'Blocked private IP address (172.16.0.0/12)' };
168
+ }
169
+ // Block 192.168.0.0/16
170
+ if (octets[0] === 192 && octets[1] === 168) {
171
+ return { valid: false, reason: 'Blocked private IP address (192.168.0.0/16)' };
172
+ }
173
+ // Block 169.254.0.0/16 (link-local, includes cloud metadata 169.254.169.254)
174
+ if (octets[0] === 169 && octets[1] === 254) {
175
+ return { valid: false, reason: 'Blocked link-local/cloud metadata address (169.254.0.0/16)' };
176
+ }
177
+ // Block 0.0.0.0
178
+ if (octets.every((o) => o === 0)) {
179
+ return { valid: false, reason: 'Blocked null address (0.0.0.0)' };
180
+ }
181
+ }
182
+ return { valid: true };
183
+ }
@@ -14,9 +14,20 @@ const wait_1 = require("./playwright/tools/wait");
14
14
  const file_1 = require("./playwright/tools/file");
15
15
  const utility_1 = require("./playwright/tools/utility");
16
16
  const extraction_1 = require("./playwright/tools/extraction");
17
+ const fs_1 = require("fs");
18
+ const path_1 = require("path");
19
+ function getVersion() {
20
+ try {
21
+ const pkg = JSON.parse((0, fs_1.readFileSync)((0, path_1.join)(__dirname, '..', '..', 'package.json'), 'utf-8'));
22
+ return pkg.version ?? '0.0.0';
23
+ }
24
+ catch {
25
+ return '0.0.0';
26
+ }
27
+ }
17
28
  const server = new mcp_js_1.McpServer({
18
29
  name: 'wmux',
19
- version: '1.0.0',
30
+ version: getVersion(),
20
31
  });
21
32
  // Helper: wrap an RPC call as an MCP tool result
22
33
  async function callRpc(method, params = {}) {