feat: port CLI, TUI, and launcher from v1.5 into v2 (#1452)
Ports the CLI, TUI, and launcher from v1.5 into v2 behind a single `mcp-inspector` entrypoint (launcher → web/cli/tui). Includes the `mcp-inspector --web --dev` launcher fix (Vite alias resolution + prod-server double-start), the root `postinstall` install cascade with simplified CI, and spec/AGENTS docs (no-workspaces rationale, Development setup). Closes #1246. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
53dc4549be
commit
2488ca5e38
1 changed files with 25594 additions and 236 deletions
83
clients/tui/src/components/NotificationsTab.tsx
Normal file
83
clients/tui/src/components/NotificationsTab.tsx
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
import React, { useEffect, useRef } from "react";
|
||||
import { Box, Text, useInput, type Key } from "ink";
|
||||
import { ScrollView, type ScrollViewRef } from "ink-scroll-view";
|
||||
import type { StderrLogEntry } from "@inspector/core/mcp/index.js";
|
||||
|
||||
interface NotificationsTabProps {
|
||||
stderrLogs: StderrLogEntry[];
|
||||
width: number;
|
||||
height: number;
|
||||
onCountChange?: (count: number) => void;
|
||||
focused?: boolean;
|
||||
}
|
||||
|
||||
export function NotificationsTab({
|
||||
stderrLogs,
|
||||
width,
|
||||
height,
|
||||
onCountChange,
|
||||
focused = false,
|
||||
}: NotificationsTabProps) {
|
||||
const scrollViewRef = useRef<ScrollViewRef>(null);
|
||||
const onCountChangeRef = useRef(onCountChange);
|
||||
|
||||
// Update ref when callback changes
|
||||
useEffect(() => {
|
||||
onCountChangeRef.current = onCountChange;
|
||||
}, [onCountChange]);
|
||||
|
||||
useEffect(() => {
|
||||
onCountChangeRef.current?.(stderrLogs.length);
|
||||
}, [stderrLogs.length]);
|
||||
|
||||
// Handle keyboard input for scrolling
|
||||
useInput(
|
||||
(input: string, key: Key) => {
|
||||
if (focused) {
|
||||
if (key.upArrow) {
|
||||
scrollViewRef.current?.scrollBy(-1);
|
||||
} else if (key.downArrow) {
|
||||
scrollViewRef.current?.scrollBy(1);
|
||||
} else if (key.pageUp) {
|
||||
const viewportHeight =
|
||||
scrollViewRef.current?.getViewportHeight() || 1;
|
||||
scrollViewRef.current?.scrollBy(-viewportHeight);
|
||||
} else if (key.pageDown) {
|
||||
const viewportHeight =
|
||||
scrollViewRef.current?.getViewportHeight() || 1;
|
||||
scrollViewRef.current?.scrollBy(viewportHeight);
|
||||
}
|
||||
}
|
||||
},
|
||||
{ isActive: focused },
|
||||
);
|
||||
|
||||
return (
|
||||
<Box width={width} height={height} flexDirection="column" paddingX={1}>
|
||||
<Box paddingY={1} flexShrink={0}>
|
||||
<Text bold backgroundColor={focused ? "yellow" : undefined}>
|
||||
Logging ({stderrLogs.length})
|
||||
</Text>
|
||||
</Box>
|
||||
{stderrLogs.length === 0 ? (
|
||||
<Box paddingY={1}>
|
||||
<Text dimColor>No stderr output yet</Text>
|
||||
</Box>
|
||||
) : (
|
||||
<ScrollView ref={scrollViewRef} height={height - 3}>
|
||||
{stderrLogs.map((log, index) => (
|
||||
<Box
|
||||
key={`log-${log.timestamp.getTime()}-${index}`}
|
||||
paddingY={0}
|
||||
flexDirection="row"
|
||||
flexShrink={0}
|
||||
>
|
||||
<Text dimColor>[{log.timestamp.toLocaleTimeString()}] </Text>
|
||||
<Text color="red">{log.message}</Text>
|
||||
</Box>
|
||||
))}
|
||||
</ScrollView>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue