dmux 1.3.3 → 1.5.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.
- package/dist/AutoUpdater.d.ts +35 -0
- package/dist/AutoUpdater.d.ts.map +1 -0
- package/dist/AutoUpdater.js +225 -0
- package/dist/AutoUpdater.js.map +1 -0
- package/dist/BetterTextInput.d.ts +10 -0
- package/dist/BetterTextInput.d.ts.map +1 -0
- package/dist/BetterTextInput.js +177 -0
- package/dist/BetterTextInput.js.map +1 -0
- package/dist/CleanTextInput.d.ts +10 -0
- package/dist/CleanTextInput.d.ts.map +1 -0
- package/dist/CleanTextInput.js +572 -0
- package/dist/CleanTextInput.js.map +1 -0
- package/dist/DmuxApp.d.ts +2 -0
- package/dist/DmuxApp.d.ts.map +1 -1
- package/dist/DmuxApp.js +330 -165
- package/dist/DmuxApp.js.map +1 -1
- package/dist/EnhancedTextInput.d.ts.map +1 -1
- package/dist/EnhancedTextInput.js +155 -50
- package/dist/EnhancedTextInput.js.map +1 -1
- package/dist/GeminiTextInput.d.ts +12 -0
- package/dist/GeminiTextInput.d.ts.map +1 -0
- package/dist/GeminiTextInput.js +210 -0
- package/dist/GeminiTextInput.js.map +1 -0
- package/dist/MultilineTextInput.d.ts +10 -0
- package/dist/MultilineTextInput.d.ts.map +1 -0
- package/dist/MultilineTextInput.js +184 -0
- package/dist/MultilineTextInput.js.map +1 -0
- package/dist/SimpleGeminiInput.d.ts +12 -0
- package/dist/SimpleGeminiInput.d.ts.map +1 -0
- package/dist/SimpleGeminiInput.js +223 -0
- package/dist/SimpleGeminiInput.js.map +1 -0
- package/dist/StyledTextInput.d.ts +10 -0
- package/dist/StyledTextInput.d.ts.map +1 -0
- package/dist/StyledTextInput.js +10 -0
- package/dist/StyledTextInput.js.map +1 -0
- package/dist/index.js +48 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/DmuxApp.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import React, { useState, useEffect } from 'react';
|
|
2
2
|
import { Box, Text, useInput, useApp } from 'ink';
|
|
3
|
-
import
|
|
3
|
+
import CleanTextInput from './CleanTextInput.js';
|
|
4
|
+
import StyledTextInput from './StyledTextInput.js';
|
|
4
5
|
import { execSync } from 'child_process';
|
|
5
6
|
import fs from 'fs/promises';
|
|
6
7
|
import path from 'path';
|
|
7
8
|
import { createRequire } from 'module';
|
|
8
9
|
const require = createRequire(import.meta.url);
|
|
9
10
|
const packageJson = require('../package.json');
|
|
10
|
-
const DmuxApp = ({ dmuxDir, panesFile, projectName, sessionName, settingsFile }) => {
|
|
11
|
+
const DmuxApp = ({ dmuxDir, panesFile, projectName, sessionName, settingsFile, autoUpdater }) => {
|
|
11
12
|
const [panes, setPanes] = useState([]);
|
|
12
13
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
13
14
|
const [showNewPaneDialog, setShowNewPaneDialog] = useState(false);
|
|
@@ -25,14 +26,23 @@ const DmuxApp = ({ dmuxDir, panesFile, projectName, sessionName, settingsFile })
|
|
|
25
26
|
const [showFileCopyPrompt, setShowFileCopyPrompt] = useState(false);
|
|
26
27
|
const [currentCommandType, setCurrentCommandType] = useState(null);
|
|
27
28
|
const [runningCommand, setRunningCommand] = useState(false);
|
|
29
|
+
const [updateInfo, setUpdateInfo] = useState(null);
|
|
30
|
+
const [showUpdateDialog, setShowUpdateDialog] = useState(false);
|
|
31
|
+
const [isUpdating, setIsUpdating] = useState(false);
|
|
32
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
28
33
|
const { exit } = useApp();
|
|
29
34
|
// Track terminal dimensions for responsive layout
|
|
30
35
|
const [terminalWidth, setTerminalWidth] = useState(process.stdout.columns || 80);
|
|
31
36
|
// Load panes and settings on mount and refresh periodically
|
|
32
37
|
useEffect(() => {
|
|
38
|
+
// Load immediately for initial data
|
|
33
39
|
loadPanes();
|
|
34
40
|
loadSettings();
|
|
35
|
-
|
|
41
|
+
// Start polling after a short delay to avoid competing with startup
|
|
42
|
+
let pollingInterval;
|
|
43
|
+
const startPolling = setTimeout(() => {
|
|
44
|
+
pollingInterval = setInterval(loadPanes, 3000); // Poll every 3 seconds instead of 2
|
|
45
|
+
}, 1000); // Wait 1 second before starting polling
|
|
36
46
|
// Handle terminal resize
|
|
37
47
|
const handleResize = () => {
|
|
38
48
|
setTerminalWidth(process.stdout.columns || 80);
|
|
@@ -53,142 +63,202 @@ const DmuxApp = ({ dmuxDir, panesFile, projectName, sessionName, settingsFile })
|
|
|
53
63
|
process.on('SIGINT', handleTermination);
|
|
54
64
|
process.on('SIGTERM', handleTermination);
|
|
55
65
|
return () => {
|
|
56
|
-
|
|
66
|
+
clearTimeout(startPolling);
|
|
67
|
+
if (pollingInterval)
|
|
68
|
+
clearInterval(pollingInterval);
|
|
57
69
|
process.stdout.removeListener('resize', handleResize);
|
|
58
70
|
process.removeListener('SIGINT', handleTermination);
|
|
59
71
|
process.removeListener('SIGTERM', handleTermination);
|
|
60
72
|
};
|
|
61
73
|
}, []);
|
|
74
|
+
// Auto-show new pane dialog when starting with no panes
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
// Only show the dialog if:
|
|
77
|
+
// 1. We have no panes
|
|
78
|
+
// 2. We're not already showing the dialog
|
|
79
|
+
// 3. We're not showing any other dialogs or prompts
|
|
80
|
+
if (panes.length === 0 &&
|
|
81
|
+
!showNewPaneDialog &&
|
|
82
|
+
!showMergeConfirmation &&
|
|
83
|
+
!showCloseOptions &&
|
|
84
|
+
!showCommandPrompt &&
|
|
85
|
+
!showFileCopyPrompt &&
|
|
86
|
+
!showUpdateDialog &&
|
|
87
|
+
!isCreatingPane &&
|
|
88
|
+
!runningCommand &&
|
|
89
|
+
!isUpdating) {
|
|
90
|
+
setShowNewPaneDialog(true);
|
|
91
|
+
}
|
|
92
|
+
}, [panes.length, showNewPaneDialog, showMergeConfirmation, showCloseOptions, showCommandPrompt, showFileCopyPrompt, showUpdateDialog, isCreatingPane, runningCommand, isUpdating]);
|
|
93
|
+
// Check for updates periodically
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
if (!autoUpdater)
|
|
96
|
+
return;
|
|
97
|
+
const checkForUpdates = async () => {
|
|
98
|
+
try {
|
|
99
|
+
const info = await autoUpdater.checkForUpdates();
|
|
100
|
+
if (await autoUpdater.shouldShowUpdateNotification(info)) {
|
|
101
|
+
setUpdateInfo(info);
|
|
102
|
+
setShowUpdateDialog(true);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// Silently ignore update check failures
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
// Delay initial update check to avoid slowing startup
|
|
110
|
+
const initialCheckTimer = setTimeout(() => {
|
|
111
|
+
checkForUpdates();
|
|
112
|
+
}, 3000); // Check after 3 seconds
|
|
113
|
+
// Check for updates every 6 hours
|
|
114
|
+
const updateInterval = setInterval(checkForUpdates, 6 * 60 * 60 * 1000);
|
|
115
|
+
return () => {
|
|
116
|
+
clearTimeout(initialCheckTimer);
|
|
117
|
+
clearInterval(updateInterval);
|
|
118
|
+
};
|
|
119
|
+
}, [autoUpdater]);
|
|
62
120
|
// Monitor Claude status in all panes with proper dependency tracking
|
|
63
121
|
useEffect(() => {
|
|
64
122
|
if (panes.length === 0)
|
|
65
123
|
return;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
124
|
+
// Defer Claude monitoring to avoid slowing down startup and dialog interactions
|
|
125
|
+
const startupDelay = setTimeout(() => {
|
|
126
|
+
const monitorClaudeStatus = async () => {
|
|
127
|
+
// Skip monitoring if any dialog is open to avoid UI freezing
|
|
128
|
+
if (showNewPaneDialog || showMergeConfirmation || showCloseOptions ||
|
|
129
|
+
showCommandPrompt || showFileCopyPrompt || showUpdateDialog) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
// Monitor Claude Code status for all panes
|
|
133
|
+
const updatedPanesWithNulls = await Promise.all(panes.map(async (pane) => {
|
|
134
|
+
try {
|
|
135
|
+
// Skip if recently checked (within 500ms to avoid overlapping checks)
|
|
136
|
+
if (pane.lastClaudeCheck && Date.now() - pane.lastClaudeCheck < 500) {
|
|
137
|
+
return pane;
|
|
138
|
+
}
|
|
139
|
+
// First check if pane exists before trying to capture
|
|
140
|
+
const paneIds = execSync(`tmux list-panes -F '#{pane_id}'`, {
|
|
141
|
+
encoding: 'utf-8',
|
|
142
|
+
stdio: 'pipe'
|
|
143
|
+
}).trim().split('\n');
|
|
144
|
+
if (!paneIds.includes(pane.paneId)) {
|
|
145
|
+
// Pane doesn't exist anymore, return unchanged
|
|
146
|
+
return pane;
|
|
147
|
+
}
|
|
148
|
+
// Capture the last 30 lines of the pane for better detection
|
|
149
|
+
const captureOutput = execSync(`tmux capture-pane -t '${pane.paneId}' -p -S -30`, { encoding: 'utf-8', stdio: 'pipe' });
|
|
150
|
+
// Pattern detection for Claude Code states
|
|
151
|
+
// Working patterns - Claude is processing
|
|
152
|
+
// The ONLY reliable indicator is "(esc to interrupt)"
|
|
153
|
+
const workingPatterns = [
|
|
154
|
+
/esc to interrupt/i, // The ONLY reliable indicator that Claude is actively working
|
|
155
|
+
];
|
|
156
|
+
// Extract last few lines to check for patterns
|
|
157
|
+
const lines = captureOutput.split('\n');
|
|
158
|
+
const lastLines = lines.slice(-10).join('\n');
|
|
159
|
+
// Check if Claude's input box is present (waiting for input)
|
|
160
|
+
const hasInputBox = /╭─+╮/.test(lastLines) && /╰─+╯/.test(lastLines) && /│\s+>\s+.*│/.test(lastLines);
|
|
161
|
+
// Permission/attention patterns - needs user input (very forgiving)
|
|
162
|
+
const attentionPatterns = [
|
|
163
|
+
/\?\s*$/m, // Any line ending with a question mark
|
|
164
|
+
/y\/n/i, // Any y/n prompt
|
|
165
|
+
/yes.*no/i, // Yes or no prompts
|
|
166
|
+
/\ballow\b.*\?/i,
|
|
167
|
+
/\bapprove\b.*\?/i,
|
|
168
|
+
/\bgrant\b.*\?/i,
|
|
169
|
+
/\btrust\b.*\?/i,
|
|
170
|
+
/\baccept\b.*\?/i,
|
|
171
|
+
/\bcontinue\b.*\?/i,
|
|
172
|
+
/\bproceed\b.*\?/i,
|
|
173
|
+
/permission/i,
|
|
174
|
+
/confirmation/i,
|
|
175
|
+
/press.*enter/i,
|
|
176
|
+
/waiting for/i,
|
|
177
|
+
/are you sure/i,
|
|
178
|
+
/would you like/i,
|
|
179
|
+
/do you want/i,
|
|
180
|
+
/please confirm/i,
|
|
181
|
+
/requires.*approval/i,
|
|
182
|
+
/needs.*input/i,
|
|
183
|
+
/⏵⏵\s*accept edits/i, // Claude's accept edits mode
|
|
184
|
+
/shift\+tab to cycle/i, // Claude's interface hints
|
|
185
|
+
];
|
|
186
|
+
// Check if Claude is working
|
|
187
|
+
const isWorking = workingPatterns.some(pattern => pattern.test(captureOutput));
|
|
188
|
+
// Check if Claude needs attention
|
|
189
|
+
const needsAttention = attentionPatterns.some(pattern => pattern.test(captureOutput)) || hasInputBox;
|
|
190
|
+
// Determine status - working takes precedence
|
|
191
|
+
let newStatus = 'idle';
|
|
192
|
+
if (isWorking) {
|
|
193
|
+
newStatus = 'working';
|
|
194
|
+
}
|
|
195
|
+
else if (needsAttention && !isWorking) {
|
|
196
|
+
// Only show as waiting if NOT working (working takes precedence)
|
|
197
|
+
newStatus = 'waiting';
|
|
198
|
+
}
|
|
199
|
+
// Additional checks for specific Claude states
|
|
200
|
+
// If we see "accept edits" without other working indicators, it's waiting
|
|
201
|
+
if (/accept edits/i.test(captureOutput) && !/esc to interrupt/i.test(captureOutput)) {
|
|
202
|
+
newStatus = 'waiting';
|
|
203
|
+
}
|
|
204
|
+
// If Claude's input box is visible and no working indicators, it's waiting for input
|
|
205
|
+
if (hasInputBox && !isWorking) {
|
|
206
|
+
newStatus = 'waiting';
|
|
207
|
+
}
|
|
208
|
+
// Check for specific Claude question patterns that might not end with ?
|
|
209
|
+
const claudeQuestionPatterns = [
|
|
210
|
+
/I (can|could|should|would|will|may|might)/i,
|
|
211
|
+
/Let me know/i,
|
|
212
|
+
/Please (tell|let|inform|advise)/i,
|
|
213
|
+
/Would you prefer/i,
|
|
214
|
+
/Should I (proceed|continue|go ahead)/i,
|
|
215
|
+
];
|
|
216
|
+
if (claudeQuestionPatterns.some(pattern => pattern.test(lastLines)) && !isWorking) {
|
|
217
|
+
newStatus = 'waiting';
|
|
218
|
+
}
|
|
219
|
+
// Return updated pane if status changed
|
|
220
|
+
if (pane.claudeStatus !== newStatus) {
|
|
221
|
+
return {
|
|
222
|
+
...pane,
|
|
223
|
+
claudeStatus: newStatus,
|
|
224
|
+
lastClaudeCheck: Date.now()
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
// Just update timestamp
|
|
156
228
|
return {
|
|
157
229
|
...pane,
|
|
158
|
-
claudeStatus: newStatus,
|
|
159
230
|
lastClaudeCheck: Date.now()
|
|
160
231
|
};
|
|
161
232
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
233
|
+
catch (error) {
|
|
234
|
+
// If we can't capture the pane, it might be dead - mark it for removal
|
|
235
|
+
return null; // Will be filtered out below
|
|
236
|
+
}
|
|
237
|
+
}));
|
|
238
|
+
// Filter out null values (dead panes) and keep only valid panes
|
|
239
|
+
const updatedPanes = updatedPanesWithNulls.filter((pane) => pane !== null);
|
|
240
|
+
// Check if we have changes (including pane removals)
|
|
241
|
+
const hasChanges = updatedPanes.length !== panes.length ||
|
|
242
|
+
updatedPanes.some((pane, index) => pane.claudeStatus !== panes[index]?.claudeStatus);
|
|
243
|
+
if (hasChanges) {
|
|
244
|
+
setPanes(updatedPanes);
|
|
245
|
+
// Save to file
|
|
246
|
+
await fs.writeFile(panesFile, JSON.stringify(updatedPanes, null, 2));
|
|
171
247
|
}
|
|
172
|
-
}
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
//
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
await fs.writeFile(panesFile, JSON.stringify(updatedPanes, null, 2));
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
// Run monitoring immediately
|
|
185
|
-
monitorClaudeStatus();
|
|
186
|
-
// Set up interval for continuous monitoring
|
|
187
|
-
const claudeInterval = setInterval(monitorClaudeStatus, 1000); // Check every second
|
|
248
|
+
};
|
|
249
|
+
// Run monitoring after delay
|
|
250
|
+
monitorClaudeStatus();
|
|
251
|
+
// Set up interval for continuous monitoring (less frequent for better performance)
|
|
252
|
+
const claudeInterval = setInterval(monitorClaudeStatus, 2000); // Check every 2 seconds
|
|
253
|
+
return () => {
|
|
254
|
+
clearInterval(claudeInterval);
|
|
255
|
+
};
|
|
256
|
+
}, 500); // Wait 500ms before starting monitoring
|
|
188
257
|
return () => {
|
|
189
|
-
|
|
258
|
+
clearTimeout(startupDelay);
|
|
190
259
|
};
|
|
191
|
-
}, [panes, panesFile
|
|
260
|
+
}, [panes, panesFile, showNewPaneDialog, showMergeConfirmation, showCloseOptions,
|
|
261
|
+
showCommandPrompt, showFileCopyPrompt, showUpdateDialog]); // Re-run when panes or dialogs change
|
|
192
262
|
const loadSettings = async () => {
|
|
193
263
|
try {
|
|
194
264
|
const content = await fs.readFile(settingsFile, 'utf-8');
|
|
@@ -205,42 +275,58 @@ const DmuxApp = ({ dmuxDir, panesFile, projectName, sessionName, settingsFile })
|
|
|
205
275
|
setProjectSettings(settings);
|
|
206
276
|
};
|
|
207
277
|
const loadPanes = async () => {
|
|
278
|
+
// Skip loading if any dialog is open to avoid UI freezing
|
|
279
|
+
if (showNewPaneDialog || showMergeConfirmation || showCloseOptions ||
|
|
280
|
+
showCommandPrompt || showFileCopyPrompt || showUpdateDialog) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
if (isLoading) {
|
|
284
|
+
// Don't set loading to false immediately - keep it true for initial load
|
|
285
|
+
}
|
|
208
286
|
try {
|
|
209
287
|
const content = await fs.readFile(panesFile, 'utf-8');
|
|
210
288
|
const loadedPanes = JSON.parse(content);
|
|
211
|
-
//
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
289
|
+
// Get all pane IDs in a single call (much faster)
|
|
290
|
+
let allPaneIds = [];
|
|
291
|
+
try {
|
|
292
|
+
allPaneIds = execSync(`tmux list-panes -F '#{pane_id}'`, {
|
|
293
|
+
encoding: 'utf-8',
|
|
294
|
+
stdio: 'pipe'
|
|
295
|
+
}).trim().split('\n').filter(id => id);
|
|
296
|
+
}
|
|
297
|
+
catch {
|
|
298
|
+
// No panes or tmux error
|
|
299
|
+
}
|
|
300
|
+
// Filter out dead panes
|
|
301
|
+
const activePanes = loadedPanes.filter(pane => allPaneIds.includes(pane.paneId));
|
|
302
|
+
// Batch update all pane titles in one go (only if needed)
|
|
303
|
+
if (activePanes.length > 0) {
|
|
304
|
+
// Update titles for all active panes at once
|
|
305
|
+
activePanes.forEach(pane => {
|
|
306
|
+
try {
|
|
307
|
+
execSync(`tmux select-pane -t '${pane.paneId}' -T "${pane.slug}"`, { stdio: 'pipe' });
|
|
229
308
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
});
|
|
309
|
+
catch {
|
|
310
|
+
// Ignore if setting title fails
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
}
|
|
236
314
|
setPanes(activePanes);
|
|
237
315
|
// Save cleaned list
|
|
238
316
|
if (activePanes.length !== loadedPanes.length) {
|
|
239
317
|
await fs.writeFile(panesFile, JSON.stringify(activePanes, null, 2));
|
|
240
318
|
}
|
|
319
|
+
// Set loading to false after first load
|
|
320
|
+
if (isLoading) {
|
|
321
|
+
setIsLoading(false);
|
|
322
|
+
}
|
|
241
323
|
}
|
|
242
324
|
catch {
|
|
243
325
|
setPanes([]);
|
|
326
|
+
// Set loading to false even on error
|
|
327
|
+
if (isLoading) {
|
|
328
|
+
setIsLoading(false);
|
|
329
|
+
}
|
|
244
330
|
}
|
|
245
331
|
};
|
|
246
332
|
const getPanePositions = () => {
|
|
@@ -272,7 +358,7 @@ const DmuxApp = ({ dmuxDir, panesFile, projectName, sessionName, settingsFile })
|
|
|
272
358
|
return { row, col };
|
|
273
359
|
};
|
|
274
360
|
const findCardInDirection = (currentIndex, direction) => {
|
|
275
|
-
const totalItems = panes.length + 1; // +1 for "New dmux pane" button
|
|
361
|
+
const totalItems = panes.length + (isLoading ? 0 : 1); // +1 for "New dmux pane" button when not loading
|
|
276
362
|
const currentPos = getCardGridPosition(currentIndex);
|
|
277
363
|
// Calculate cards per row based on current terminal width
|
|
278
364
|
const cardWidth = 35 + 2; // Card width plus gap
|
|
@@ -1479,6 +1565,45 @@ const DmuxApp = ({ dmuxDir, panesFile, projectName, sessionName, settingsFile })
|
|
|
1479
1565
|
setTimeout(() => setStatusMessage(''), 3000);
|
|
1480
1566
|
}
|
|
1481
1567
|
};
|
|
1568
|
+
// Update handling functions
|
|
1569
|
+
const performUpdate = async () => {
|
|
1570
|
+
if (!autoUpdater || !updateInfo)
|
|
1571
|
+
return;
|
|
1572
|
+
try {
|
|
1573
|
+
setIsUpdating(true);
|
|
1574
|
+
setStatusMessage('Updating dmux...');
|
|
1575
|
+
const success = await autoUpdater.performUpdate(updateInfo);
|
|
1576
|
+
if (success) {
|
|
1577
|
+
setStatusMessage('Update completed successfully! Please restart dmux.');
|
|
1578
|
+
setTimeout(() => {
|
|
1579
|
+
process.exit(0);
|
|
1580
|
+
}, 3000);
|
|
1581
|
+
}
|
|
1582
|
+
else {
|
|
1583
|
+
setStatusMessage('Update failed. Please update manually.');
|
|
1584
|
+
setTimeout(() => setStatusMessage(''), 3000);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
catch (error) {
|
|
1588
|
+
setStatusMessage('Update failed. Please update manually.');
|
|
1589
|
+
setTimeout(() => setStatusMessage(''), 3000);
|
|
1590
|
+
}
|
|
1591
|
+
finally {
|
|
1592
|
+
setIsUpdating(false);
|
|
1593
|
+
setShowUpdateDialog(false);
|
|
1594
|
+
}
|
|
1595
|
+
};
|
|
1596
|
+
const skipUpdate = async () => {
|
|
1597
|
+
if (!autoUpdater || !updateInfo)
|
|
1598
|
+
return;
|
|
1599
|
+
await autoUpdater.skipVersion(updateInfo.latestVersion);
|
|
1600
|
+
setShowUpdateDialog(false);
|
|
1601
|
+
setUpdateInfo(null);
|
|
1602
|
+
};
|
|
1603
|
+
const dismissUpdate = () => {
|
|
1604
|
+
setShowUpdateDialog(false);
|
|
1605
|
+
setUpdateInfo(null);
|
|
1606
|
+
};
|
|
1482
1607
|
// Cleanup function for exit
|
|
1483
1608
|
const cleanExit = () => {
|
|
1484
1609
|
// Clear screen multiple times to ensure no artifacts
|
|
@@ -1495,8 +1620,20 @@ const DmuxApp = ({ dmuxDir, panesFile, projectName, sessionName, settingsFile })
|
|
|
1495
1620
|
exit();
|
|
1496
1621
|
};
|
|
1497
1622
|
useInput(async (input, key) => {
|
|
1498
|
-
if (isCreatingPane || runningCommand) {
|
|
1499
|
-
// Disable input while performing operations
|
|
1623
|
+
if (isCreatingPane || runningCommand || isUpdating || isLoading) {
|
|
1624
|
+
// Disable input while performing operations or loading
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
if (showUpdateDialog && updateInfo) {
|
|
1628
|
+
if (input === 'u' || input === 'U') {
|
|
1629
|
+
await performUpdate();
|
|
1630
|
+
}
|
|
1631
|
+
else if (input === 's' || input === 'S') {
|
|
1632
|
+
await skipUpdate();
|
|
1633
|
+
}
|
|
1634
|
+
else if (input === 'l' || input === 'L' || key.escape) {
|
|
1635
|
+
dismissUpdate();
|
|
1636
|
+
}
|
|
1500
1637
|
return;
|
|
1501
1638
|
}
|
|
1502
1639
|
if (showFileCopyPrompt) {
|
|
@@ -1645,8 +1782,11 @@ const DmuxApp = ({ dmuxDir, panesFile, projectName, sessionName, settingsFile })
|
|
|
1645
1782
|
if (input === 'q') {
|
|
1646
1783
|
cleanExit();
|
|
1647
1784
|
}
|
|
1648
|
-
else if (input === 'n' || (key.return && selectedIndex === panes.length)) {
|
|
1785
|
+
else if (!isLoading && (input === 'n' || (key.return && selectedIndex === panes.length))) {
|
|
1786
|
+
// Defer state updates slightly to avoid blocking UI
|
|
1649
1787
|
setShowNewPaneDialog(true);
|
|
1788
|
+
setTimeout(() => setNewPanePrompt(''), 0); // Clear after dialog renders
|
|
1789
|
+
return; // Consume the 'n' keystroke so it doesn't propagate
|
|
1650
1790
|
}
|
|
1651
1791
|
else if (input === 'j' && selectedIndex < panes.length) {
|
|
1652
1792
|
jumpToPane(panes[selectedIndex].paneId);
|
|
@@ -1725,20 +1865,23 @@ const DmuxApp = ({ dmuxDir, panesFile, projectName, sessionName, settingsFile })
|
|
|
1725
1865
|
" ",
|
|
1726
1866
|
pane.devUrl.replace(/https?:\/\//, '').substring(0, 15))))))))));
|
|
1727
1867
|
}),
|
|
1728
|
-
React.createElement(Box, { paddingX: 1, borderStyle: "single", borderColor: selectedIndex === panes.length ? 'green' : 'gray', width: 35, flexShrink: 0 },
|
|
1729
|
-
React.createElement(Text, { color: selectedIndex === panes.length ? 'green' : 'white' }, "+ New dmux pane"))),
|
|
1730
|
-
|
|
1731
|
-
React.createElement(Box, { flexDirection: "
|
|
1732
|
-
React.createElement(Text,
|
|
1733
|
-
React.createElement(
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1868
|
+
!isLoading && !showNewPaneDialog && (React.createElement(Box, { paddingX: 1, borderStyle: "single", borderColor: selectedIndex === panes.length ? 'green' : 'gray', width: 35, flexShrink: 0 },
|
|
1869
|
+
React.createElement(Text, { color: selectedIndex === panes.length ? 'green' : 'white' }, "+ New dmux pane")))),
|
|
1870
|
+
isLoading && (React.createElement(Box, { borderStyle: "round", borderColor: "cyan", paddingX: 2, marginTop: 1 },
|
|
1871
|
+
React.createElement(Box, { flexDirection: "row", gap: 1 },
|
|
1872
|
+
React.createElement(Text, { color: "cyan" }, "\u23F3"),
|
|
1873
|
+
React.createElement(Text, null, "Loading dmux sessions...")))),
|
|
1874
|
+
showNewPaneDialog && (React.createElement(Box, { flexDirection: "column", marginTop: 1 },
|
|
1875
|
+
React.createElement(Text, null, "Enter initial Claude prompt (ESC to cancel):"),
|
|
1876
|
+
React.createElement(Box, { borderStyle: "round", borderColor: "#E67E22", paddingX: 1, marginTop: 1 },
|
|
1877
|
+
React.createElement(CleanTextInput, { value: newPanePrompt, onChange: setNewPanePrompt, onSubmit: (expandedValue) => {
|
|
1878
|
+
// Use expanded value if provided (for paste references), otherwise use raw value
|
|
1879
|
+
createNewPane(expandedValue || newPanePrompt);
|
|
1880
|
+
setShowNewPaneDialog(false);
|
|
1881
|
+
setNewPanePrompt('');
|
|
1882
|
+
} })),
|
|
1883
|
+
React.createElement(Box, { marginTop: 1 },
|
|
1884
|
+
React.createElement(Text, { dimColor: true, italic: true }, "Press Ctrl+O to open in $EDITOR for complex multi-line input")))),
|
|
1742
1885
|
isCreatingPane && (React.createElement(Box, { borderStyle: "single", borderColor: "yellow", paddingX: 1, marginTop: 1 },
|
|
1743
1886
|
React.createElement(Text, { color: "yellow" },
|
|
1744
1887
|
React.createElement(Text, { bold: true }, "\u23F3 Creating new pane... "),
|
|
@@ -1786,7 +1929,7 @@ const DmuxApp = ({ dmuxDir, panesFile, projectName, sessionName, settingsFile })
|
|
|
1786
1929
|
" in worktrees"),
|
|
1787
1930
|
React.createElement(Text, { dimColor: true }, "(Press Enter with empty input for suggested command, ESC to cancel)"),
|
|
1788
1931
|
React.createElement(Box, { marginTop: 1 },
|
|
1789
|
-
React.createElement(
|
|
1932
|
+
React.createElement(StyledTextInput, { value: commandInput, onChange: setCommandInput, placeholder: showCommandPrompt === 'test' ? 'e.g., npm test, pnpm test' : 'e.g., npm run dev, pnpm dev' }))))),
|
|
1790
1933
|
showFileCopyPrompt && (React.createElement(Box, { borderStyle: "double", borderColor: "yellow", paddingX: 1, marginTop: 1 },
|
|
1791
1934
|
React.createElement(Box, { flexDirection: "column" },
|
|
1792
1935
|
React.createElement(Text, { color: "yellow", bold: true }, "First Run Setup"),
|
|
@@ -1797,6 +1940,28 @@ const DmuxApp = ({ dmuxDir, panesFile, projectName, sessionName, settingsFile })
|
|
|
1797
1940
|
runningCommand && (React.createElement(Box, { borderStyle: "single", borderColor: "blue", paddingX: 1, marginTop: 1 },
|
|
1798
1941
|
React.createElement(Text, { color: "blue" },
|
|
1799
1942
|
React.createElement(Text, { bold: true }, "\u25B6 Running command...")))),
|
|
1943
|
+
isUpdating && (React.createElement(Box, { borderStyle: "single", borderColor: "yellow", paddingX: 1, marginTop: 1 },
|
|
1944
|
+
React.createElement(Text, { color: "yellow" },
|
|
1945
|
+
React.createElement(Text, { bold: true }, "\u2B07 Updating dmux...")))),
|
|
1946
|
+
showUpdateDialog && updateInfo && (React.createElement(Box, { borderStyle: "double", borderColor: "green", paddingX: 1, marginTop: 1 },
|
|
1947
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
1948
|
+
React.createElement(Text, { color: "green", bold: true }, "\uD83C\uDF89 dmux Update Available!"),
|
|
1949
|
+
React.createElement(Text, null,
|
|
1950
|
+
"Current version: ",
|
|
1951
|
+
React.createElement(Text, { color: "cyan" }, updateInfo.currentVersion)),
|
|
1952
|
+
React.createElement(Text, null,
|
|
1953
|
+
"Latest version: ",
|
|
1954
|
+
React.createElement(Text, { color: "green" }, updateInfo.latestVersion)),
|
|
1955
|
+
updateInfo.installMethod === 'global' && updateInfo.packageManager && (React.createElement(Text, null,
|
|
1956
|
+
"Detected global install via: ",
|
|
1957
|
+
React.createElement(Text, { color: "yellow" }, updateInfo.packageManager))),
|
|
1958
|
+
React.createElement(Box, { marginTop: 1 }, updateInfo.installMethod === 'global' && updateInfo.packageManager ? (React.createElement(Text, null, "[U]pdate now \u2022 [S]kip this version \u2022 [L]ater")) : (React.createElement(Text, null,
|
|
1959
|
+
"Manual update required: ",
|
|
1960
|
+
React.createElement(Text, { color: "cyan" },
|
|
1961
|
+
updateInfo.packageManager || 'npm',
|
|
1962
|
+
" update -g dmux"),
|
|
1963
|
+
'\n',
|
|
1964
|
+
"[S]kip this version \u2022 [L]ater")))))),
|
|
1800
1965
|
statusMessage && (React.createElement(Box, { marginTop: 1 },
|
|
1801
1966
|
React.createElement(Text, { color: "green" }, statusMessage))),
|
|
1802
1967
|
!showNewPaneDialog && !showCommandPrompt && (React.createElement(Box, { marginTop: 1, flexDirection: "column" },
|