@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,132 +0,0 @@
1
- /**
2
- * AutoUpdater
3
- *
4
- * Electron 내장 autoUpdater API 기반 자동 업데이트 시스템.
5
- *
6
- * 실제 배포 환경에서는:
7
- * 1. electron-forge squirrel maker로 빌드
8
- * 2. GitHub Releases (또는 S3)에 업데이트 파일 업로드
9
- * 3. FEED_URL을 업데이트 서버 주소로 변경
10
- *
11
- * 개발 환경에서는 autoUpdater가 지원되지 않으므로 모두 no-op 처리.
12
- */
13
-
14
- import { autoUpdater, type BrowserWindow, ipcMain } from 'electron';
15
- import { IPC } from '../../shared/constants';
16
-
17
- // GitHub Releases 또는 별도 업데이트 서버 URL
18
- // 예: https://update.winmux.app/update/win32/${version}
19
- const FEED_URL = '';
20
-
21
- // 업데이트 자동 확인 간격 (30분)
22
- const CHECK_INTERVAL_MS = 30 * 60 * 1000;
23
-
24
- export class AutoUpdater {
25
- private checkTimer: ReturnType<typeof setInterval> | null = null;
26
- private getWindow: () => BrowserWindow | null;
27
- private isChecking = false;
28
-
29
- constructor(getWindow: () => BrowserWindow | null) {
30
- this.getWindow = getWindow;
31
- }
32
-
33
- start(): void {
34
- if (!FEED_URL || process.env.NODE_ENV === 'development') {
35
- // 업데이트 서버가 설정되지 않았거나 개발 모드 — 초기화 스킵
36
- this.registerIpcHandlers();
37
- return;
38
- }
39
-
40
- try {
41
- autoUpdater.setFeedURL({ url: FEED_URL });
42
- this.setupAutoUpdaterEvents();
43
- this.registerIpcHandlers();
44
-
45
- // 앱 시작 후 15초 뒤 첫 번째 확인 (시작 부하 방지)
46
- setTimeout(() => this.check(), 15_000);
47
-
48
- // 이후 주기적 확인
49
- this.checkTimer = setInterval(() => this.check(), CHECK_INTERVAL_MS);
50
- } catch (err) {
51
- console.warn('[AutoUpdater] Failed to initialize:', err);
52
- }
53
- }
54
-
55
- stop(): void {
56
- if (this.checkTimer !== null) {
57
- clearInterval(this.checkTimer);
58
- this.checkTimer = null;
59
- }
60
- // IPC 핸들러 정리
61
- ipcMain.removeHandler(IPC.UPDATE_CHECK);
62
- ipcMain.removeHandler(IPC.UPDATE_INSTALL);
63
- }
64
-
65
- private check(): void {
66
- if (this.isChecking) return;
67
- try {
68
- this.isChecking = true;
69
- autoUpdater.checkForUpdates();
70
- } catch (err) {
71
- console.warn('[AutoUpdater] checkForUpdates error:', err);
72
- this.isChecking = false;
73
- }
74
- }
75
-
76
- private setupAutoUpdaterEvents(): void {
77
- autoUpdater.on('checking-for-update', () => {
78
- this.sendToRenderer(IPC.UPDATE_CHECK, { status: 'checking' });
79
- });
80
-
81
- autoUpdater.on('update-available', () => {
82
- this.isChecking = false;
83
- this.sendToRenderer(IPC.UPDATE_AVAILABLE, { status: 'available' });
84
- });
85
-
86
- autoUpdater.on('update-not-available', () => {
87
- this.isChecking = false;
88
- this.sendToRenderer(IPC.UPDATE_NOT_AVAILABLE, { status: 'not-available' });
89
- });
90
-
91
- autoUpdater.on('error', (err: Error) => {
92
- this.isChecking = false;
93
- console.warn('[AutoUpdater] error:', err.message);
94
- this.sendToRenderer(IPC.UPDATE_ERROR, { status: 'error', message: err.message });
95
- });
96
-
97
- autoUpdater.on('update-downloaded', (_event, releaseNotes, releaseName) => {
98
- this.sendToRenderer(IPC.UPDATE_AVAILABLE, {
99
- status: 'downloaded',
100
- releaseName,
101
- releaseNotes,
102
- });
103
- });
104
- }
105
-
106
- private registerIpcHandlers(): void {
107
- // Renderer가 수동으로 업데이트 확인 요청
108
- ipcMain.handle(IPC.UPDATE_CHECK, () => {
109
- if (!FEED_URL || process.env.NODE_ENV === 'development') {
110
- return { status: 'not-available' };
111
- }
112
- this.check();
113
- return { status: 'checking' };
114
- });
115
-
116
- // Renderer가 "지금 설치" 요청 → 앱 재시작 후 업데이트 적용
117
- ipcMain.handle(IPC.UPDATE_INSTALL, () => {
118
- try {
119
- autoUpdater.quitAndInstall();
120
- } catch (err) {
121
- console.warn('[AutoUpdater] quitAndInstall error:', err);
122
- }
123
- });
124
- }
125
-
126
- private sendToRenderer(channel: string, data: Record<string, unknown>): void {
127
- const win = this.getWindow();
128
- if (win && !win.isDestroyed()) {
129
- win.webContents.send(channel, data);
130
- }
131
- }
132
- }
@@ -1,71 +0,0 @@
1
- import { BrowserWindow } from 'electron';
2
- import path from 'node:path';
3
-
4
- export function createWindow(): BrowserWindow {
5
- const mainWindow = new BrowserWindow({
6
- width: 1280,
7
- height: 800,
8
- minWidth: 800,
9
- minHeight: 600,
10
- title: 'wmux',
11
- icon: path.join(__dirname, '../../assets/icon.ico'),
12
- backgroundColor: '#1e1e2e',
13
- webPreferences: {
14
- preload: path.join(__dirname, 'preload.js'),
15
- contextIsolation: true,
16
- nodeIntegration: false,
17
- webviewTag: true,
18
- },
19
- });
20
-
21
- if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
22
- mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL);
23
- } else {
24
- mainWindow.loadFile(
25
- path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`),
26
- );
27
- }
28
-
29
- // CSP header — production only (dev needs full access for Vite HMR)
30
- if (!MAIN_WINDOW_VITE_DEV_SERVER_URL) {
31
- mainWindow.webContents.session.webRequest.onHeadersReceived((details, callback) => {
32
- callback({
33
- responseHeaders: {
34
- ...details.responseHeaders,
35
- 'Content-Security-Policy': [
36
- "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'",
37
- ],
38
- },
39
- });
40
- });
41
- }
42
-
43
- // Harden webview security: strip preload, enforce contextIsolation
44
- mainWindow.webContents.on('will-attach-webview', (_event, webPreferences) => {
45
- delete webPreferences.preload;
46
- delete (webPreferences as Record<string, unknown>)['preloadURL'];
47
- webPreferences.nodeIntegration = false;
48
- webPreferences.contextIsolation = true;
49
- webPreferences.sandbox = true;
50
- // Ensure web security (same-origin policy) is not accidentally disabled
51
- (webPreferences as Record<string, unknown>)['webSecurity'] = true;
52
- });
53
-
54
- // Block webview navigations to dangerous URL schemes
55
- mainWindow.webContents.on('will-navigate', (event, url) => {
56
- const normalized = url.trim().toLowerCase();
57
- if (
58
- normalized.startsWith('javascript:') ||
59
- normalized.startsWith('vbscript:') ||
60
- normalized.startsWith('data:')
61
- ) {
62
- event.preventDefault();
63
- }
64
- });
65
-
66
- if (process.env.NODE_ENV === 'development') {
67
- mainWindow.webContents.openDevTools();
68
- }
69
-
70
- return mainWindow;
71
- }
package/src/mcp/README.md DELETED
@@ -1,56 +0,0 @@
1
- # wmux MCP Server
2
-
3
- MCP server that lets Claude Code control wmux's browser and terminal.
4
- Supports multi-agent use — each agent can target its own browser via `surfaceId`.
5
-
6
- ## Setup
7
-
8
- 1. Build the MCP server:
9
- ```bash
10
- npm run build:mcp
11
- ```
12
-
13
- 2. Add to your project's `.mcp.json`:
14
- ```json
15
- {
16
- "mcpServers": {
17
- "wmux": {
18
- "command": "node",
19
- "args": ["<path-to-wmux>/dist/mcp/mcp/index.js"]
20
- }
21
- }
22
- }
23
- ```
24
-
25
- `WMUX_SOCKET_PATH` and `WMUX_AUTH_TOKEN` are automatically set in wmux
26
- terminal sessions. When running Claude Code inside wmux, no extra env
27
- config is needed.
28
-
29
- ## Available Tools
30
-
31
- | Tool | Description |
32
- |------|-------------|
33
- | `browser_navigate` | Navigate browser to URL |
34
- | `browser_snapshot` | Get page HTML |
35
- | `browser_click` | Click element by CSS selector |
36
- | `browser_fill` | Fill input by CSS selector |
37
- | `browser_eval` | Execute JS in browser |
38
- | `terminal_read` | Read terminal screen |
39
- | `terminal_send` | Send text to terminal |
40
- | `terminal_send_key` | Send key (enter, ctrl+c, etc.) |
41
- | `workspace_list` | List workspaces |
42
- | `surface_list` | List surfaces (terminals + browsers) |
43
- | `pane_list` | List panes |
44
-
45
- ## Multi-Agent Usage
46
-
47
- All browser tools accept an optional `surfaceId` parameter. Use `surface_list`
48
- to discover available surfaces, then pass the browser surface's ID:
49
-
50
- ```
51
- 1. Call surface_list → find your browser surface ID
52
- 2. Call browser_navigate with surfaceId="<your-browser-id>"
53
- 3. Call browser_snapshot with surfaceId="<your-browser-id>"
54
- ```
55
-
56
- When `surfaceId` is omitted, the currently active browser surface is used.
package/src/mcp/index.ts DELETED
@@ -1,153 +0,0 @@
1
- #!/usr/bin/env node
2
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
- import { z } from 'zod';
5
- import { sendRpc } from './wmux-client';
6
- import type { RpcMethod } from '../shared/rpc';
7
-
8
- const server = new McpServer({
9
- name: 'wmux',
10
- version: '1.0.0',
11
- });
12
-
13
- // Helper: wrap an RPC call as an MCP tool result
14
- async function callRpc(
15
- method: RpcMethod,
16
- params: Record<string, unknown> = {},
17
- ): Promise<{ content: { type: 'text'; text: string }[] }> {
18
- const result = await sendRpc(method, params);
19
- const text = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
20
- return { content: [{ type: 'text', text }] };
21
- }
22
-
23
- // Optional surfaceId schema used by browser and terminal tools
24
- const optionalSurfaceId = z.string().optional().describe(
25
- 'Target a specific surface by ID. Omit to use the active surface.',
26
- );
27
-
28
- // === Browser tools ===
29
-
30
- server.tool(
31
- 'browser_open',
32
- 'Open a new browser panel in the active pane. Use this when no browser surface exists yet.',
33
- {
34
- url: z.string().optional().describe('Initial URL to load (defaults to google.com)'),
35
- },
36
- async ({ url }) =>
37
- callRpc('browser.open', url ? { url } : {}),
38
- );
39
-
40
- server.tool(
41
- 'browser_navigate',
42
- 'Navigate the wmux browser panel to a URL',
43
- {
44
- url: z.string().describe('The URL to navigate to'),
45
- surfaceId: optionalSurfaceId,
46
- },
47
- async ({ url, surfaceId }) =>
48
- callRpc('browser.navigate', { url, ...(surfaceId && { surfaceId }) }),
49
- );
50
-
51
- server.tool(
52
- 'browser_snapshot',
53
- 'Get the full HTML content of the current page in the wmux browser panel',
54
- { surfaceId: optionalSurfaceId },
55
- async ({ surfaceId }) =>
56
- callRpc('browser.snapshot', surfaceId ? { surfaceId } : {}),
57
- );
58
-
59
- server.tool(
60
- 'browser_click',
61
- 'Click an element in the wmux browser panel by CSS selector',
62
- {
63
- selector: z.string().describe('CSS selector of the element to click'),
64
- surfaceId: optionalSurfaceId,
65
- },
66
- async ({ selector, surfaceId }) =>
67
- callRpc('browser.click', { selector, ...(surfaceId && { surfaceId }) }),
68
- );
69
-
70
- server.tool(
71
- 'browser_fill',
72
- 'Fill an input field in the wmux browser panel by CSS selector',
73
- {
74
- selector: z.string().describe('CSS selector of the input element'),
75
- text: z.string().describe('Text to fill into the input'),
76
- surfaceId: optionalSurfaceId,
77
- },
78
- async ({ selector, text, surfaceId }) =>
79
- callRpc('browser.fill', { selector, text, ...(surfaceId && { surfaceId }) }),
80
- );
81
-
82
- server.tool(
83
- 'browser_eval',
84
- 'Execute JavaScript in the wmux browser panel and return the result',
85
- {
86
- code: z.string().describe('JavaScript code to execute in the browser context'),
87
- surfaceId: optionalSurfaceId,
88
- },
89
- async ({ code, surfaceId }) =>
90
- callRpc('browser.eval', { code, ...(surfaceId && { surfaceId }) }),
91
- );
92
-
93
- // === Terminal tools ===
94
-
95
- server.tool(
96
- 'terminal_read',
97
- 'Read the current visible text from the active terminal in wmux',
98
- {},
99
- async () => callRpc('input.readScreen'),
100
- );
101
-
102
- server.tool(
103
- 'terminal_send',
104
- 'Send text to the active terminal in wmux',
105
- { text: z.string().describe('Text to send to the terminal') },
106
- async ({ text }) => callRpc('input.send', { text }),
107
- );
108
-
109
- server.tool(
110
- 'terminal_send_key',
111
- 'Send a named key to the active terminal (enter, tab, ctrl+c, ctrl+d, ctrl+z, ctrl+l, escape, up, down, right, left)',
112
- {
113
- key: z.string().describe(
114
- 'Key name: enter, tab, ctrl+c, ctrl+d, ctrl+z, ctrl+l, escape, up, down, right, left',
115
- ),
116
- },
117
- async ({ key }) => callRpc('input.sendKey', { key }),
118
- );
119
-
120
- // === Workspace tools ===
121
-
122
- server.tool(
123
- 'workspace_list',
124
- 'List all workspaces in wmux',
125
- {},
126
- async () => callRpc('workspace.list'),
127
- );
128
-
129
- server.tool(
130
- 'surface_list',
131
- 'List all surfaces (terminals and browsers) in the active workspace',
132
- {},
133
- async () => callRpc('surface.list'),
134
- );
135
-
136
- server.tool(
137
- 'pane_list',
138
- 'List all panes in the current workspace',
139
- {},
140
- async () => callRpc('pane.list'),
141
- );
142
-
143
- // === Start server ===
144
-
145
- async function main(): Promise<void> {
146
- const transport = new StdioServerTransport();
147
- await server.connect(transport);
148
- }
149
-
150
- main().catch((err) => {
151
- console.error('wmux MCP server failed to start:', err);
152
- process.exit(1);
153
- });
@@ -1,127 +0,0 @@
1
- import * as net from 'net';
2
- import * as fs from 'fs';
3
- import * as crypto from 'crypto';
4
- import type { RpcMethod, RpcResponse } from '../shared/rpc';
5
- import { getPipeName, getAuthTokenPath } from '../shared/constants';
6
-
7
- const TIMEOUT_MS = 10000;
8
- const RETRY_COUNT = 3;
9
- const RETRY_DELAY_MS = 1000;
10
-
11
- function readAuthToken(): string | undefined {
12
- // Env var takes priority (when running inside wmux terminal)
13
- if (process.env.WMUX_AUTH_TOKEN) return process.env.WMUX_AUTH_TOKEN;
14
- // File fallback (when spawned by Claude Code as MCP server)
15
- try {
16
- return fs.readFileSync(getAuthTokenPath(), 'utf8').trim();
17
- } catch {
18
- return undefined;
19
- }
20
- }
21
-
22
- function attemptRpc(
23
- pipePath: string,
24
- token: string,
25
- method: RpcMethod,
26
- params: Record<string, unknown>,
27
- ): Promise<unknown> {
28
- return new Promise((resolve, reject) => {
29
- const id = crypto.randomUUID();
30
- const request = JSON.stringify({ id, method, params, token }) + '\n';
31
-
32
- const socket = net.connect(pipePath);
33
- let buffer = '';
34
- let settled = false;
35
-
36
- const timer = setTimeout(() => {
37
- if (!settled) {
38
- settled = true;
39
- socket.destroy();
40
- reject(new Error(`RPC timeout: ${method} (${TIMEOUT_MS}ms)`));
41
- }
42
- }, TIMEOUT_MS);
43
-
44
- socket.on('connect', () => {
45
- socket.write(request);
46
- });
47
-
48
- socket.on('data', (chunk: Buffer) => {
49
- buffer += chunk.toString('utf8');
50
- const lines = buffer.split('\n');
51
- buffer = lines.pop() ?? '';
52
-
53
- for (const line of lines) {
54
- const trimmed = line.trim();
55
- if (!trimmed) continue;
56
- try {
57
- const response = JSON.parse(trimmed) as RpcResponse;
58
- if (response.id === id && !settled) {
59
- settled = true;
60
- clearTimeout(timer);
61
- socket.destroy();
62
- if (response.ok) {
63
- resolve(response.result);
64
- } else {
65
- reject(new Error(response.error));
66
- }
67
- }
68
- } catch {
69
- // ignore malformed lines
70
- }
71
- }
72
- });
73
-
74
- socket.on('error', (err: NodeJS.ErrnoException) => {
75
- if (!settled) {
76
- settled = true;
77
- clearTimeout(timer);
78
- if (err.code === 'ENOENT' || err.code === 'ECONNREFUSED') {
79
- reject(new Error('wmux is not running. Start the app first.'));
80
- } else {
81
- reject(new Error(`Connection error: ${err.message}`));
82
- }
83
- }
84
- });
85
-
86
- socket.on('close', () => {
87
- if (!settled) {
88
- settled = true;
89
- clearTimeout(timer);
90
- reject(new Error('Connection closed before response was received.'));
91
- }
92
- });
93
- });
94
- }
95
-
96
- function sleep(ms: number): Promise<void> {
97
- return new Promise((r) => setTimeout(r, ms));
98
- }
99
-
100
- export async function sendRpc(
101
- method: RpcMethod,
102
- params: Record<string, unknown> = {},
103
- ): Promise<unknown> {
104
- const pipePath = process.env.WMUX_SOCKET_PATH || getPipeName();
105
-
106
- for (let attempt = 0; attempt < RETRY_COUNT; attempt++) {
107
- // Re-read token on every attempt (wmux may have restarted with new token)
108
- const token = readAuthToken();
109
- if (!token) {
110
- throw new Error('wmux auth token not found. Is wmux running?');
111
- }
112
-
113
- try {
114
- return await attemptRpc(pipePath, token, method, params);
115
- } catch (err) {
116
- const msg = (err as Error).message;
117
- const isRetryable = msg.includes('not running') || msg.includes('unauthorized');
118
- if (isRetryable && attempt < RETRY_COUNT - 1) {
119
- await sleep(RETRY_DELAY_MS);
120
- continue;
121
- }
122
- throw err;
123
- }
124
- }
125
-
126
- throw new Error('wmux is not running. Start the app first.');
127
- }
@@ -1,111 +0,0 @@
1
- import { contextBridge, ipcRenderer } from 'electron';
2
- import { IPC } from '../shared/constants';
3
-
4
- const electronAPI = {
5
- pty: {
6
- create: (options?: { shell?: string; cwd?: string; cols?: number; rows?: number }) =>
7
- ipcRenderer.invoke(IPC.PTY_CREATE, options),
8
- write: (id: string, data: string) =>
9
- ipcRenderer.invoke(IPC.PTY_WRITE, id, data),
10
- resize: (id: string, cols: number, rows: number) =>
11
- ipcRenderer.invoke(IPC.PTY_RESIZE, id, cols, rows),
12
- dispose: (id: string) =>
13
- ipcRenderer.invoke(IPC.PTY_DISPOSE, id),
14
- onData: (callback: (id: string, data: string) => void) => {
15
- const listener = (_event: Electron.IpcRendererEvent, id: string, data: string) => callback(id, data);
16
- ipcRenderer.on(IPC.PTY_DATA, listener);
17
- return () => { ipcRenderer.removeListener(IPC.PTY_DATA, listener); };
18
- },
19
- onExit: (callback: (id: string, exitCode: number) => void) => {
20
- const listener = (_event: Electron.IpcRendererEvent, id: string, exitCode: number) => callback(id, exitCode);
21
- ipcRenderer.on(IPC.PTY_EXIT, listener);
22
- return () => { ipcRenderer.removeListener(IPC.PTY_EXIT, listener); };
23
- },
24
- },
25
- shell: {
26
- list: () => ipcRenderer.invoke(IPC.SHELL_LIST) as Promise<{ name: string; path: string; args?: string[] }[]>,
27
- },
28
- session: {
29
- save: (data: unknown) => ipcRenderer.invoke(IPC.SESSION_SAVE, data),
30
- load: () => ipcRenderer.invoke(IPC.SESSION_LOAD),
31
- },
32
- settings: {
33
- setToastEnabled: (enabled: boolean) => ipcRenderer.send(IPC.TOAST_ENABLED, enabled),
34
- },
35
- notification: {
36
- onNew: (callback: (ptyId: string, data: { type: string; title: string; body: string }) => void) => {
37
- const listener = (_event: Electron.IpcRendererEvent, ptyId: string, data: { type: string; title: string; body: string }) =>
38
- callback(ptyId, data);
39
- ipcRenderer.on(IPC.NOTIFICATION, listener);
40
- return () => { ipcRenderer.removeListener(IPC.NOTIFICATION, listener); };
41
- },
42
- onCwdChanged: (callback: (ptyId: string, cwd: string) => void) => {
43
- const listener = (_event: Electron.IpcRendererEvent, ptyId: string, cwd: string) =>
44
- callback(ptyId, cwd);
45
- ipcRenderer.on(IPC.CWD_CHANGED, listener);
46
- return () => { ipcRenderer.removeListener(IPC.CWD_CHANGED, listener); };
47
- },
48
- },
49
- metadata: {
50
- request: (ptyId: string) =>
51
- ipcRenderer.invoke(IPC.METADATA_REQUEST, ptyId),
52
- onUpdate: (callback: (ptyId: string, data: { gitBranch?: string; cwd?: string; listeningPorts?: number[] }) => void) => {
53
- const listener = (_event: Electron.IpcRendererEvent, ptyId: string, data: { gitBranch?: string; cwd?: string; listeningPorts?: number[] }) =>
54
- callback(ptyId, data);
55
- ipcRenderer.on(IPC.METADATA_UPDATE, listener);
56
- return () => { ipcRenderer.removeListener(IPC.METADATA_UPDATE, listener); };
57
- },
58
- },
59
- rpc: {
60
- onCommand: (
61
- callback: (requestId: string, method: string, params: Record<string, unknown>) => void,
62
- ) => {
63
- const listener = (
64
- _event: Electron.IpcRendererEvent,
65
- requestId: string,
66
- method: string,
67
- params: Record<string, unknown>,
68
- ) => callback(requestId, method, params);
69
- ipcRenderer.on(IPC.RPC_COMMAND, listener);
70
- return () => { ipcRenderer.removeListener(IPC.RPC_COMMAND, listener); };
71
- },
72
- respond: (requestId: string, result: unknown) =>
73
- ipcRenderer.send(`${IPC.RPC_RESPONSE}:${requestId}`, result),
74
- },
75
- updater: {
76
- checkForUpdates: () =>
77
- ipcRenderer.invoke(IPC.UPDATE_CHECK) as Promise<{ status: string }>,
78
- installUpdate: () =>
79
- ipcRenderer.invoke(IPC.UPDATE_INSTALL),
80
- onUpdateAvailable: (callback: (data: { status: string; releaseName?: string }) => void) => {
81
- const listener = (_event: Electron.IpcRendererEvent, data: { status: string; releaseName?: string }) =>
82
- callback(data);
83
- ipcRenderer.on(IPC.UPDATE_AVAILABLE, listener);
84
- return () => { ipcRenderer.removeListener(IPC.UPDATE_AVAILABLE, listener); };
85
- },
86
- onUpdateNotAvailable: (callback: (data: { status: string }) => void) => {
87
- const listener = (_event: Electron.IpcRendererEvent, data: { status: string }) =>
88
- callback(data);
89
- ipcRenderer.on(IPC.UPDATE_NOT_AVAILABLE, listener);
90
- return () => { ipcRenderer.removeListener(IPC.UPDATE_NOT_AVAILABLE, listener); };
91
- },
92
- onUpdateError: (callback: (data: { status: string; message: string }) => void) => {
93
- const listener = (_event: Electron.IpcRendererEvent, data: { status: string; message: string }) =>
94
- callback(data);
95
- ipcRenderer.on(IPC.UPDATE_ERROR, listener);
96
- return () => { ipcRenderer.removeListener(IPC.UPDATE_ERROR, listener); };
97
- },
98
- },
99
- };
100
-
101
- contextBridge.exposeInMainWorld('electronAPI', electronAPI);
102
-
103
- // Expose clipboard via IPC for reliable copy/paste in terminal
104
- const clipboardAPI = {
105
- writeText: (text: string) => ipcRenderer.invoke(IPC.CLIPBOARD_WRITE, text),
106
- readText: () => ipcRenderer.invoke(IPC.CLIPBOARD_READ) as Promise<string>,
107
- };
108
- contextBridge.exposeInMainWorld('clipboardAPI', clipboardAPI);
109
-
110
- export type ElectronAPI = typeof electronAPI;
111
- export type ClipboardAPI = typeof clipboardAPI;