Skip to content

Commit 782dfe2

Browse files
elsighclaude
andcommitted
Improve TUI layout and fix dynamic MCP capability discovery
TUI improvements: - Embed version in top border line (like Claude Code: ╭── dev3000 v0.0.77 ──) - Move Ctrl+C warning to bottom status line with ⚠️ emoji after first press - Remove spacing between header and log boxes for more compact layout - Remove blank lines in log box (after header, before scroll indicator) - Improve height calculation with safety buffer to prevent header from being pushed up - Gain ~2 lines of vertical space for logs MCP tools fixes: - Fix missing CHROME_DEVTOOLS_CAPABILITY_MAP by using dynamic capability discovery - Fix missing NEXTJS_DEV_CAPABILITY_MAP by using dynamic capability discovery - Add optional chaining for cap.description to handle undefined values - Fix TypeScript type error with mcpType by constraining to valid types 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 92516b4 commit 782dfe2

File tree

2 files changed

+105
-64
lines changed

2 files changed

+105
-64
lines changed

mcp-server/app/mcp/tools.ts

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,8 +1041,12 @@ async function introspectMcpTools(mcpName: string): Promise<McpCapability[]> {
10411041
*/
10421042
function extractFunctionsFromLog(logContent: string, mcpName: string): McpCapability[] {
10431043
const capabilities: McpCapability[] = []
1044-
const mcpType = mcpName.includes("chrome") ? "chrome" : mcpName.includes("nextjs") ? "nextjs" : "unknown"
1045-
const advancedKeywords = ADVANCED_CAPABILITY_KEYWORDS[mcpType] || []
1044+
const mcpType: "chrome" | "nextjs" = mcpName.includes("chrome")
1045+
? "chrome"
1046+
: mcpName.includes("nextjs")
1047+
? "nextjs"
1048+
: "chrome" // default to chrome if unknown
1049+
const advancedKeywords = ADVANCED_CAPABILITY_KEYWORDS[mcpType]
10461050

10471051
// Look for function definitions in various formats
10481052
const patterns = [
@@ -1265,14 +1269,20 @@ async function delegateToChromeDevtools(
12651269
action: string,
12661270
params: Record<string, unknown>
12671271
): Promise<{ content: Array<{ type: "text"; text: string }> }> {
1268-
const mapping = CHROME_DEVTOOLS_CAPABILITY_MAP[action]
1269-
if (!mapping) {
1272+
// Get dynamic capabilities from chrome-devtools MCP
1273+
const capabilities = await discoverMcpCapabilities("dev3000-chrome-devtools")
1274+
1275+
// Find a relevant capability for this action
1276+
const relevantCap = capabilities.find(
1277+
(cap) =>
1278+
cap.function.toLowerCase().includes(action.toLowerCase()) ||
1279+
cap.description?.toLowerCase().includes(action.toLowerCase())
1280+
)
1281+
1282+
if (!relevantCap) {
12701283
throw new Error(`Action ${action} cannot be delegated to chrome-devtools`)
12711284
}
12721285

1273-
// Transform parameters if needed
1274-
const chromeParams = mapping.paramMap ? mapping.paramMap(params) : params
1275-
12761286
return {
12771287
content: [
12781288
{
@@ -1282,13 +1292,13 @@ async function delegateToChromeDevtools(
12821292
For advanced debugging capabilities, use the \`dev3000-chrome-devtools\` MCP:
12831293
12841294
\`\`\`
1285-
dev3000-chrome-devtools:${mapping.function}(${JSON.stringify(chromeParams, null, 2)})
1295+
dev3000-chrome-devtools:${relevantCap.function}(${JSON.stringify(params, null, 2)})
12861296
\`\`\`
12871297
1288-
🎯 **Why use chrome-devtools for this:** ${mapping.reason}
1298+
🎯 **Why use chrome-devtools for this:** ${relevantCap.reason}
12891299
12901300
💡 **When to use each tool:**
1291-
• **dev3000**: Basic browser automation (screenshots, navigation, clicks, simple scripts)
1301+
• **dev3000**: Basic browser automation (screenshots, navigation, clicks, simple scripts)
12921302
• **dev3000-chrome-devtools**: Advanced debugging (DOM inspection, breakpoints, performance profiling, network interception)
12931303
12941304
⚡ **Both tools share the same Chrome instance** - no conflicts or duplicate browsers`
@@ -1301,8 +1311,11 @@ dev3000-chrome-devtools:${mapping.function}(${JSON.stringify(chromeParams, null,
13011311
* Delegate to nextjs-dev MCP with suggested functions
13021312
*/
13031313
async function _delegateToNextjs(): Promise<{ content: Array<{ type: "text"; text: string }> }> {
1304-
const availableFunctions = Object.entries(NEXTJS_DEV_CAPABILITY_MAP)
1305-
.map(([_key, { function: func, reason }]) => `• \`dev3000-nextjs-dev:${func}()\` - ${reason}`)
1314+
// Get dynamic capabilities from nextjs-dev MCP
1315+
const capabilities = await discoverMcpCapabilities("dev3000-nextjs-dev")
1316+
1317+
const availableFunctions = capabilities
1318+
.map((cap) => `• \`dev3000-nextjs-dev:${cap.function}()\` - ${cap.reason}`)
13061319
.join("\n")
13071320

13081321
return {

src/tui-interface-impl.tsx

Lines changed: 80 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ const TUIApp = ({
4242
const logIdCounter = useRef(0)
4343
const { stdout } = useStdout()
4444
const ctrlCMessageDefault = "^C quit"
45-
const [ctrlCMessage] = useState(ctrlCMessageDefault)
45+
const [ctrlCMessage, setCtrlCMessage] = useState(ctrlCMessageDefault)
4646

4747
const [terminalSize, setTerminalSize] = useState(() => ({
4848
width: stdout?.columns || 80,
@@ -82,7 +82,21 @@ const TUIApp = ({
8282

8383
// Provide status update function to parent
8484
useEffect(() => {
85-
onStatusUpdate(setInitStatus)
85+
onStatusUpdate((status: string | null) => {
86+
// Check if this is the "Press Ctrl+C again" warning
87+
if (status?.includes("Press Ctrl+C again")) {
88+
// Update the bottom Ctrl+C message with warning emoji
89+
setCtrlCMessage("⚠️ ^C again to quit")
90+
// Clear the init status since we don't want it in the header anymore
91+
setInitStatus(null)
92+
// Reset after 3 seconds
93+
setTimeout(() => {
94+
setCtrlCMessage(ctrlCMessageDefault)
95+
}, 3000)
96+
} else {
97+
setInitStatus(status)
98+
}
99+
})
86100
}, [onStatusUpdate])
87101

88102
// Calculate available lines for logs dynamically based on terminal height and mode
@@ -94,13 +108,22 @@ const TUIApp = ({
94108
// In compact mode, reduce header size, account for bottom status line
95109
return Math.max(3, termHeight - 10)
96110
} else {
97-
// Normal mode calculation - reduced header lines since we removed Controls section
98-
const headerLines = 10 // Reduced from 12 since Controls section is commented out
99-
const logBoxHeaderLines = 3
100-
const logBoxFooterLines = scrollOffset > 0 ? 3 : 1
101-
const bottomStatusLine = 1 // Just one line for the log path
102-
const safetyBuffer = 0 // Removed safety buffer to use more space
103-
const totalReservedLines = headerLines + logBoxHeaderLines + logBoxFooterLines + bottomStatusLine + safetyBuffer
111+
// Normal mode calculation - account for all UI elements
112+
const headerBorderLines = 2 // Top border (with title) + bottom border
113+
const headerContentLines = 5 // Logo is 4 lines tall, +1 for padding
114+
const logBoxBorderLines = 2 // Top and bottom border of log box
115+
const logBoxHeaderLines = 2 // "Logs (X total)" text (no blank line after)
116+
const logBoxFooterLines = scrollOffset > 0 ? 2 : 0 // "(X lines below)" when scrolled
117+
const bottomStatusLine = 1 // Log path and quit message
118+
const safetyBuffer = 1 // Small buffer to prevent header from being pushed up
119+
const totalReservedLines =
120+
headerBorderLines +
121+
headerContentLines +
122+
logBoxBorderLines +
123+
logBoxHeaderLines +
124+
logBoxFooterLines +
125+
bottomStatusLine +
126+
safetyBuffer
104127
return Math.max(3, termHeight - totalReservedLines)
105128
}
106129
}
@@ -237,41 +260,52 @@ const TUIApp = ({
237260
)
238261

239262
// Render normal header
240-
const renderNormalHeader = () => (
241-
<Box borderStyle="round" borderColor="#A18CE5" paddingX={2} paddingY={1} marginBottom={1} flexDirection="column">
242-
<Box flexDirection="row" gap={3}>
243-
{/* ASCII Logo on the left */}
244-
{/* biome-ignore format: preserve ASCII art alignment */}
245-
<Box flexDirection="column" alignItems="flex-start">
246-
{FULL_LOGO.map((line) => (
247-
<Text key={line} color="#A18CE5" bold>{line}</Text>
248-
))}
249-
</Box>
250-
251-
{/* Info on the right */}
252-
<Box flexDirection="column" flexGrow={1}>
253-
<Text color="#A18CE5" bold>
254-
{commandName} v{version} {initStatus ? `- ${initStatus}` : "is running!"}
255-
</Text>
256-
<Text> </Text>
257-
<Text color="cyan">🌐 Your App: http://localhost:{appPort}</Text>
258-
<Text color="cyan">🤖 MCP Server: http://localhost:{mcpPort}/mcp</Text>
259-
<Text color="cyan">
260-
📸 Visual Timeline: http://localhost:{mcpPort}/logs
261-
{projectName ? `?project=${encodeURIComponent(projectName)}` : ""}
262-
</Text>
263-
{serversOnly && <Text color="cyan">🖥️ Servers-only mode - use Chrome extension for browser monitoring</Text>}
263+
const renderNormalHeader = () => {
264+
// Create custom top border with title embedded (like Claude Code)
265+
const title = ` ${commandName} v${version} ${initStatus ? `- ${initStatus} ` : ""}`
266+
const borderChar = "─"
267+
const leftPadding = 2
268+
// Account for border characters and padding
269+
const availableWidth = termWidth - 2 // -2 for corner characters
270+
const titleLength = title.length
271+
const rightBorderLength = Math.max(0, availableWidth - titleLength - leftPadding)
272+
const topBorderLine = `╭${borderChar.repeat(leftPadding)}${title}${borderChar.repeat(rightBorderLength)}╮`
273+
274+
return (
275+
<Box flexDirection="column">
276+
{/* Custom top border with embedded title */}
277+
<Text color="#A18CE5">{topBorderLine}</Text>
278+
279+
{/* Content with side borders only */}
280+
<Box borderStyle="round" borderColor="#A18CE5" borderTop={false} paddingX={2} paddingY={1}>
281+
<Box flexDirection="row" gap={3}>
282+
{/* ASCII Logo on the left */}
283+
{/* biome-ignore format: preserve ASCII art alignment */}
284+
<Box flexDirection="column" alignItems="flex-start">
285+
{FULL_LOGO.map((line) => (
286+
<Text key={line} color="#A18CE5" bold>
287+
{line}
288+
</Text>
289+
))}
290+
</Box>
291+
292+
{/* Info on the right */}
293+
<Box flexDirection="column" flexGrow={1}>
294+
<Text color="cyan">🌐 Your App: http://localhost:{appPort}</Text>
295+
<Text color="cyan">🤖 MCP Server: http://localhost:{mcpPort}/mcp</Text>
296+
<Text color="cyan">
297+
📸 Visual Timeline: http://localhost:{mcpPort}/logs
298+
{projectName ? `?project=${encodeURIComponent(projectName)}` : ""}
299+
</Text>
300+
{serversOnly && (
301+
<Text color="cyan">🖥️ Servers-only mode - use Chrome extension for browser monitoring</Text>
302+
)}
303+
</Box>
304+
</Box>
264305
</Box>
265306
</Box>
266-
267-
{/* Controls at the bottom of header box */}
268-
{/*
269-
<Box marginTop={1}>
270-
<Text dimColor>💡 Controls: ↑/↓ scroll | PgUp/PgDn page | g/G start/end | Ctrl-C quit</Text>
271-
</Box>
272-
*/}
273-
</Box>
274-
)
307+
)
308+
}
275309

276310
return (
277311
<Box flexDirection="column" height="100%">
@@ -281,12 +315,9 @@ const TUIApp = ({
281315
{/* Logs Box - flexGrow makes it expand to fill available height */}
282316
<Box flexDirection="column" borderStyle="single" borderColor="gray" paddingX={1} flexGrow={1} minHeight={0}>
283317
{!isVeryCompact && (
284-
<>
285-
<Text color="gray" dimColor>
286-
Logs ({logs.length} total{scrollOffset > 0 && `, scrolled up ${scrollOffset} lines`})
287-
</Text>
288-
<Text> </Text>
289-
</>
318+
<Text color="gray" dimColor>
319+
Logs ({logs.length} total{scrollOffset > 0 && `, scrolled up ${scrollOffset} lines`})
320+
</Text>
290321
)}
291322

292323
{/* Logs content area - also uses flexGrow to expand */}
@@ -401,10 +432,7 @@ const TUIApp = ({
401432

402433
{/* Scroll indicator - only show when scrolled up and not in very compact mode */}
403434
{!isVeryCompact && logs.length > maxVisibleLogs && scrollOffset > 0 && (
404-
<>
405-
<Text> </Text>
406-
<Text dimColor>({scrollOffset} lines below)</Text>
407-
</>
435+
<Text dimColor>({scrollOffset} lines below)</Text>
408436
)}
409437
</Box>
410438

0 commit comments

Comments
 (0)