@wong2kim/wmux 1.0.0 → 1.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.
Files changed (110) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +209 -157
  3. package/dist/cli/cli/client.js +1 -1
  4. package/dist/cli/cli/commands/browser.js +19 -19
  5. package/dist/cli/cli/index.js +58 -58
  6. package/dist/cli/shared/constants.js +17 -4
  7. package/dist/mcp/shared/constants.js +17 -4
  8. package/package.json +96 -84
  9. package/assets/icon.ico +0 -0
  10. package/assets/icon.svg +0 -6
  11. package/forge.config.ts +0 -61
  12. package/index.html +0 -12
  13. package/postcss.config.js +0 -6
  14. package/src/cli/client.ts +0 -76
  15. package/src/cli/commands/browser.ts +0 -128
  16. package/src/cli/commands/input.ts +0 -72
  17. package/src/cli/commands/notify.ts +0 -29
  18. package/src/cli/commands/pane.ts +0 -90
  19. package/src/cli/commands/surface.ts +0 -102
  20. package/src/cli/commands/system.ts +0 -95
  21. package/src/cli/commands/workspace.ts +0 -116
  22. package/src/cli/index.ts +0 -145
  23. package/src/cli/utils.ts +0 -44
  24. package/src/main/index.ts +0 -86
  25. package/src/main/ipc/handlers/clipboard.handler.ts +0 -20
  26. package/src/main/ipc/handlers/metadata.handler.ts +0 -56
  27. package/src/main/ipc/handlers/pty.handler.ts +0 -69
  28. package/src/main/ipc/handlers/session.handler.ts +0 -17
  29. package/src/main/ipc/handlers/shell.handler.ts +0 -11
  30. package/src/main/ipc/registerHandlers.ts +0 -31
  31. package/src/main/mcp/McpRegistrar.ts +0 -156
  32. package/src/main/metadata/MetadataCollector.ts +0 -58
  33. package/src/main/notification/ToastManager.ts +0 -32
  34. package/src/main/pipe/PipeServer.ts +0 -190
  35. package/src/main/pipe/RpcRouter.ts +0 -46
  36. package/src/main/pipe/handlers/_bridge.ts +0 -40
  37. package/src/main/pipe/handlers/browser.rpc.ts +0 -132
  38. package/src/main/pipe/handlers/input.rpc.ts +0 -120
  39. package/src/main/pipe/handlers/meta.rpc.ts +0 -59
  40. package/src/main/pipe/handlers/notify.rpc.ts +0 -53
  41. package/src/main/pipe/handlers/pane.rpc.ts +0 -39
  42. package/src/main/pipe/handlers/surface.rpc.ts +0 -43
  43. package/src/main/pipe/handlers/system.rpc.ts +0 -36
  44. package/src/main/pipe/handlers/workspace.rpc.ts +0 -52
  45. package/src/main/pty/AgentDetector.ts +0 -247
  46. package/src/main/pty/OscParser.ts +0 -81
  47. package/src/main/pty/PTYBridge.ts +0 -88
  48. package/src/main/pty/PTYManager.ts +0 -104
  49. package/src/main/pty/ShellDetector.ts +0 -63
  50. package/src/main/session/SessionManager.ts +0 -53
  51. package/src/main/updater/AutoUpdater.ts +0 -132
  52. package/src/main/window/createWindow.ts +0 -71
  53. package/src/mcp/README.md +0 -56
  54. package/src/mcp/index.ts +0 -153
  55. package/src/mcp/wmux-client.ts +0 -127
  56. package/src/preload/index.ts +0 -111
  57. package/src/preload/preload.ts +0 -108
  58. package/src/renderer/App.tsx +0 -5
  59. package/src/renderer/components/Browser/BrowserPanel.tsx +0 -219
  60. package/src/renderer/components/Browser/BrowserToolbar.tsx +0 -253
  61. package/src/renderer/components/Company/ApprovalDialog.tsx +0 -3
  62. package/src/renderer/components/Company/CompanyView.tsx +0 -7
  63. package/src/renderer/components/Company/MessageFeedPanel.tsx +0 -3
  64. package/src/renderer/components/Layout/AppLayout.tsx +0 -234
  65. package/src/renderer/components/Notification/NotificationPanel.tsx +0 -129
  66. package/src/renderer/components/Palette/CommandPalette.tsx +0 -409
  67. package/src/renderer/components/Palette/PaletteItem.tsx +0 -55
  68. package/src/renderer/components/Pane/Pane.tsx +0 -122
  69. package/src/renderer/components/Pane/PaneContainer.tsx +0 -41
  70. package/src/renderer/components/Pane/SurfaceTabs.tsx +0 -46
  71. package/src/renderer/components/Settings/SettingsPanel.tsx +0 -886
  72. package/src/renderer/components/Sidebar/MiniSidebar.tsx +0 -67
  73. package/src/renderer/components/Sidebar/Sidebar.tsx +0 -84
  74. package/src/renderer/components/Sidebar/WorkspaceItem.tsx +0 -241
  75. package/src/renderer/components/StatusBar/StatusBar.tsx +0 -93
  76. package/src/renderer/components/Terminal/SearchBar.tsx +0 -126
  77. package/src/renderer/components/Terminal/Terminal.tsx +0 -102
  78. package/src/renderer/components/Terminal/ViCopyMode.tsx +0 -104
  79. package/src/renderer/hooks/useKeyboard.ts +0 -310
  80. package/src/renderer/hooks/useNotificationListener.ts +0 -80
  81. package/src/renderer/hooks/useNotificationSound.ts +0 -75
  82. package/src/renderer/hooks/useRpcBridge.ts +0 -451
  83. package/src/renderer/hooks/useT.ts +0 -11
  84. package/src/renderer/hooks/useTerminal.ts +0 -349
  85. package/src/renderer/hooks/useViCopyMode.ts +0 -320
  86. package/src/renderer/i18n/index.ts +0 -69
  87. package/src/renderer/i18n/locales/en.ts +0 -157
  88. package/src/renderer/i18n/locales/ja.ts +0 -155
  89. package/src/renderer/i18n/locales/ko.ts +0 -155
  90. package/src/renderer/i18n/locales/zh.ts +0 -155
  91. package/src/renderer/index.tsx +0 -6
  92. package/src/renderer/stores/index.ts +0 -19
  93. package/src/renderer/stores/slices/notificationSlice.ts +0 -56
  94. package/src/renderer/stores/slices/paneSlice.ts +0 -141
  95. package/src/renderer/stores/slices/surfaceSlice.ts +0 -122
  96. package/src/renderer/stores/slices/uiSlice.ts +0 -247
  97. package/src/renderer/stores/slices/workspaceSlice.ts +0 -120
  98. package/src/renderer/styles/globals.css +0 -150
  99. package/src/renderer/themes.ts +0 -99
  100. package/src/shared/constants.ts +0 -53
  101. package/src/shared/electron.d.ts +0 -11
  102. package/src/shared/rpc.ts +0 -71
  103. package/src/shared/types.ts +0 -176
  104. package/tailwind.config.js +0 -11
  105. package/tsconfig.cli.json +0 -24
  106. package/tsconfig.json +0 -21
  107. package/tsconfig.mcp.json +0 -25
  108. package/vite.main.config.ts +0 -14
  109. package/vite.preload.config.ts +0 -9
  110. package/vite.renderer.config.ts +0 -6
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ENV_KEYS = exports.PIPE_NAME = exports.IPC = void 0;
3
+ exports.ENV_KEYS = exports.IPC = void 0;
4
4
  exports.getPipeName = getPipeName;
