@wong2kim/wmux 1.0.0

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 (122) hide show
  1. package/README.md +157 -0
  2. package/assets/icon.ico +0 -0
  3. package/assets/icon.svg +6 -0
  4. package/dist/cli/cli/client.js +102 -0
  5. package/dist/cli/cli/commands/browser.js +137 -0
  6. package/dist/cli/cli/commands/input.js +80 -0
  7. package/dist/cli/cli/commands/notify.js +28 -0
  8. package/dist/cli/cli/commands/pane.js +88 -0
  9. package/dist/cli/cli/commands/surface.js +98 -0
  10. package/dist/cli/cli/commands/system.js +98 -0
  11. package/dist/cli/cli/commands/workspace.js +117 -0
  12. package/dist/cli/cli/index.js +140 -0
  13. package/dist/cli/cli/utils.js +47 -0
  14. package/dist/cli/shared/constants.js +54 -0
  15. package/dist/cli/shared/rpc.js +33 -0
  16. package/dist/cli/shared/types.js +79 -0
  17. package/dist/mcp/mcp/index.js +60 -0
  18. package/dist/mcp/mcp/wmux-client.js +146 -0
  19. package/dist/mcp/shared/constants.js +54 -0
  20. package/dist/mcp/shared/rpc.js +33 -0
  21. package/dist/mcp/shared/types.js +79 -0
  22. package/forge.config.ts +61 -0
  23. package/index.html +12 -0
  24. package/package.json +84 -0
  25. package/postcss.config.js +6 -0
  26. package/src/cli/client.ts +76 -0
  27. package/src/cli/commands/browser.ts +128 -0
  28. package/src/cli/commands/input.ts +72 -0
  29. package/src/cli/commands/notify.ts +29 -0
  30. package/src/cli/commands/pane.ts +90 -0
  31. package/src/cli/commands/surface.ts +102 -0
  32. package/src/cli/commands/system.ts +95 -0
  33. package/src/cli/commands/workspace.ts +116 -0
  34. package/src/cli/index.ts +145 -0
  35. package/src/cli/utils.ts +44 -0
  36. package/src/main/index.ts +86 -0
  37. package/src/main/ipc/handlers/clipboard.handler.ts +20 -0
  38. package/src/main/ipc/handlers/metadata.handler.ts +56 -0
  39. package/src/main/ipc/handlers/pty.handler.ts +69 -0
  40. package/src/main/ipc/handlers/session.handler.ts +17 -0
  41. package/src/main/ipc/handlers/shell.handler.ts +11 -0
  42. package/src/main/ipc/registerHandlers.ts +31 -0
  43. package/src/main/mcp/McpRegistrar.ts +156 -0
  44. package/src/main/metadata/MetadataCollector.ts +58 -0
  45. package/src/main/notification/ToastManager.ts +32 -0
  46. package/src/main/pipe/PipeServer.ts +190 -0
  47. package/src/main/pipe/RpcRouter.ts +46 -0
  48. package/src/main/pipe/handlers/_bridge.ts +40 -0
  49. package/src/main/pipe/handlers/browser.rpc.ts +132 -0
  50. package/src/main/pipe/handlers/input.rpc.ts +120 -0
  51. package/src/main/pipe/handlers/meta.rpc.ts +59 -0
  52. package/src/main/pipe/handlers/notify.rpc.ts +53 -0
  53. package/src/main/pipe/handlers/pane.rpc.ts +39 -0
  54. package/src/main/pipe/handlers/surface.rpc.ts +43 -0
  55. package/src/main/pipe/handlers/system.rpc.ts +36 -0
  56. package/src/main/pipe/handlers/workspace.rpc.ts +52 -0
  57. package/src/main/pty/AgentDetector.ts +247 -0
  58. package/src/main/pty/OscParser.ts +81 -0
  59. package/src/main/pty/PTYBridge.ts +88 -0
  60. package/src/main/pty/PTYManager.ts +104 -0
  61. package/src/main/pty/ShellDetector.ts +63 -0
  62. package/src/main/session/SessionManager.ts +53 -0
  63. package/src/main/updater/AutoUpdater.ts +132 -0
  64. package/src/main/window/createWindow.ts +71 -0
  65. package/src/mcp/README.md +56 -0
  66. package/src/mcp/index.ts +153 -0
  67. package/src/mcp/wmux-client.ts +127 -0
  68. package/src/preload/index.ts +111 -0
  69. package/src/preload/preload.ts +108 -0
  70. package/src/renderer/App.tsx +5 -0
  71. package/src/renderer/components/Browser/BrowserPanel.tsx +219 -0
  72. package/src/renderer/components/Browser/BrowserToolbar.tsx +253 -0
  73. package/src/renderer/components/Company/ApprovalDialog.tsx +3 -0
  74. package/src/renderer/components/Company/CompanyView.tsx +7 -0
  75. package/src/renderer/components/Company/MessageFeedPanel.tsx +3 -0
  76. package/src/renderer/components/Layout/AppLayout.tsx +234 -0
  77. package/src/renderer/components/Notification/NotificationPanel.tsx +129 -0
  78. package/src/renderer/components/Palette/CommandPalette.tsx +409 -0
  79. package/src/renderer/components/Palette/PaletteItem.tsx +55 -0
  80. package/src/renderer/components/Pane/Pane.tsx +122 -0
  81. package/src/renderer/components/Pane/PaneContainer.tsx +41 -0
  82. package/src/renderer/components/Pane/SurfaceTabs.tsx +46 -0
  83. package/src/renderer/components/Settings/SettingsPanel.tsx +886 -0
  84. package/src/renderer/components/Sidebar/MiniSidebar.tsx +67 -0
  85. package/src/renderer/components/Sidebar/Sidebar.tsx +84 -0
  86. package/src/renderer/components/Sidebar/WorkspaceItem.tsx +241 -0
  87. package/src/renderer/components/StatusBar/StatusBar.tsx +93 -0
  88. package/src/renderer/components/Terminal/SearchBar.tsx +126 -0
  89. package/src/renderer/components/Terminal/Terminal.tsx +102 -0
  90. package/src/renderer/components/Terminal/ViCopyMode.tsx +104 -0
  91. package/src/renderer/hooks/useKeyboard.ts +310 -0
  92. package/src/renderer/hooks/useNotificationListener.ts +80 -0
  93. package/src/renderer/hooks/useNotificationSound.ts +75 -0
  94. package/src/renderer/hooks/useRpcBridge.ts +451 -0
  95. package/src/renderer/hooks/useT.ts +11 -0
  96. package/src/renderer/hooks/useTerminal.ts +349 -0
  97. package/src/renderer/hooks/useViCopyMode.ts +320 -0
  98. package/src/renderer/i18n/index.ts +69 -0
  99. package/src/renderer/i18n/locales/en.ts +157 -0
  100. package/src/renderer/i18n/locales/ja.ts +155 -0
  101. package/src/renderer/i18n/locales/ko.ts +155 -0
  102. package/src/renderer/i18n/locales/zh.ts +155 -0
  103. package/src/renderer/index.tsx +6 -0
  104. package/src/renderer/stores/index.ts +19 -0
  105. package/src/renderer/stores/slices/notificationSlice.ts +56 -0
  106. package/src/renderer/stores/slices/paneSlice.ts +141 -0
  107. package/src/renderer/stores/slices/surfaceSlice.ts +122 -0
  108. package/src/renderer/stores/slices/uiSlice.ts +247 -0
  109. package/src/renderer/stores/slices/workspaceSlice.ts +120 -0
  110. package/src/renderer/styles/globals.css +150 -0
  111. package/src/renderer/themes.ts +99 -0
  112. package/src/shared/constants.ts +53 -0
  113. package/src/shared/electron.d.ts +11 -0
  114. package/src/shared/rpc.ts +71 -0
  115. package/src/shared/types.ts +176 -0
  116. package/tailwind.config.js +11 -0
  117. package/tsconfig.cli.json +24 -0
  118. package/tsconfig.json +21 -0
  119. package/tsconfig.mcp.json +25 -0
  120. package/vite.main.config.ts +14 -0
  121. package/vite.preload.config.ts +9 -0
  122. package/vite.renderer.config.ts +6 -0
