@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,234 +0,0 @@
1
- import { useEffect, useState, useRef } from 'react';
2
- import { useStore } from '../../stores';
3
- import Sidebar from '../Sidebar/Sidebar';
4
- import MiniSidebar from '../Sidebar/MiniSidebar';
5
- import PaneContainer from '../Pane/PaneContainer';
6
- import StatusBar from '../StatusBar/StatusBar';
7
- import NotificationPanel from '../Notification/NotificationPanel';
8
- import CommandPalette from '../Palette/CommandPalette';
9
- import SettingsPanel from '../Settings/SettingsPanel';
10
- import ApprovalDialog from '../Company/ApprovalDialog';
11
- import CompanyView from '../Company/CompanyView';
12
- import MessageFeedPanel from '../Company/MessageFeedPanel';
13
- import { useKeyboard } from '../../hooks/useKeyboard';
14
- import { useNotificationListener } from '../../hooks/useNotificationListener';
15
- import { useRpcBridge } from '../../hooks/useRpcBridge';
16
- import type { SessionData, PaneLeaf } from '../../../shared/types';
17
-
18
- export default function AppLayout() {
19
- const sidebarVisible = useStore((s) => s.sidebarVisible);
20
- const sidebarPosition = useStore((s) => s.sidebarPosition);
21
- const companyViewVisible = useStore((s) => s.companyViewVisible);
22
- const setCompanyViewVisible = useStore((s) => s.setCompanyViewVisible);
23
- const activeWorkspaceId = useStore((s) => s.activeWorkspaceId);
24
- const workspaces = useStore((s) => s.workspaces);
25
- const addSurface = useStore((s) => s.addSurface);
26
-
27
- const activeWorkspace = workspaces.find((w) => w.id === activeWorkspaceId);
28
-
29
- useKeyboard();
30
- useNotificationListener();
31
- useRpcBridge();
32
-
33
- // ─── Drop overlay (VS Code-style) ─────────────────────────────────────
34
- // A transparent full-window overlay appears during external file drags.
35
- // This guarantees the OS-level cursor shows "copy" regardless of WebGL canvas.
36
- const [isDragging, setIsDragging] = useState(false);
37
- const dragCounterRef = useRef(0);
38
-
39
- useEffect(() => {
40
- // dragenter/dragleave fire for every child boundary crossing,
41
- // so we use a counter to track when the drag truly leaves the window.
42
- const onEnter = (e: DragEvent) => {
43
- e.preventDefault();
44
- if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy';
45
- dragCounterRef.current++;
46
- if (dragCounterRef.current === 1) setIsDragging(true);
47
- };
48
- const onLeave = () => {
49
- dragCounterRef.current--;
50
- if (dragCounterRef.current <= 0) {
51
- dragCounterRef.current = 0;
52
- setIsDragging(false);
53
- }
54
- };
55
- const onDrop = (e: DragEvent) => {
56
- e.preventDefault();
57
- dragCounterRef.current = 0;
58
- setIsDragging(false);
59
-
60
- const files = e.dataTransfer?.files;
61
- if (!files || files.length === 0) return;
62
-
63
- // Get active terminal's PTY ID
64
- const state = useStore.getState();
65
- const ws = state.workspaces.find((w) => w.id === state.activeWorkspaceId);
66
- if (!ws) return;
67
-
68
- const findLeaf = (pane: typeof ws.rootPane): PaneLeaf | null => {
69
- if (pane.type === 'leaf') return pane.id === ws.activePaneId ? pane : null;
70
- for (const child of pane.children) {
71
- const found = findLeaf(child);
72
- if (found) return found;
73
- }
74
- return null;
75
- };
76
- const leaf = findLeaf(ws.rootPane);
77
- if (!leaf) return;
78
-
79
- const activeSurface = leaf.surfaces.find((s) => s.id === leaf.activeSurfaceId);
80
- if (!activeSurface || activeSurface.surfaceType === 'browser') return;
81
-
82
- const ptyId = activeSurface.ptyId;
83
- const paths: string[] = [];
84
- for (let i = 0; i < files.length; i++) {
85
- paths.push((files[i] as File & { path: string }).path);
86
- }
87
- const text = paths.map((p) => (p.includes(' ') ? `"${p}"` : p)).join(' ');
88
- window.electronAPI.pty.write(ptyId, text);
89
- };
90
- // Prevent default on dragover at window level to allow drop everywhere
91
- const onOver = (e: DragEvent) => {
92
- e.preventDefault();
93
- if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy';
94
- };
95
- // Use capture phase so these fire before any child element (e.g. xterm
96
- // WebGL canvas) can consume the event and cause a "forbidden" cursor.
97
- window.addEventListener('dragenter', onEnter, true);
98
- window.addEventListener('dragleave', onLeave, true);
99
- window.addEventListener('dragover', onOver, true);
100
- window.addEventListener('drop', onDrop, true);
101
- return () => {
102
- window.removeEventListener('dragenter', onEnter, true);
103
- window.removeEventListener('dragleave', onLeave, true);
104
- window.removeEventListener('dragover', onOver, true);
105
- window.removeEventListener('drop', onDrop, true);
106
- };
107
- }, []);
108
-
109
- // ė•ą ė‹œėž‘ ė‹œ ė„¸ė…˜ ëŗĩ뛐
110
- useEffect(() => {
111
- window.electronAPI.session.load().then((saved: SessionData | null) => {
112
- if (!saved) return;
113
- useStore.getState().loadSession(saved);
114
- });
115
- }, []);
116
-
117
- // Save session on beforeunload
118
- useEffect(() => {
119
- const saveSession = () => {
120
- const state = useStore.getState();
121
- // Strip dangerous flags from session persistence
122
- const companySafe = state.company ? { ...state.company, skipPermissions: undefined } : null;
123
- const data: SessionData = {
124
- workspaces: state.workspaces,
125
- activeWorkspaceId: state.activeWorkspaceId,
126
- sidebarVisible: state.sidebarVisible,
127
- sidebarMode: state.sidebarMode,
128
- company: companySafe,
129
- memberCosts: state.memberCosts,
130
- sessionStartTime: state.sessionStartTime,
131
- // User preferences
132
- theme: state.theme,
133
- locale: state.locale,
134
- terminalFontSize: state.terminalFontSize,
135
- terminalFontFamily: state.terminalFontFamily,
136
- defaultShell: state.defaultShell,
137
- scrollbackLines: state.scrollbackLines,
138
- sidebarPosition: state.sidebarPosition,
139
- notificationSoundEnabled: state.notificationSoundEnabled,
140
- toastEnabled: state.toastEnabled,
141
- notificationRingEnabled: state.notificationRingEnabled,
142
- customKeybindings: state.customKeybindings,
143
- };
144
- window.electronAPI.session.save(data);
145
- };
146
-
147
- window.addEventListener('beforeunload', saveSession);
148
- return () => window.removeEventListener('beforeunload', saveSession);
149
- }, []);
150
-
151
- // Auto-create initial surface for empty leaf panes
152
- // ė„¸ė…˜ ëŗĩė›ëœ ę˛Ŋ뚰: surfaces가 ė´ë¯¸ ėžˆėœŧë¯€ëĄœ ė´ effect는 ė‹¤í–‰ë˜ė§€ ė•ŠėŒ
153
- // 브ëŧėš°ė € surface만 ėžˆëŠ” pane: surfaceTypeė´ 'browser'ė´ëŠ´ PTY ėƒė„ą 늤í‚ĩ
154
- useEffect(() => {
155
- if (!activeWorkspace) return;
156
- const root = activeWorkspace.rootPane;
157
- if (root.type !== 'leaf') return;
158
-
159
- // surfaces가 ëš„ė–´ėžˆė„ 때만 냈 PTY ėƒė„ą
160
- if (root.surfaces.length === 0) {
161
- let cancelled = false;
162
- const paneId = root.id;
163
- window.electronAPI.pty.create().then((result: { id: string }) => {
164
- if (cancelled) {
165
- window.electronAPI.pty.dispose(result.id);
166
- return;
167
- }
168
- addSurface(paneId, result.id, 'Terminal', '');
169
- });
170
- return () => { cancelled = true; };
171
- }
172
-
173
- // surfaces가 ėžˆė§€ë§Œ ëĒ¨ë‘ browser íƒ€ėž…ė¸ ę˛Ŋ뚰 PTY ėƒė„ą 늤í‚ĩ
174
- const hasTerminalSurface = root.surfaces.some(
175
- (s) => !s.surfaceType || s.surfaceType === 'terminal'
176
- );
177
- if (!hasTerminalSurface) {
178
- // 브ëŧėš°ė €ë§Œ ėžˆëŠ” pane — PTY ëļˆí•„ėš”, ė•„ëŦ´ę˛ƒë„ í•˜ė§€ ė•ŠėŒ
179
- return;
180
- }
181
- }, [activeWorkspace?.id]);
182
-
183
- if (!activeWorkspace) return null;
184
-
185
- return (
186
- <div className={`flex h-screen w-screen bg-[var(--bg-base)] overflow-hidden ${sidebarPosition === 'right' ? 'flex-row-reverse' : ''}`}>
187
- {sidebarVisible ? <Sidebar /> : <MiniSidebar />}
188
- <div className="flex-1 min-w-0 flex flex-col">
189
- <StatusBar />
190
- {/* Render ALL workspaces but only show the active one.
191
- This preserves xterm Terminal instances (and their scroll state)
192
- across workspace switches — same pattern as surface tab switching. */}
193
- <div className="flex-1 min-h-0 relative">
194
- {workspaces.map((ws) => (
195
- <div
196
- key={ws.id}
197
- style={{
198
- position: 'absolute',
199
- inset: 0,
200
- display: ws.id === activeWorkspaceId ? 'flex' : 'none',
201
- flexDirection: 'column',
202
- }}
203
- >
204
- <PaneContainer pane={ws.rootPane} isWorkspaceVisible={ws.id === activeWorkspaceId} />
205
- </div>
206
- ))}
207
- </div>
208
- </div>
209
- <NotificationPanel />
210
- <MessageFeedPanel />
211
- <CommandPalette />
212
- <SettingsPanel />
213
- <ApprovalDialog />
214
- {companyViewVisible && (
215
- <CompanyView onClose={() => setCompanyViewVisible(false)} />
216
- )}
217
-
218
- {/* Visual drag indicator — pointer-events always 'none' so it never
219
- blocks clicks, scrolling, or keyboard. Drop handling is done entirely
220
- via the window-level listeners registered in the useEffect above. */}
221
- {isDragging && (
222
- <div
223
- style={{
224
- position: 'fixed',
225
- inset: 0,
226
- zIndex: 99999,
227
- pointerEvents: 'none',
228
- backgroundColor: 'rgba(137, 180, 250, 0.08)',
229
- }}
230
- />
231
- )}
232
- </div>
233
- );
234
- }
@@ -1,129 +0,0 @@
1
- import { useMemo } from 'react';
2
- import { useStore } from '../../stores';
3
- import { useT } from '../../hooks/useT';
4
-
5
- export default function NotificationPanel() {
6
- const t = useT();
7
- const notifications = useStore((s) => s.notifications);
8
- const notificationPanelVisible = useStore((s) => s.notificationPanelVisible);
9
- const toggleNotificationPanel = useStore((s) => s.toggleNotificationPanel);
10
- const markRead = useStore((s) => s.markRead);
11
- const markAllReadForWorkspace = useStore((s) => s.markAllReadForWorkspace);
12
- const clearNotifications = useStore((s) => s.clearNotifications);
13
- const setActiveWorkspace = useStore((s) => s.setActiveWorkspace);
14
- const activeWorkspaceId = useStore((s) => s.activeWorkspaceId);
15
-
16
- const sorted = useMemo(
17
- () => [...notifications].sort((a, b) => b.timestamp - a.timestamp),
18
- [notifications],
19
- );
20
- const unreadCount = useMemo(
21
- () => notifications.filter((n) => !n.read).length,
22
- [notifications],
23
- );
24
-
25
- if (!notificationPanelVisible) return null;
26
-
27
- const handleNotifClick = (notif: typeof sorted[0]) => {
28
- markRead(notif.id);
29
- if (notif.workspaceId !== activeWorkspaceId) {
30
- setActiveWorkspace(notif.workspaceId);
31
- }
32
- };
33
-
34
- const formatTime = (ts: number) => {
35
- const d = new Date(ts);
36
- return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
37
- };
38
-
39
- const typeIcon = (type: string) => {
40
- switch (type) {
41
- case 'agent': return '🤖';
42
- case 'error': return '❌';
43
- case 'warning': return 'âš ī¸';
44
- default: return 'â„šī¸';
45
- }
46
- };
47
-
48
- return (
49
- <div className="fixed right-0 top-0 h-full w-80 bg-[var(--bg-mantle)] border-l border-[var(--bg-surface)] z-50 flex flex-col shadow-2xl notification-panel-enter">
50
- {/* Header */}
51
- <div className="flex items-center justify-between px-4 py-3 border-b border-[var(--bg-surface)]">
52
- <div className="flex items-center gap-2">
53
- <span className="text-sm font-bold text-[var(--text-main)]">{t('notification.title')}</span>
54
- {unreadCount > 0 && (
55
- <span className="bg-[var(--accent-blue)] text-[var(--bg-base)] text-[10px] font-bold px-1.5 py-0.5 rounded-full">
56
- {unreadCount}
57
- </span>
58
- )}
59
- </div>
60
- <div className="flex items-center gap-2">
61
- {notifications.length > 0 && (
62
- <>
63
- <button
64
- className="text-[10px] text-[var(--text-subtle)] hover:text-[var(--accent-blue)] transition-colors"
65
- onClick={() => markAllReadForWorkspace(activeWorkspaceId)}
66
- >
67
- {t('notification.markAllRead')}
68
- </button>
69
- <button
70
- className="text-[10px] text-[var(--text-subtle)] hover:text-[var(--accent-red)] transition-colors"
71
- onClick={clearNotifications}
72
- >
73
- {t('notification.clear')}
74
- </button>
75
- </>
76
- )}
77
- <button
78
- className="text-[var(--text-subtle)] hover:text-[var(--text-main)] text-sm transition-colors"
79
- onClick={toggleNotificationPanel}
80
- >
81
- ✕
82
- </button>
83
- </div>
84
- </div>
85
-
86
- {/* List */}
87
- <div className="flex-1 overflow-y-auto">
88
- {sorted.length === 0 ? (
89
- <div className="flex items-center justify-center h-full text-[var(--text-muted)] text-sm">
90
- {t('notification.empty')}
91
- </div>
92
- ) : (
93
- sorted.map((notif) => (
94
- <div
95
- key={notif.id}
96
- className={`px-4 py-3 border-b border-[rgba(var(--bg-surface-rgb),0.5)] cursor-pointer hover:bg-[rgba(var(--bg-surface-rgb),0.3)] transition-colors ${
97
- notif.read ? 'opacity-60' : ''
98
- }`}
99
- onClick={() => handleNotifClick(notif)}
100
- >
101
- <div className="flex items-start gap-2">
102
- <span className="text-xs mt-0.5">{typeIcon(notif.type)}</span>
103
- <div className="flex-1 min-w-0">
104
- <div className="flex items-center justify-between">
105
- <span className={`text-xs font-medium truncate ${notif.read ? 'text-[var(--text-subtle)]' : 'text-[var(--text-main)]'}`}>
106
- {notif.title}
107
- </span>
108
- <span className="text-[10px] text-[var(--text-muted)] flex-shrink-0 ml-2">
109
- {formatTime(notif.timestamp)}
110
- </span>
111
- </div>
112
- <p className="text-[11px] text-[var(--text-sub2)] mt-0.5 truncate">{notif.body}</p>
113
- </div>
114
- {!notif.read && (
115
- <div className="w-1.5 h-1.5 rounded-full bg-[var(--accent-blue)] mt-1.5 flex-shrink-0" />
116
- )}
117
- </div>
118
- </div>
119
- ))
120
- )}
121
- </div>
122
-
123
- {/* Footer */}
124
- <div className="px-4 py-2 border-t border-[var(--bg-surface)] text-[10px] text-[var(--text-muted)]">
125
- {t('notification.toggle')}
126
- </div>
127
- </div>
128
- );
129
- }