5
5
  exports.getAuthTokenPath = getAuthTokenPath;
6
6
  // IPC Channel names
@@ -11,6 +11,8 @@ exports.IPC = {
11
11
  PTY_DISPOSE: 'pty:dispose',
12
12
  PTY_DATA: 'pty:data',
13
13
  PTY_EXIT: 'pty:exit',
14
+ PTY_LIST: 'pty:list',
15
+ PTY_RECONNECT: 'pty:reconnect',
14
16
  SHELL_LIST: 'shell:list',
15
17
  SESSION_SAVE: 'session:save',
16
18
  SESSION_LOAD: 'session:load',
@@ -24,6 +26,8 @@ exports.IPC = {
24
26
  // Clipboard (main process bridge)
25
27
  CLIPBOARD_WRITE: 'clipboard:write',
26
28
  CLIPBOARD_READ: 'clipboard:read',
29
+ CLIPBOARD_READ_IMAGE: 'clipboard:read-image',
30
+ CLIPBOARD_HAS_IMAGE: 'clipboard:has-image',
27
31
  // Phase 4: Auto updater
28
32
  UPDATE_CHECK: 'update:check',
29
33
  UPDATE_AVAILABLE: 'update:available',
@@ -33,12 +37,21 @@ exports.IPC = {
33
37
  UPDATE_INSTALL: 'update:install',
34
38
  // Settings sync (renderer → main)
35
39
  TOAST_ENABLED: 'settings:toast-enabled',
40
+ // Agent critical action approval
41
+ APPROVAL_REQUEST: 'approval:request',
42
+ // File system
43
+ FS_READ_DIR: 'fs:read-dir',
44
+ FS_READ_FILE: 'fs:read-file',
45
+ FS_WATCH: 'fs:watch',
46
+ FS_UNWATCH: 'fs:unwatch',
47
+ FS_CHANGED: 'fs:changed',
36
48
  };
37
- // Named Pipe path for wmux API
49
+ // Named Pipe / Unix socket path for wmux API
38
50
  // Fixed name so MCP clients (e.g. Claude Code) can reconnect across wmux restarts
39
- exports.PIPE_NAME = '\\\\.\\pipe\\wmux';
40
51
  function getPipeName() {
41
- return exports.PIPE_NAME;
52
+ if (process.platform === 'win32')
53
+ return '\\\\.\\pipe\\wmux';
54
+ return '/tmp/wmux.sock';
42
55
  }
43
56
  // Environment variable names injected into PTY sessions
44
57
  exports.ENV_KEYS = {
package/package.json CHANGED
@@ -1,84 +1,96 @@
1
- {
2
- "name": "@wong2kim/wmux",
3
- "productName": "wmux",
4
- "version": "1.0.0",
5
- "description": "AI Agent Terminal for Windows - Run Claude Code, Codex, Gemini CLI in parallel",
6
- "main": ".vite/build/index.js",
7
- "scripts": {
8
- "start": "npm run build:mcp && electron-forge start",
9
- "package": "npm run build:mcp && electron-forge package",
10
- "make": "npm run build:mcp && electron-forge make",
11
- "publish": "electron-forge publish",
12
- "lint": "eslint --ext .ts,.tsx .",
13
- "build:cli": "tsc -p tsconfig.cli.json",
14
- "build:mcp": "tsc -p tsconfig.mcp.json",
15
- "cli": "node dist/cli/cli/index.js",
16
- "mcp": "node dist/mcp/mcp/index.js"
17
- },
18
- "bin": {
19
- "wmux": "dist/cli/cli/index.js",
20
- "wmux-mcp": "dist/mcp/mcp/index.js"
21
- },
22
- "keywords": ["terminal", "multiplexer", "electron", "ai", "claude", "agent", "windows", "pty", "mcp"],
23
- "repository": {
24
- "type": "git",
25
- "url": "https://github.com/openwong2kim/wmux.git"
26
- },
27
- "author": {
28
- "name": "openwong2kim",
29
- "email": "100856670+openwong2kim@users.noreply.github.com"
30
- },
31
- "license": "MIT",
32
- "files": [
33
- "dist/cli",
34
- "dist/mcp",
35
- "src",
36
- "assets",
37
- "index.html",
38
- "forge.config.ts",
39
- "vite.*.config.ts",
40
- "tsconfig*.json",
41
- "tailwind.config.js",
42
- "postcss.config.js"
43
- ],
44
- "devDependencies": {
45
- "@electron-forge/cli": "^7.11.1",
46
- "@electron-forge/maker-deb": "^7.11.1",
47
- "@electron-forge/maker-rpm": "^7.11.1",
48
- "@electron-forge/maker-squirrel": "^7.11.1",
49
- "@electron-forge/maker-zip": "^7.11.1",
50
- "@electron-forge/plugin-auto-unpack-natives": "^7.11.1",
51
- "@electron-forge/plugin-fuses": "^7.11.1",
52
- "@electron-forge/plugin-vite": "^7.11.1",
53
- "@electron/fuses": "^1.8.0",
54
- "@types/electron-squirrel-startup": "^1.0.2",
55
- "@types/react": "^19.2.14",
56
- "@types/react-dom": "^19.2.3",
57
- "@typescript-eslint/eslint-plugin": "^5.62.0",
58
- "@typescript-eslint/parser": "^5.62.0",
59
- "@vitejs/plugin-react": "^4.7.0",
60
- "autoprefixer": "^10.4.27",
61
- "electron": "41.0.3",
62
- "eslint": "^8.57.1",
63
- "eslint-plugin-import": "^2.32.0",
64
- "postcss": "^8.5.8",
65
- "tailwindcss": "^3.4.19",
66
- "typescript": "^5.9.3",
67
- "vite": "^5.4.21"
68
- },
69
- "dependencies": {
70
- "@modelcontextprotocol/sdk": "^1.27.1",
71
- "@xterm/addon-fit": "^0.11.0",
72
- "@xterm/addon-search": "^0.16.0",
73
- "@xterm/addon-webgl": "^0.19.0",
74
- "@xterm/xterm": "^6.0.0",
75
- "electron-squirrel-startup": "^1.0.1",
76
- "immer": "^11.1.4",
77
- "node-pty": "^1.1.0",
78
- "react": "^19.2.4",
79
- "react-dom": "^19.2.4",
80
- "react-resizable-panels": "^4.7.3",
81
- "zod": "^4.3.6",
82
- "zustand": "^5.0.12"
83
- }
84
- }
1
+ {
2
+ "name": "@wong2kim/wmux",
3
+ "productName": "wmux",
4
+ "version": "1.0.1",
5
+ "description": "Windows terminal multiplexer with MCP server for AI agents - run multiple CLI sessions in parallel, control via Claude Code and other AI tools",
6
+ "main": ".vite/build/index.js",
7
+ "scripts": {
8
+ "postinstall": "node scripts/fix-node-pty.js",
9
+ "start": "npm run build:mcp && electron-forge start",
10
+ "package": "npm run build:mcp && electron-forge package",
11
+ "make": "npm run build:mcp && electron-forge make",
12
+ "publish": "electron-forge publish",
13
+ "lint": "eslint --ext .ts,.tsx .",
14
+ "build:cli": "tsc -p tsconfig.cli.json",
15
+ "build:mcp": "tsc -p tsconfig.mcp.json",
16
+ "cli": "node dist/cli/cli/index.js",
17
+ "mcp": "node dist/mcp/mcp/index.js",
18
+ "prepublishOnly": "npm run build:cli && npm run build:mcp"
19
+ },
20
+ "bin": {
21
+ "wmux": "dist/cli/cli/index.js",
22
+ "wmux-mcp": "dist/mcp/mcp/index.js"
23
+ },
24
+ "files": [
25
+ "dist/cli/",
26
+ "dist/mcp/",
27
+ "README.md",
28
+ "LICENSE"
29
+ ],
30
+ "keywords": [
31
+ "terminal",
32
+ "multiplexer",
33
+ "mcp",
34
+ "ai-agent",
35
+ "cli",
36
+ "windows",
37
+ "claude-code",
38
+ "electron"
39
+ ],
40
+ "author": {
41
+ "name": "openwong2kim",
42
+ "email": "100856670+openwong2kim@users.noreply.github.com"
43
+ },
44
+ "license": "MIT",
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "https://github.com/openwong2kim/wmux.git"
48
+ },
49
+ "homepage": "https://github.com/openwong2kim/wmux#readme",
50
+ "bugs": {
51
+ "url": "https://github.com/openwong2kim/wmux/issues"
52
+ },
53
+ "engines": {
54
+ "node": ">=18.0.0"
55
+ },
56
+ "devDependencies": {
57
+ "@electron-forge/cli": "^7.11.1",
58
+ "@electron-forge/maker-deb": "^7.11.1",
59
+ "@electron-forge/maker-rpm": "^7.11.1",
60
+ "@electron-forge/maker-squirrel": "^7.11.1",
61
+ "@electron-forge/maker-zip": "^7.11.1",
62
+ "@electron-forge/plugin-auto-unpack-natives": "^7.11.1",
63
+ "@electron-forge/plugin-fuses": "^7.11.1",
64
+ "@electron-forge/plugin-vite": "^7.11.1",
65
+ "@electron/fuses": "^1.8.0",
66
+ "@types/electron-squirrel-startup": "^1.0.2",
67
+ "@types/react": "^19.2.14",
68
+ "@types/react-dom": "^19.2.3",
69
+ "@typescript-eslint/eslint-plugin": "^5.62.0",
70
+ "@typescript-eslint/parser": "^5.62.0",
71
+ "@vitejs/plugin-react": "^4.7.0",
72
+ "autoprefixer": "^10.4.27",
73
+ "electron": "41.0.3",
74
+ "eslint": "^8.57.1",
75
+ "eslint-plugin-import": "^2.32.0",
76
+ "postcss": "^8.5.8",
77
+ "tailwindcss": "^3.4.19",
78
+ "typescript": "^5.9.3",
79
+ "vite": "^5.4.21"
80
+ },
81
+ "dependencies": {
82
+ "@modelcontextprotocol/sdk": "^1.27.1",
83
+ "@xterm/addon-fit": "^0.11.0",
84
+ "@xterm/addon-search": "^0.16.0",
85
+ "@xterm/addon-webgl": "^0.19.0",
86
+ "@xterm/xterm": "^6.0.0",
87
+ "electron-squirrel-startup": "^1.0.1",
88
+ "immer": "^11.1.4",
89
+ "node-pty": "^1.1.0",
90
+ "react": "^19.2.4",
91
+ "react-dom": "^19.2.4",
92
+ "react-resizable-panels": "^4.7.3",
93
+ "zod": "^4.3.6",
94
+ "zustand": "^5.0.12"
95
+ }
96
+ }
package/assets/icon.ico DELETED
Binary file
package/assets/icon.svg DELETED
@@ -1,6 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="none">
2
- <rect width="256" height="256" rx="48" fill="#1e1e2e"/>
3
- <rect x="8" y="8" width="240" height="240" rx="40" fill="#181825" stroke="#313244" stroke-width="2"/>
4
- <text x="128" y="170" text-anchor="middle" font-family="monospace" font-weight="bold" font-size="140" fill="#89b4fa">W</text>
5
- <circle cx="200" cy="64" r="16" fill="#a6e3a1"/>
6
- </svg>
package/forge.config.ts DELETED
@@ -1,61 +0,0 @@
1
- import type { ForgeConfig } from '@electron-forge/shared-types';
2
- import { MakerSquirrel } from '@electron-forge/maker-squirrel';
3
- import { MakerZIP } from '@electron-forge/maker-zip';
4
- import { VitePlugin } from '@electron-forge/plugin-vite';
5
- import { AutoUnpackNativesPlugin } from '@electron-forge/plugin-auto-unpack-natives';
6
- import { FusesPlugin } from '@electron-forge/plugin-fuses';
7
- import { FuseV1Options, FuseVersion } from '@electron/fuses';
8
-
9
- const config: ForgeConfig = {
10
- packagerConfig: {
11
- asar: true,
12
- icon: './assets/icon',
13
- extraResource: ['./dist/mcp'],
14
- },
15
- rebuildConfig: { disablePreGypRecuild: true },
16
- hooks: {
17
- // Skip native rebuild — pre-built binaries already in node_modules
18
- readPackageJson: async (_config, packageJson) => {
19
- packageJson.dependencies = packageJson.dependencies || {};
20
- return packageJson;
21
- },
22
- },
23
- makers: [
24
- new MakerSquirrel({}),
25
- new MakerZIP({}, ['darwin']),
26
- ],
27
- plugins: [
28
- new AutoUnpackNativesPlugin({}),
29
- new VitePlugin({
30
- build: [
31
- {
32
- entry: 'src/main/index.ts',
33
- config: 'vite.main.config.ts',
34
- target: 'main',
35
- },
36
- {
37
- entry: 'src/preload/preload.ts',
38
- config: 'vite.preload.config.ts',
39
- target: 'preload',
40
- },
41
- ],
42
- renderer: [
43
- {
44
- name: 'main_window',
45
- config: 'vite.renderer.config.ts',
46
- },
47
- ],
48
- }),
49
- new FusesPlugin({
50
- version: FuseVersion.V1,
51
- [FuseV1Options.RunAsNode]: false,
52
- [FuseV1Options.EnableCookieEncryption]: true,
53
- [FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,
54
- [FuseV1Options.EnableNodeCliInspectArguments]: false,
55
- [FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,
56
- [FuseV1Options.OnlyLoadAppFromAsar]: true,
57
- }),
58
- ],
59
- };
60
-
61
- export default config;
package/index.html DELETED
@@ -1,12 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>WinMux</title>
7
- </head>
8
- <body>
9
- <div id="root"></div>
10
- <script type="module" src="/src/renderer/index.tsx"></script>
11
- </body>
12
- </html>
package/postcss.config.js DELETED
@@ -1,6 +0,0 @@
1
- module.exports = {
2
- plugins: {
3
- tailwindcss: {},
4
- autoprefixer: {},
5
- },
6
- };
package/src/cli/client.ts DELETED
@@ -1,76 +0,0 @@
1
- #!/usr/bin/env node
2
- import * as net from 'net';
3
- import * as crypto from 'crypto';
4
- import type { RpcRequest, RpcResponse, RpcMethod } from '../shared/rpc';
5
-
6
- const PIPE_NAME = process.env.WMUX_SOCKET_PATH || '\\\\.\\pipe\\wmux';
7
- const TIMEOUT_MS = 5000;
8
-
9
- export function sendRequest(
10
- method: RpcMethod,
11
- params: Record<string, unknown> = {}
12
- ): Promise<RpcResponse> {
13
- return new Promise((resolve, reject) => {
14
- const id = crypto.randomUUID();
15
- const token = process.env.WMUX_AUTH_TOKEN;
16
- const request: RpcRequest = { id, method, params, token };
17
-
18
- const socket = net.connect(PIPE_NAME);
19
- let buffer = '';
20
- let settled = false;
21
-
22
- const timer = setTimeout(() => {
23
- if (!settled) {
24
- settled = true;
25
- socket.destroy();
26
- reject(new Error('Request timed out after 5 seconds.'));
27
- }
28
- }, TIMEOUT_MS);
29
-
30
- socket.on('connect', () => {
31
- socket.write(JSON.stringify(request) + '\n');
32
- });
33
-
34
- socket.on('data', (chunk: Buffer) => {
35
- buffer += chunk.toString('utf8');
36
- const lines = buffer.split('\n');
37
- buffer = lines.pop() ?? '';
38
-
39
- for (const line of lines) {
40
- const trimmed = line.trim();
41
- if (!trimmed) continue;
42
- try {
43
- const response = JSON.parse(trimmed) as RpcResponse;
44
- if (response.id === id && !settled) {
45
- settled = true;
46
- clearTimeout(timer);
47
- socket.destroy();
48
- resolve(response);
49
- }
50
- } catch {
51
- // ignore malformed lines
52
- }
53
- }
54
- });
55
-
56
- socket.on('error', (err: NodeJS.ErrnoException) => {
57
- if (!settled) {
58
- settled = true;
59
- clearTimeout(timer);
60
- if (err.code === 'ENOENT' || err.code === 'ECONNREFUSED') {
61
- reject(new Error('wmux is not running. Start the app first.'));
62
- } else {
63
- reject(new Error(`Connection error: ${err.message}`));
64
- }
65
- }
66
- });
67
-
68
- socket.on('close', () => {
69
- if (!settled) {
70
- settled = true;
71
- clearTimeout(timer);
72
- reject(new Error('Connection closed before response was received.'));
73
- }
74
- });
75
- });
76
- }
@@ -1,128 +0,0 @@
1
- import { sendRequest } from '../client';
2
- import { printResult, printError } from '../utils';
3
- import type { RpcResponse } from '../../shared/rpc';
4
-
5
- const BROWSER_HELP = `
6
- wmux browser — Scriptable Browser API
7
-
8
- USAGE
9
- wmux browser <subcommand> [args]
10
-
11
- SUBCOMMANDS
12
- snapshot Return the full page HTML (document.documentElement.outerHTML)
13
- click <selector> Click the first element matching the CSS selector
14
- fill <selector> <text> Set the value of an input matching the CSS selector
15
- eval <code> Execute arbitrary JavaScript in the page context
16
- navigate <url> Navigate the active browser surface to a URL
17
-
18
- EXAMPLES
19
- wmux browser snapshot
20
- wmux browser click "#submit-btn"
21
- wmux browser fill "input[name=email]" "user@example.com"
22
- wmux browser eval "document.title"
23
- wmux browser navigate "https://example.com"
24
- `.trimStart();
25
-
26
- export async function handleBrowser(
27
- args: string[],
28
- jsonMode: boolean,
29
- ): Promise<void> {
30
- const sub = args[0];
31
- const rest = args.slice(1);
32
-
33
- if (!sub || sub === '--help' || sub === '-h') {
34
- process.stdout.write(BROWSER_HELP);
35
- process.exit(0);
36
- }
37
-
38
- let response: RpcResponse;
39
-
40
- switch (sub) {
41
- // ── browser snapshot ─────────────────────────────────────────────────────
42
- case 'snapshot': {
43
- response = await sendRequest('browser.snapshot', {});
44
- if (jsonMode) {
45
- printResult(response);
46
- } else {
47
- if (!response.ok) { printError(response); return; }
48
- const r = response.result as { html?: string } | null;
49
- process.stdout.write(r?.html ?? '');
50
- }
51
- break;
52
- }
53
-
54
- // ── browser click <selector> ─────────────────────────────────────────────
55
- case 'click': {
56
- const selector = rest[0];
57
- if (!selector) {
58
- console.error('Error: browser click requires <selector>');
59
- process.exit(1);
60
- }
61
- response = await sendRequest('browser.click', { selector });
62
- if (jsonMode) {
63
- printResult(response);
64
- } else {
65
- if (!response.ok) { printError(response); return; }
66
- console.log(`Clicked: ${selector}`);
67
- }
68
- break;
69
- }
70
-
71
- // ── browser fill <selector> <text> ───────────────────────────────────────
72
- case 'fill': {
73
- const selector = rest[0];
74
- const text = rest.slice(1).join(' ');
75
- if (!selector) {
76
- console.error('Error: browser fill requires <selector> <text>');
77
- process.exit(1);
78
- }
79
- response = await sendRequest('browser.fill', { selector, text });
80
- if (jsonMode) {
81
- printResult(response);
82
- } else {
83
- if (!response.ok) { printError(response); return; }
84
- console.log(`Filled "${selector}" with "${text}"`);
85
- }
86
- break;
87
- }
88
-
89
- // ── browser eval <code> ──────────────────────────────────────────────────
90
- case 'eval': {
91
- const code = rest.join(' ');
92
- if (!code) {
93
- console.error('Error: browser eval requires <code>');
94
- process.exit(1);
95
- }
96
- response = await sendRequest('browser.eval', { code });
97
- if (jsonMode) {
98
- printResult(response);
99
- } else {
100
- if (!response.ok) { printError(response); return; }
101
- const r = response.result as { result?: unknown } | null;
102
- console.log(JSON.stringify(r?.result, null, 2));
103
- }
104
- break;
105
- }
106
-
107
- // ── browser navigate <url> ───────────────────────────────────────────────
108
- case 'navigate': {
109
- const url = rest[0];
110
- if (!url) {
111
- console.error('Error: browser navigate requires <url>');
112
- process.exit(1);
113
- }
114
- response = await sendRequest('browser.navigate', { url });
115
- if (jsonMode) {
116
- printResult(response);
117
- } else {
118
- if (!response.ok) { printError(response); return; }
119
- console.log(`Navigated to: ${url}`);
120
- }
121
- break;
122
- }
123
-
124
- default:
125
- console.error(`Unknown browser subcommand: "${sub}". Run 'wmux browser --help' for usage.`);
126
- process.exit(1);
127
- }
128
- }
@@ -1,72 +0,0 @@
1
- import { sendRequest } from '../client';
2
- import { printResult, printError } from '../utils';
3
- import type { RpcResponse } from '../../shared/rpc';
4
-
5
- export async function handleInput(
6
- cmd: string,
7
- args: string[],
8
- jsonMode: boolean
9
- ): Promise<void> {
10
- let response: RpcResponse;
11
-
12
- switch (cmd) {
13
- case 'send': {
14
- const text = args.join(' ');
15
- if (!text) {
16
- console.error('Error: send requires <text>');
17
- process.exit(1);
18
- }
19
- response = await sendRequest('input.send', { text });
20
- if (jsonMode) {
21
- printResult(response);
22
- } else {
23
- if (!response.ok) { printError(response); return; }
24
- console.log('Text sent.');
25
- }
26
- break;
27
- }
28
-
29
- case 'send-key': {
30
- const key = args[0];
31
- if (!key) {
32
- console.error('Error: send-key requires <keystroke>');
33
- process.exit(1);
34
- }
35
- response = await sendRequest('input.sendKey', { key });
36
- if (jsonMode) {
37
- printResult(response);
38
- } else {
39
- if (!response.ok) { printError(response); return; }
40
- console.log(`Key sent: ${key}`);
41
- }
42
- break;
43
- }
44
-
45
- case 'read-screen': {
46
- response = await sendRequest('input.readScreen', {});
47
- if (jsonMode) {
48
- printResult(response);
49
- } else {
50
- if (!response.ok) { printError(response); return; }
51
- // server returns { text: string } object or plain string
52
- const result = response.result as { text?: string } | string;
53
- let screen: string;
54
- if (typeof result === 'object' && result !== null && 'text' in result) {
55
- screen = result.text ?? '';
56
- } else if (typeof result === 'string') {
57
- screen = result;
58
- } else {
59
- console.log(JSON.stringify(result, null, 2));
60
- break;
61
- }
62
- process.stdout.write(screen);
63
- if (!screen.endsWith('\n')) process.stdout.write('\n');
64
- }
65
- break;
66
- }
67
-
68
- default:
69
- console.error(`Unknown input command: ${cmd}`);
70
- process.exit(1);
71
- }
72
- }
@@ -1,29 +0,0 @@
1
- import { sendRequest } from '../client';
2
- import { printResult, printError, parseFlag } from '../utils';
3
- import type { RpcResponse } from '../../shared/rpc';
4
-
5
- export async function handleNotify(
6
- args: string[],
7
- jsonMode: boolean
8
- ): Promise<void> {
9
- const title = parseFlag(args, '--title');
10
- const body = parseFlag(args, '--body');
11
-
12
- if (!title) {
13
- console.error('Error: notify requires --title <text>');
14
- process.exit(1);
15
- }
16
- if (!body) {
17
- console.error('Error: notify requires --body <text>');
18
- process.exit(1);
19
- }
20
-
21
- const response: RpcResponse = await sendRequest('notify', { title, body });
22
-
23
- if (jsonMode) {
24
- printResult(response);
25
- } else {
26
- if (!response.ok) { printError(response); return; }
27
- console.log(`Notification sent: "${title}"`);
28
- }
29
- }