@@ -0,0 +1,247 @@
1
+ import type { StateCreator } from 'zustand';
2
+ import type { StoreState } from '../index';
3
+ import { setLocale as i18nSetLocale, type Locale } from '../../i18n';
4
+ import { generateId, type CustomKeybinding } from '../../../shared/types';
5
+
6
+ export interface UISlice {
7
+ sidebarVisible: boolean;
8
+ toggleSidebar: () => void;
9
+ setSidebarVisible: (visible: boolean) => void;
10
+
11
+ notificationPanelVisible: boolean;
12
+ toggleNotificationPanel: () => void;
13
+ setNotificationPanelVisible: (visible: boolean) => void;
14
+
15
+ commandPaletteVisible: boolean;
16
+ toggleCommandPalette: () => void;
17
+ setCommandPaletteVisible: (visible: boolean) => void;
18
+
19
+ settingsPanelVisible: boolean;
20
+ toggleSettingsPanel: () => void;
21
+ setSettingsPanelVisible: (visible: boolean) => void;
22
+
23
+ notificationSoundEnabled: boolean;
24
+ toggleNotificationSound: () => void;
25
+ setNotificationSoundEnabled: (enabled: boolean) => void;
26
+
27
+ locale: Locale;
28
+ setLocale: (locale: Locale) => void;
29
+
30
+ viCopyModeActive: boolean;
31
+ setViCopyModeActive: (active: boolean) => void;
32
+
33
+ searchBarVisible: boolean;
34
+ toggleSearchBar: () => void;
35
+ setSearchBarVisible: (visible: boolean) => void;
36
+
37
+ // ─── Terminal settings ───────────────────────────────────────────────────
38
+ terminalFontSize: number;
39
+ setTerminalFontSize: (size: number) => void;
40
+
41
+ terminalFontFamily: string;
42
+ setTerminalFontFamily: (family: string) => void;
43
+
44
+ defaultShell: string;
45
+ setDefaultShell: (shell: string) => void;
46
+
47
+ scrollbackLines: number;
48
+ setScrollbackLines: (lines: number) => void;
49
+
50
+ // ─── Theme ──────────────────────────────────────────────────────────────
51
+ theme: string;
52
+ setTheme: (theme: string) => void;
53
+
54
+ // ─── Layout ────────────────────────────────────────────────────────────
55
+ sidebarPosition: 'left' | 'right';
56
+ setSidebarPosition: (position: 'left' | 'right') => void;
57
+
58
+ // ─── Toast / ring notification UI ────────────────────────────────────────
59
+ toastEnabled: boolean;
60
+ setToastEnabled: (enabled: boolean) => void;
61
+
62
+ notificationRingEnabled: boolean;
63
+ setNotificationRingEnabled: (enabled: boolean) => void;
64
+
65
+ // ─── Custom keybindings ──────────────────────────────────────────────
66
+ customKeybindings: CustomKeybinding[];
67
+ addKeybinding: (kb: Omit<CustomKeybinding, 'id'>) => void;
68
+ updateKeybinding: (id: string, kb: Partial<Omit<CustomKeybinding, 'id'>>) => void;
69
+ removeKeybinding: (id: string) => void;
70
+
71
+ }
72
+
73
+ export const createUISlice: StateCreator<StoreState, [['zustand/immer', never]], [], UISlice> = (set) => ({
74
+ // ─── Sidebar ─────────────────────────────────────────────────────────────
75
+ sidebarVisible: true,
76
+
77
+ toggleSidebar: () => set((state) => {
78
+ state.sidebarVisible = !state.sidebarVisible;
79
+ }),
80
+
81
+ setSidebarVisible: (visible) => set((state) => {
82
+ state.sidebarVisible = visible;
83
+ }),
84
+
85
+ // ─── Notification panel ──────────────────────────────────────────────────
86
+ notificationPanelVisible: false,
87
+
88
+ toggleNotificationPanel: () => set((state) => {
89
+ state.notificationPanelVisible = !state.notificationPanelVisible;
90
+ if (state.notificationPanelVisible) {
91
+ state.commandPaletteVisible = false;
92
+ state.settingsPanelVisible = false;
93
+ }
94
+ }),
95
+
96
+ setNotificationPanelVisible: (visible) => set((state) => {
97
+ state.notificationPanelVisible = visible;
98
+ }),
99
+
100
+ // ─── Command palette ─────────────────────────────────────────────────────
101
+ commandPaletteVisible: false,
102
+
103
+ toggleCommandPalette: () => set((state) => {
104
+ state.commandPaletteVisible = !state.commandPaletteVisible;
105
+ if (state.commandPaletteVisible) {
106
+ state.notificationPanelVisible = false;
107
+ state.settingsPanelVisible = false;
108
+ }
109
+ }),
110
+
111
+ setCommandPaletteVisible: (visible) => set((state) => {
112
+ state.commandPaletteVisible = visible;
113
+ }),
114
+
115
+ // ─── Settings panel ──────────────────────────────────────────────────────
116
+ settingsPanelVisible: false,
117
+
118
+ toggleSettingsPanel: () => set((state) => {
119
+ state.settingsPanelVisible = !state.settingsPanelVisible;
120
+ if (state.settingsPanelVisible) {
121
+ state.commandPaletteVisible = false;
122
+ state.notificationPanelVisible = false;
123
+ }
124
+ }),
125
+
126
+ setSettingsPanelVisible: (visible) => set((state) => {
127
+ state.settingsPanelVisible = visible;
128
+ }),
129
+
130
+ // ─── Notification sound ──────────────────────────────────────────────────
131
+ notificationSoundEnabled: true,
132
+
133
+ toggleNotificationSound: () => set((state) => {
134
+ state.notificationSoundEnabled = !state.notificationSoundEnabled;
135
+ }),
136
+
137
+ setNotificationSoundEnabled: (enabled) => set((state) => {
138
+ state.notificationSoundEnabled = enabled;
139
+ }),
140
+
141
+ // ─── Locale / i18n ───────────────────────────────────────────────────────
142
+ locale: 'en',
143
+
144
+ setLocale: (locale) => {
145
+ // Sync i18n module state immediately (outside immer — pure function call)
146
+ i18nSetLocale(locale);
147
+ set((state) => {
148
+ state.locale = locale;
149
+ });
150
+ },
151
+
152
+ // ─── VI copy mode ─────────────────────────────────────────────────────────
153
+ viCopyModeActive: false,
154
+
155
+ setViCopyModeActive: (active) => set((state) => {
156
+ state.viCopyModeActive = active;
157
+ }),
158
+
159
+ // ─── Search bar ───────────────────────────────────────────────────────────
160
+ searchBarVisible: false,
161
+
162
+ toggleSearchBar: () => set((state) => {
163
+ state.searchBarVisible = !state.searchBarVisible;
164
+ }),
165
+
166
+ setSearchBarVisible: (visible) => set((state) => {
167
+ state.searchBarVisible = visible;
168
+ }),
169
+
170
+ // ─── Terminal settings ───────────────────────────────────────────────────
171
+ terminalFontSize: 14,
172
+
173
+ setTerminalFontSize: (size) => set((state) => {
174
+ state.terminalFontSize = size;
175
+ }),
176
+
177
+ terminalFontFamily: 'Cascadia Code',
178
+
179
+ setTerminalFontFamily: (family) => set((state) => {
180
+ state.terminalFontFamily = family;
181
+ }),
182
+
183
+ defaultShell: 'powershell',
184
+
185
+ setDefaultShell: (shell) => set((state) => {
186
+ state.defaultShell = shell;
187
+ }),
188
+
189
+ scrollbackLines: 10000,
190
+
191
+ setScrollbackLines: (lines) => set((state) => {
192
+ state.scrollbackLines = lines;
193
+ }),
194
+
195
+ // ─── Theme ──────────────────────────────────────────────────────────────
196
+ theme: 'catppuccin',
197
+
198
+ setTheme: (theme) => {
199
+ document.documentElement.setAttribute('data-theme', theme);
200
+ set((state) => {
201
+ state.theme = theme;
202
+ });
203
+ },
204
+
205
+ // ─── Layout ────────────────────────────────────────────────────────────
206
+ sidebarPosition: 'left',
207
+
208
+ setSidebarPosition: (position) => set((state) => {
209
+ state.sidebarPosition = position;
210
+ }),
211
+
212
+ // ─── Toast / ring notification UI ────────────────────────────────────────
213
+ toastEnabled: true,
214
+
215
+ setToastEnabled: (enabled) => {
216
+ window.electronAPI.settings.setToastEnabled(enabled);
217
+ set((state) => {
218
+ state.toastEnabled = enabled;
219
+ });
220
+ },
221
+
222
+ notificationRingEnabled: true,
223
+
224
+ setNotificationRingEnabled: (enabled) => set((state) => {
225
+ state.notificationRingEnabled = enabled;
226
+ }),
227
+
228
+ // ─── Custom keybindings ──────────────────────────────────────────────
229
+ customKeybindings: [],
230
+
231
+ addKeybinding: (kb) => set((state) => {
232
+ state.customKeybindings.push({
233
+ id: generateId('kb'),
234
+ ...kb,
235
+ });
236
+ }),
237
+
238
+ updateKeybinding: (id, updates) => set((state) => {
239
+ const idx = state.customKeybindings.findIndex((k) => k.id === id);
240
+ if (idx !== -1) Object.assign(state.customKeybindings[idx], updates);
241
+ }),
242
+
243
+ removeKeybinding: (id) => set((state) => {
244
+ state.customKeybindings = state.customKeybindings.filter((k) => k.id !== id);
245
+ }),
246
+
247
+ });
@@ -0,0 +1,120 @@
1
+ import type { StateCreator } from 'zustand';
2
+ import type { StoreState } from '../index';
3
+ import { createWorkspace, type Pane, type SessionData, type Workspace, type WorkspaceMetadata } from '../../../shared/types';
4
+ import { setLocale as i18nSetLocale, type Locale } from '../../i18n';
5
+
6
+ export interface WorkspaceSlice {
7
+ workspaces: Workspace[];
8
+ activeWorkspaceId: string;
9
+ addWorkspace: (name?: string) => void;
10
+ removeWorkspace: (id: string) => void;
11
+ setActiveWorkspace: (id: string) => void;
12
+ renameWorkspace: (id: string, name: string) => void;
13
+ updateWorkspaceMetadata: (id: string, metadata: Partial<WorkspaceMetadata>) => void;
14
+ reorderWorkspace: (fromIndex: number, toIndex: number) => void;
15
+ loadSession: (data: SessionData) => void;
16
+ }
17
+
18
+ export const createWorkspaceSlice: StateCreator<StoreState, [['zustand/immer', never]], [], WorkspaceSlice> = (set) => {
19
+ const initial = createWorkspace('Workspace 1');
20
+ return {
21
+ workspaces: [initial],
22
+ activeWorkspaceId: initial.id,
23
+
24
+ addWorkspace: (name) => set((state: StoreState) => {
25
+ const ws = createWorkspace(name || `Workspace ${state.workspaces.length + 1}`);
26
+ state.workspaces.push(ws);
27
+ state.activeWorkspaceId = ws.id;
28
+ }),
29
+
30
+ // NOTE: PTY cleanup is the caller's responsibility (see Sidebar.handleClose, useKeyboard Ctrl+Shift+W)
31
+ removeWorkspace: (id) => set((state: StoreState) => {
32
+ if (state.workspaces.length <= 1) return;
33
+ const idx = state.workspaces.findIndex((w: Workspace) => w.id === id);
34
+ if (idx === -1) return;
35
+ state.workspaces.splice(idx, 1);
36
+ if (state.activeWorkspaceId === id) {
37
+ state.activeWorkspaceId = state.workspaces[Math.min(idx, state.workspaces.length - 1)].id;
38
+ }
39
+ }),
40
+
41
+ setActiveWorkspace: (id) => set((state: StoreState) => {
42
+ if (state.workspaces.some((w: Workspace) => w.id === id)) {
43
+ state.activeWorkspaceId = id;
44
+ }
45
+ }),
46
+
47
+ renameWorkspace: (id, name) => set((state: StoreState) => {
48
+ const ws = state.workspaces.find((w: Workspace) => w.id === id);
49
+ if (ws) ws.name = name;
50
+ }),
51
+
52
+ updateWorkspaceMetadata: (id, metadata) => set((state: StoreState) => {
53
+ const ws = state.workspaces.find((w: Workspace) => w.id === id);
54
+ if (ws) {
55
+ if (!ws.metadata) ws.metadata = {};
56
+ Object.assign(ws.metadata, metadata);
57
+ }
58
+ }),
59
+
60
+ reorderWorkspace: (fromIndex, toIndex) => set((state: StoreState) => {
61
+ if (fromIndex === toIndex) return;
62
+ if (fromIndex < 0 || fromIndex >= state.workspaces.length) return;
63
+ if (toIndex < 0 || toIndex >= state.workspaces.length) return;
64
+ const [removed] = state.workspaces.splice(fromIndex, 1);
65
+ state.workspaces.splice(toIndex, 0, removed);
66
+ }),
67
+
68
+ loadSession: (data: SessionData) => set((state: StoreState) => {
69
+ if (!data.workspaces || data.workspaces.length === 0) return;
70
+
71
+ // Security: sanitize surfaces — clear ptyIds and block dangerous URLs
72
+ const BLOCKED_URL_SCHEMES = ['javascript:', 'data:', 'vbscript:', 'file:'];
73
+ const sanitizePanes = (pane: Pane) => {
74
+ if (pane.type === 'leaf') {
75
+ for (const s of pane.surfaces) {
76
+ if (s.surfaceType !== 'browser') {
77
+ s.ptyId = '';
78
+ }
79
+ // Strip dangerous browserUrl schemes that could execute code on load
80
+ if (s.browserUrl) {
81
+ const normalized = s.browserUrl.trim().toLowerCase();
82
+ if (BLOCKED_URL_SCHEMES.some((scheme) => normalized.startsWith(scheme))) {
83
+ s.browserUrl = 'about:blank';
84
+ }
85
+ }
86
+ }
87
+ } else {
88
+ for (const child of pane.children) sanitizePanes(child);
89
+ }
90
+ };
91
+ for (const ws of data.workspaces) sanitizePanes(ws.rootPane);
92
+
93
+ state.workspaces = data.workspaces;
94
+ state.activeWorkspaceId = data.activeWorkspaceId;
95
+ state.sidebarVisible = data.sidebarVisible;
96
+
97
+ // Restore user preferences
98
+ if (data.theme) {
99
+ state.theme = data.theme;
100
+ document.documentElement.setAttribute('data-theme', data.theme);
101
+ }
102
+ if (data.locale) {
103
+ state.locale = data.locale as Locale;
104
+ i18nSetLocale(data.locale as Locale);
105
+ }
106
+ if (data.terminalFontSize != null) state.terminalFontSize = data.terminalFontSize;
107
+ if (data.terminalFontFamily) state.terminalFontFamily = data.terminalFontFamily;
108
+ if (data.defaultShell) state.defaultShell = data.defaultShell;
109
+ if (data.scrollbackLines != null) state.scrollbackLines = data.scrollbackLines;
110
+ if (data.sidebarPosition) state.sidebarPosition = data.sidebarPosition;
111
+ if (data.notificationSoundEnabled != null) state.notificationSoundEnabled = data.notificationSoundEnabled;
112
+ if (data.toastEnabled != null) {
113
+ state.toastEnabled = data.toastEnabled;
114
+ window.electronAPI.settings.setToastEnabled(data.toastEnabled);
115
+ }
116
+ if (data.notificationRingEnabled != null) state.notificationRingEnabled = data.notificationRingEnabled;
117
+ if (data.customKeybindings) state.customKeybindings = data.customKeybindings;
118
+ }),
119
+ };
120
+ };
@@ -0,0 +1,150 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ /* ─── Theme CSS Variables ────────────────────────────────────────────────── */
6
+
7
+ :root, [data-theme="catppuccin"] {
8
+ --bg-base: #1e1e2e;
9
+ --bg-mantle: #181825;
10
+ --bg-surface: #313244;
11
+ --bg-overlay: #45475a;
12
+ --text-muted: #585b70;
13
+ --text-subtle: #6c7086;
14
+ --text-sub: #bac2de;
15
+ --text-sub2: #a6adc8;
16
+ --text-main: #cdd6f4;
17
+ --accent-cursor: #f5e0dc;
18
+ --accent-blue: #89b4fa;
19
+ --accent-green: #a6e3a1;
20
+ --accent-red: #f38ba8;
21
+ --accent-yellow: #f9e2af;
22
+ --accent-pink: #f5c2e7;
23
+ --accent-teal: #94e2d5;
24
+ --accent-purple: #cba6f7;
25
+ /* RGB variants for opacity usage */
26
+ --accent-blue-rgb: 137, 180, 250;
27
+ --bg-surface-rgb: 49, 52, 68;
28
+ --bg-base-rgb: 30, 30, 46;
29
+ }
30
+
31
+ [data-theme="monochrome"] {
32
+ --bg-base: #080808;
33
+ --bg-mantle: #050505;
34
+ --bg-surface: #1c1c1c;
35
+ --bg-overlay: #2e2e2e;
36
+ --text-muted: #555555;
37
+ --text-subtle: #777777;
38
+ --text-sub: #aaaaaa;
39
+ --text-sub2: #999999;
40
+ --text-main: #e0e0e0;
41
+ --accent-cursor: #ffffff;
42
+ --accent-blue: #b0b0b0;
43
+ --accent-green: #d0d0d0;
44
+ --accent-red: #ff5555;
45
+ --accent-yellow: #cccccc;
46
+ --accent-pink: #c0c0c0;
47
+ --accent-teal: #b8b8b8;
48
+ --accent-purple: #b8b8b8;
49
+ --accent-blue-rgb: 176, 176, 176;
50
+ --bg-surface-rgb: 28, 28, 28;
51
+ --bg-base-rgb: 8, 8, 8;
52
+ }
53
+
54
+ [data-theme="claude"] {
55
+ --bg-base: #F5F0E8;
56
+ --bg-mantle: #EDE6D9;
57
+ --bg-surface: #E0D8CA;
58
+ --bg-overlay: #D5CCBC;
59
+ --text-muted: #B8A898;
60
+ --text-subtle: #9A8C7C;
61
+ --text-sub: #6B5D4F;
62
+ --text-sub2: #7A6E60;
63
+ --text-main: #3D3429;
64
+ --accent-cursor: #3D3429;
65
+ --accent-blue: #DA7756;
66
+ --accent-green: #5A8A4C;
67
+ --accent-red: #C4533A;
68
+ --accent-yellow: #B8912D;
69
+ --accent-pink: #B5647A;
70
+ --accent-teal: #4A9588;
71
+ --accent-purple: #7A6AAF;
72
+ --accent-blue-rgb: 218, 119, 86;
73
+ --bg-surface-rgb: 224, 216, 202;
74
+ --bg-base-rgb: 245, 240, 232;
75
+ }
76
+
77
+ /* ─── Base ────────────────────────────────────────────────────────────────── */
78
+
79
+ * {
80
+ margin: 0;
81
+ padding: 0;
82
+ box-sizing: border-box;
83
+ }
84
+
85
+ html, body, #root {
86
+ height: 100%;
87
+ width: 100%;
88
+ overflow: hidden;
89
+ background-color: var(--bg-base);
90
+ color: var(--text-main);
91
+ font-family: 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif;
92
+ }
93
+
94
+ /* Custom scrollbar */
95
+ ::-webkit-scrollbar {
96
+ width: 6px;
97
+ height: 6px;
98
+ }
99
+ ::-webkit-scrollbar-track {
100
+ background: transparent;
101
+ }
102
+ ::-webkit-scrollbar-thumb {
103
+ background: var(--bg-surface);
104
+ border-radius: 3px;
105
+ }
106
+ ::-webkit-scrollbar-thumb:hover {
107
+ background: var(--bg-overlay);
108
+ }
109
+ ::-webkit-scrollbar-corner {
110
+ background: transparent;
111
+ }
112
+
113
+ /* Notification ring pulse on inactive panes with unread notifications */
114
+ @keyframes notification-ring-pulse {
115
+ 0%, 100% { box-shadow: 0 0 0 0 rgba(var(--accent-blue-rgb), 0.4); }
116
+ 50% { box-shadow: 0 0 0 3px rgba(var(--accent-blue-rgb), 0.2); }
117
+ }
118
+
119
+ .notification-ring {
120
+ animation: notification-ring-pulse 2s ease-in-out infinite;
121
+ }
122
+
123
+ /* Notification panel slide-in */
124
+ @keyframes notification-slide-in {
125
+ from { transform: translateX(100%); }
126
+ to { transform: translateX(0); }
127
+ }
128
+
129
+ .notification-panel-enter {
130
+ animation: notification-slide-in 0.2s ease-out;
131
+ }
132
+
133
+ /* Pane flash highlight — Ctrl+Shift+H */
134
+ @keyframes pane-flash {
135
+ 0% { opacity: 0; }
136
+ 15% { opacity: 0.35; }
137
+ 70% { opacity: 0.25; }
138
+ 100% { opacity: 0; }
139
+ }
140
+
141
+ .pane-flash::after {
142
+ content: '';
143
+ position: absolute;
144
+ inset: 0;
145
+ border-radius: 2px;
146
+ background: var(--accent-blue);
147
+ pointer-events: none;
148
+ animation: pane-flash 0.5s ease-out forwards;
149
+ z-index: 100;
150
+ }
@@ -0,0 +1,99 @@
1
+ export type ThemeId = 'catppuccin' | 'monochrome' | 'claude';
2
+
3
+ export interface XtermThemeColors {
4
+ background: string;
5
+ foreground: string;
6
+ cursor: string;
7
+ selectionBackground: string;
8
+ black: string;
9
+ red: string;
10
+ green: string;
11
+ yellow: string;
12
+ blue: string;
13
+ magenta: string;
14
+ cyan: string;
15
+ white: string;
16
+ brightBlack: string;
17
+ brightRed: string;
18
+ brightGreen: string;
19
+ brightYellow: string;
20
+ brightBlue: string;
21
+ brightMagenta: string;
22
+ brightCyan: string;
23
+ brightWhite: string;
24
+ }
25
+
26
+ export const XTERM_THEMES: Record<ThemeId, XtermThemeColors> = {
27
+ catppuccin: {
28
+ background: '#1e1e2e',
29
+ foreground: '#cdd6f4',
30
+ cursor: '#f5e0dc',
31
+ selectionBackground: '#585b70',
32
+ black: '#45475a',
33
+ red: '#f38ba8',
34
+ green: '#a6e3a1',
35
+ yellow: '#f9e2af',
36
+ blue: '#89b4fa',
37
+ magenta: '#f5c2e7',
38
+ cyan: '#94e2d5',
39
+ white: '#bac2de',
40
+ brightBlack: '#585b70',
41
+ brightRed: '#f38ba8',
42
+ brightGreen: '#a6e3a1',
43
+ brightYellow: '#f9e2af',
44
+ brightBlue: '#89b4fa',
45
+ brightMagenta: '#f5c2e7',
46
+ brightCyan: '#94e2d5',
47
+ brightWhite: '#a6adc8',
48
+ },
49
+ monochrome: {
50
+ background: '#080808',
51
+ foreground: '#e0e0e0',
52
+ cursor: '#ffffff',
53
+ selectionBackground: '#333333',
54
+ black: '#2e2e2e',
55
+ red: '#ff5555',
56
+ green: '#d0d0d0',
57
+ yellow: '#cccccc',
58
+ blue: '#b0b0b0',
59
+ magenta: '#c0c0c0',
60
+ cyan: '#b8b8b8',
61
+ white: '#aaaaaa',
62
+ brightBlack: '#555555',
63
+ brightRed: '#ff5555',
64
+ brightGreen: '#e0e0e0',
65
+ brightYellow: '#dddddd',
66
+ brightBlue: '#c0c0c0',
67
+ brightMagenta: '#d0d0d0',
68
+ brightCyan: '#c8c8c8',
69
+ brightWhite: '#999999',
70
+ },
71
+ claude: {
72
+ background: '#F5F0E8',
73
+ foreground: '#3D3429',
74
+ cursor: '#3D3429',
75
+ selectionBackground: '#D5CCBC',
76
+ black: '#3D3429',
77
+ red: '#C4533A',
78
+ green: '#5A8A4C',
79
+ yellow: '#B8912D',
80
+ blue: '#DA7756',
81
+ magenta: '#B5647A',
82
+ cyan: '#4A9588',
83
+ white: '#EDE6D9',
84
+ brightBlack: '#9A8C7C',
85
+ brightRed: '#CF6659',
86
+ brightGreen: '#7DAA6E',
87
+ brightYellow: '#D4A84B',
88
+ brightBlue: '#E08A6A',
89
+ brightMagenta: '#C97A8D',
90
+ brightCyan: '#6AAB9E',
91
+ brightWhite: '#F5F0E8',
92
+ },
93
+ };
94
+
95
+ export const THEME_OPTIONS: Array<{ value: ThemeId; label: string }> = [
96
+ { value: 'catppuccin', label: 'Catppuccin Mocha' },
97
+ { value: 'monochrome', label: 'Monochrome' },
98
+ { value: 'claude', label: 'Claude' },
99
+ ];
@@ -0,0 +1,53 @@
1
+ // IPC Channel names
2
+ export const IPC = {
3
+ PTY_CREATE: 'pty:create',
4
+ PTY_WRITE: 'pty:write',
5
+ PTY_RESIZE: 'pty:resize',
6
+ PTY_DISPOSE: 'pty:dispose',
7
+ PTY_DATA: 'pty:data',
8
+ PTY_EXIT: 'pty:exit',
9
+ SHELL_LIST: 'shell:list',
10
+ SESSION_SAVE: 'session:save',
11
+ SESSION_LOAD: 'session:load',
12
+ NOTIFICATION: 'notification:new',
13
+ CWD_CHANGED: 'notification:cwd-changed',
14
+ METADATA_UPDATE: 'metadata:update',
15
+ METADATA_REQUEST: 'metadata:request',
16
+ // Phase 3: RPC bridge (Main ↔ Renderer)
17
+ RPC_COMMAND: 'rpc:command',
18
+ RPC_RESPONSE: 'rpc:response',
19
+ // Clipboard (main process bridge)
20
+ CLIPBOARD_WRITE: 'clipboard:write',
21
+ CLIPBOARD_READ: 'clipboard:read',
22
+ // Phase 4: Auto updater
23
+ UPDATE_CHECK: 'update:check',
24
+ UPDATE_AVAILABLE: 'update:available',
25
+ UPDATE_NOT_AVAILABLE: 'update:not-available',
26
+ UPDATE_ERROR: 'update:error',
27
+ UPDATE_DOWNLOAD: 'update:download',
28
+ UPDATE_INSTALL: 'update:install',
29
+ // Settings sync (renderer → main)
30
+ TOAST_ENABLED: 'settings:toast-enabled',
31
+ } as const;
32
+
33
+ // Named Pipe path for wmux API
34
+ // Fixed name so MCP clients (e.g. Claude Code) can reconnect across wmux restarts
35
+ export const PIPE_NAME = '\\\\.\\pipe\\wmux';
36
+
37
+ export function getPipeName(): string {
38
+ return PIPE_NAME;
39
+ }
40
+
41
+ // Environment variable names injected into PTY sessions
42
+ export const ENV_KEYS = {
43
+ WORKSPACE_ID: 'WMUX_WORKSPACE_ID',
44
+ SURFACE_ID: 'WMUX_SURFACE_ID',
45
+ SOCKET_PATH: 'WMUX_SOCKET_PATH',
46
+ AUTH_TOKEN: 'WMUX_AUTH_TOKEN',
47
+ } as const;
48
+
49
+ // Auth token file path — written by wmux main process, read by MCP server
50
+ export function getAuthTokenPath(): string {
51
+ const home = process.env.USERPROFILE || process.env.HOME || '';
52
+ return `${home}/.wmux-auth-token`;
53
+ }
@@ -0,0 +1,11 @@
1
+ import type { ElectronAPI } from '../preload/index';
2
+
3
+ declare global {
4
+ interface Window {
5
+ electronAPI: ElectronAPI;
6
+ clipboardAPI: {
7
+ writeText: (text: string) => Promise<void>;
8
+ readText: () => Promise<string>;
9
+ };
10
+ }
11
+ }